@hyperdrive.bot/gut 0.1.3
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 +809 -0
- package/bin/dev +16 -0
- package/bin/run +5 -0
- package/dist/base-command.d.ts +21 -0
- package/dist/base-command.js +110 -0
- package/dist/commands/add.d.ts +13 -0
- package/dist/commands/add.js +73 -0
- package/dist/commands/affected.d.ts +23 -0
- package/dist/commands/affected.js +326 -0
- package/dist/commands/audit.d.ts +33 -0
- package/dist/commands/audit.js +593 -0
- package/dist/commands/back.d.ts +6 -0
- package/dist/commands/back.js +29 -0
- package/dist/commands/commit.d.ts +11 -0
- package/dist/commands/commit.js +113 -0
- package/dist/commands/context.d.ts +6 -0
- package/dist/commands/context.js +36 -0
- package/dist/commands/contexts.d.ts +7 -0
- package/dist/commands/contexts.js +92 -0
- package/dist/commands/deps.d.ts +10 -0
- package/dist/commands/deps.js +104 -0
- package/dist/commands/entity/add.d.ts +16 -0
- package/dist/commands/entity/add.js +105 -0
- package/dist/commands/entity/clone-all.d.ts +17 -0
- package/dist/commands/entity/clone-all.js +135 -0
- package/dist/commands/entity/clone.d.ts +15 -0
- package/dist/commands/entity/clone.js +109 -0
- package/dist/commands/entity/list.d.ts +11 -0
- package/dist/commands/entity/list.js +82 -0
- package/dist/commands/entity/remove.d.ts +12 -0
- package/dist/commands/entity/remove.js +58 -0
- package/dist/commands/focus.d.ts +19 -0
- package/dist/commands/focus.js +139 -0
- package/dist/commands/graph.d.ts +18 -0
- package/dist/commands/graph.js +238 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +84 -0
- package/dist/commands/insights.d.ts +21 -0
- package/dist/commands/insights.js +434 -0
- package/dist/commands/patterns.d.ts +40 -0
- package/dist/commands/patterns.js +412 -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 +101 -0
- package/dist/commands/quick-setup.d.ts +20 -0
- package/dist/commands/quick-setup.js +422 -0
- package/dist/commands/recent.d.ts +9 -0
- package/dist/commands/recent.js +55 -0
- package/dist/commands/related.d.ts +23 -0
- package/dist/commands/related.js +257 -0
- package/dist/commands/repos.d.ts +14 -0
- package/dist/commands/repos.js +185 -0
- package/dist/commands/stack.d.ts +10 -0
- package/dist/commands/stack.js +83 -0
- package/dist/commands/status.d.ts +14 -0
- package/dist/commands/status.js +246 -0
- package/dist/commands/sync.d.ts +11 -0
- package/dist/commands/sync.js +142 -0
- package/dist/commands/unfocus.d.ts +6 -0
- package/dist/commands/unfocus.js +23 -0
- package/dist/commands/used-by.d.ts +10 -0
- package/dist/commands/used-by.js +111 -0
- package/dist/commands/workspace.d.ts +20 -0
- package/dist/commands/workspace.js +365 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/models/entity.model.d.ts +81 -0
- package/dist/models/entity.model.js +2 -0
- package/dist/services/config.service.d.ts +34 -0
- package/dist/services/config.service.js +230 -0
- package/dist/services/entity.service.d.ts +19 -0
- package/dist/services/entity.service.js +130 -0
- package/dist/services/focus.service.d.ts +70 -0
- package/dist/services/focus.service.js +587 -0
- package/dist/services/git.service.d.ts +37 -0
- package/dist/services/git.service.js +180 -0
- package/dist/utils/display.d.ts +25 -0
- package/dist/utils/display.js +150 -0
- package/dist/utils/filesystem.d.ts +32 -0
- package/dist/utils/filesystem.js +220 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.js +196 -0
- package/oclif.manifest.json +1463 -0
- package/package.json +76 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const base_command_1 = tslib_1.__importDefault(require("../base-command"));
|
|
6
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
+
const path = tslib_1.__importStar(require("path"));
|
|
8
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = tslib_1.__importDefault(require("ora"));
|
|
10
|
+
class Related extends base_command_1.default {
|
|
11
|
+
static description = 'Find entities related to a specific entity or current focus';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %> my-app',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --threshold 0.3',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> --type dependencies',
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
threshold: core_1.Flags.string({
|
|
19
|
+
char: 't',
|
|
20
|
+
description: 'Minimum relation score (0-1)',
|
|
21
|
+
default: '0.2',
|
|
22
|
+
}),
|
|
23
|
+
type: core_1.Flags.string({
|
|
24
|
+
description: 'Type of relation to check',
|
|
25
|
+
options: ['all', 'dependencies', 'git', 'files'],
|
|
26
|
+
default: 'all',
|
|
27
|
+
}),
|
|
28
|
+
detailed: core_1.Flags.boolean({
|
|
29
|
+
char: 'd',
|
|
30
|
+
description: 'Show detailed relation information',
|
|
31
|
+
default: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
static args = {
|
|
35
|
+
entity: core_1.Args.string({
|
|
36
|
+
name: 'entity',
|
|
37
|
+
required: false,
|
|
38
|
+
description: 'Entity name (uses current focus if not provided)',
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const { args, flags } = await this.parse(Related);
|
|
43
|
+
const { threshold, type, detailed } = flags;
|
|
44
|
+
const thresholdNum = parseFloat(threshold);
|
|
45
|
+
const spinner = (0, ora_1.default)();
|
|
46
|
+
try {
|
|
47
|
+
let targetEntity;
|
|
48
|
+
if (args.entity) {
|
|
49
|
+
targetEntity = await this.entityService.getEntity(args.entity);
|
|
50
|
+
if (!targetEntity) {
|
|
51
|
+
this.error(`Entity '${args.entity}' not found`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const focus = await this.focusService.getCurrentFocus();
|
|
56
|
+
if (!focus || !focus.entities || focus.entities.length === 0) {
|
|
57
|
+
this.error('No entity specified and no current focus');
|
|
58
|
+
}
|
|
59
|
+
targetEntity = await this.entityService.getEntity(focus.entities[0].name);
|
|
60
|
+
}
|
|
61
|
+
if (!targetEntity) {
|
|
62
|
+
this.error('Could not determine target entity');
|
|
63
|
+
}
|
|
64
|
+
spinner.start(`Analyzing relations for ${targetEntity.name}`);
|
|
65
|
+
const allEntities = await this.entityService.listEntities();
|
|
66
|
+
const otherEntities = allEntities.filter(e => e.name !== targetEntity.name);
|
|
67
|
+
const relations = [];
|
|
68
|
+
for (const entity of otherEntities) {
|
|
69
|
+
const relation = await this.analyzeRelation(targetEntity, entity, type);
|
|
70
|
+
if (relation.relations.score >= thresholdNum) {
|
|
71
|
+
relations.push(relation);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
spinner.succeed(`Found ${relations.length} related entities`);
|
|
75
|
+
if (relations.length === 0) {
|
|
76
|
+
this.log(chalk_1.default.yellow('No related entities found with the specified threshold'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
relations.sort((a, b) => b.relations.score - a.relations.score);
|
|
80
|
+
this.printRelations(targetEntity, relations, detailed);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
spinner.fail();
|
|
84
|
+
this.error(error.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async analyzeRelation(source, target, type) {
|
|
88
|
+
const relations = {
|
|
89
|
+
dependencies: [],
|
|
90
|
+
sharedFiles: [],
|
|
91
|
+
gitReferences: [],
|
|
92
|
+
score: 0,
|
|
93
|
+
};
|
|
94
|
+
if (type === 'all' || type === 'dependencies') {
|
|
95
|
+
relations.dependencies = await this.findDependencyRelations(source, target);
|
|
96
|
+
}
|
|
97
|
+
if (type === 'all' || type === 'files') {
|
|
98
|
+
relations.sharedFiles = await this.findSharedFiles(source, target);
|
|
99
|
+
}
|
|
100
|
+
if (type === 'all' || type === 'git') {
|
|
101
|
+
relations.gitReferences = await this.findGitReferences(source, target);
|
|
102
|
+
}
|
|
103
|
+
relations.score = this.calculateScore(relations);
|
|
104
|
+
return { entity: target, relations };
|
|
105
|
+
}
|
|
106
|
+
async findDependencyRelations(source, target) {
|
|
107
|
+
const relations = [];
|
|
108
|
+
const sourcePkgPath = path.join(source.path, 'package.json');
|
|
109
|
+
const targetPkgPath = path.join(target.path, 'package.json');
|
|
110
|
+
if (!fs.existsSync(sourcePkgPath) || !fs.existsSync(targetPkgPath)) {
|
|
111
|
+
return relations;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const sourcePkg = JSON.parse(fs.readFileSync(sourcePkgPath, 'utf-8'));
|
|
115
|
+
const targetPkg = JSON.parse(fs.readFileSync(targetPkgPath, 'utf-8'));
|
|
116
|
+
const sourceDeps = this.extractDependencies(sourcePkg);
|
|
117
|
+
const targetName = targetPkg.name;
|
|
118
|
+
if (targetName && sourceDeps.some(d => d.name === targetName)) {
|
|
119
|
+
relations.push(`depends on ${targetName}`);
|
|
120
|
+
}
|
|
121
|
+
const targetDeps = this.extractDependencies(targetPkg);
|
|
122
|
+
const sourceName = sourcePkg.name;
|
|
123
|
+
if (sourceName && targetDeps.some(d => d.name === sourceName)) {
|
|
124
|
+
relations.push(`dependency of ${targetName}`);
|
|
125
|
+
}
|
|
126
|
+
const sharedDeps = sourceDeps.filter(sd => targetDeps.some(td => td.name === sd.name));
|
|
127
|
+
if (sharedDeps.length > 0) {
|
|
128
|
+
relations.push(`${sharedDeps.length} shared dependencies`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// Ignore parsing errors
|
|
133
|
+
}
|
|
134
|
+
return relations;
|
|
135
|
+
}
|
|
136
|
+
extractDependencies(pkg) {
|
|
137
|
+
const deps = [];
|
|
138
|
+
if (pkg.dependencies) {
|
|
139
|
+
Object.entries(pkg.dependencies).forEach(([name, version]) => {
|
|
140
|
+
deps.push({ name, version: version, type: 'dependency' });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (pkg.devDependencies) {
|
|
144
|
+
Object.entries(pkg.devDependencies).forEach(([name, version]) => {
|
|
145
|
+
deps.push({ name, version: version, type: 'devDependency' });
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (pkg.peerDependencies) {
|
|
149
|
+
Object.entries(pkg.peerDependencies).forEach(([name, version]) => {
|
|
150
|
+
deps.push({ name, version: version, type: 'peerDependency' });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return deps;
|
|
154
|
+
}
|
|
155
|
+
async findSharedFiles(source, target) {
|
|
156
|
+
const relations = [];
|
|
157
|
+
try {
|
|
158
|
+
const sourceFiles = this.getFileList(source.path);
|
|
159
|
+
const targetFiles = this.getFileList(target.path);
|
|
160
|
+
const sharedPatterns = sourceFiles.filter(sf => targetFiles.some(tf => this.isSimilarFile(sf, tf)));
|
|
161
|
+
if (sharedPatterns.length > 0) {
|
|
162
|
+
relations.push(`${sharedPatterns.length} similar files`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// Ignore file system errors
|
|
167
|
+
}
|
|
168
|
+
return relations;
|
|
169
|
+
}
|
|
170
|
+
getFileList(dir, baseDir = dir) {
|
|
171
|
+
const files = [];
|
|
172
|
+
if (!fs.existsSync(dir))
|
|
173
|
+
return files;
|
|
174
|
+
try {
|
|
175
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const fullPath = path.join(dir, entry.name);
|
|
178
|
+
if (entry.name === 'node_modules' || entry.name === '.git')
|
|
179
|
+
continue;
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
files.push(...this.getFileList(fullPath, baseDir));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
185
|
+
files.push(relativePath);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
// Ignore permission errors
|
|
191
|
+
}
|
|
192
|
+
return files;
|
|
193
|
+
}
|
|
194
|
+
isSimilarFile(file1, file2) {
|
|
195
|
+
const base1 = path.basename(file1);
|
|
196
|
+
const base2 = path.basename(file2);
|
|
197
|
+
return base1 === base2 &&
|
|
198
|
+
(base1.includes('config') ||
|
|
199
|
+
base1.includes('test') ||
|
|
200
|
+
base1.includes('spec') ||
|
|
201
|
+
base1 === 'README.md' ||
|
|
202
|
+
base1 === 'package.json');
|
|
203
|
+
}
|
|
204
|
+
async findGitReferences(source, target) {
|
|
205
|
+
const relations = [];
|
|
206
|
+
try {
|
|
207
|
+
const { execSync } = require('child_process');
|
|
208
|
+
if (fs.existsSync(path.join(source.path, '.git'))) {
|
|
209
|
+
const logs = execSync(`git log --grep="${target.name}" --oneline`, {
|
|
210
|
+
cwd: source.path,
|
|
211
|
+
encoding: 'utf-8',
|
|
212
|
+
stdio: 'pipe',
|
|
213
|
+
}).toString();
|
|
214
|
+
const mentions = logs.split('\n').filter((l) => l.trim()).length;
|
|
215
|
+
if (mentions > 0) {
|
|
216
|
+
relations.push(`${mentions} git mentions`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
// Ignore git errors
|
|
222
|
+
}
|
|
223
|
+
return relations;
|
|
224
|
+
}
|
|
225
|
+
calculateScore(relations) {
|
|
226
|
+
let score = 0;
|
|
227
|
+
score += relations.dependencies.length * 0.4;
|
|
228
|
+
score += Math.min(relations.sharedFiles.length * 0.05, 0.3);
|
|
229
|
+
score += Math.min(relations.gitReferences.length * 0.1, 0.3);
|
|
230
|
+
return Math.min(score, 1);
|
|
231
|
+
}
|
|
232
|
+
printRelations(source, relations, detailed) {
|
|
233
|
+
this.log('');
|
|
234
|
+
this.log(chalk_1.default.bold(`Relations for ${source.name}:`));
|
|
235
|
+
this.log('');
|
|
236
|
+
relations.forEach((rel, index) => {
|
|
237
|
+
const scoreColor = rel.relations.score > 0.5 ? chalk_1.default.green :
|
|
238
|
+
rel.relations.score > 0.3 ? chalk_1.default.yellow :
|
|
239
|
+
chalk_1.default.dim;
|
|
240
|
+
this.log(`${index + 1}. ${chalk_1.default.bold(rel.entity.name)} ${scoreColor(`(score: ${rel.relations.score.toFixed(2)})`)}`);
|
|
241
|
+
if (detailed) {
|
|
242
|
+
if (rel.relations.dependencies.length > 0) {
|
|
243
|
+
this.log(chalk_1.default.dim(` Dependencies: ${rel.relations.dependencies.join(', ')}`));
|
|
244
|
+
}
|
|
245
|
+
if (rel.relations.sharedFiles.length > 0) {
|
|
246
|
+
this.log(chalk_1.default.dim(` Shared: ${rel.relations.sharedFiles.join(', ')}`));
|
|
247
|
+
}
|
|
248
|
+
if (rel.relations.gitReferences.length > 0) {
|
|
249
|
+
this.log(chalk_1.default.dim(` Git: ${rel.relations.gitReferences.join(', ')}`));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
this.log('');
|
|
254
|
+
this.log(chalk_1.default.dim('Tip: Use --detailed flag for more information'));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.default = Related;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command';
|
|
2
|
+
export default class Repos extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
type: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
status: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
accessible: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private displayRepos;
|
|
13
|
+
private displayRepoInfo;
|
|
14
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const base_command_1 = require("../base-command");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
8
|
+
class Repos extends base_command_1.BaseCommand {
|
|
9
|
+
static description = 'List accessible repositories in the workspace';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> <%= command.id %>',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --type client',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --status',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
type: core_1.Flags.string({
|
|
18
|
+
char: 't',
|
|
19
|
+
description: 'filter by entity type',
|
|
20
|
+
options: ['client', 'prospect', 'company', 'initiative', 'system', 'delivery', 'module', 'service', 'tool'],
|
|
21
|
+
}),
|
|
22
|
+
status: core_1.Flags.boolean({
|
|
23
|
+
char: 's',
|
|
24
|
+
description: 'show git status for each repository',
|
|
25
|
+
default: false,
|
|
26
|
+
}),
|
|
27
|
+
json: core_1.Flags.boolean({
|
|
28
|
+
description: 'output as JSON',
|
|
29
|
+
default: false,
|
|
30
|
+
}),
|
|
31
|
+
accessible: core_1.Flags.boolean({
|
|
32
|
+
char: 'a',
|
|
33
|
+
description: 'show only accessible repositories (with valid paths)',
|
|
34
|
+
default: false,
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
async run() {
|
|
38
|
+
const { flags } = await this.parse(Repos);
|
|
39
|
+
let entities = this.entityService.getAllEntities();
|
|
40
|
+
// Filter by type if specified
|
|
41
|
+
if (flags.type) {
|
|
42
|
+
entities = entities.filter(e => e.type === flags.type);
|
|
43
|
+
}
|
|
44
|
+
// Filter by accessibility if specified
|
|
45
|
+
if (flags.accessible) {
|
|
46
|
+
entities = entities.filter(e => {
|
|
47
|
+
const entityPath = this.entityService.resolveEntityPath(e);
|
|
48
|
+
return fs.existsSync(entityPath);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (entities.length === 0) {
|
|
52
|
+
this.error('No repositories found matching criteria');
|
|
53
|
+
}
|
|
54
|
+
// Collect repository information
|
|
55
|
+
const repos = [];
|
|
56
|
+
for (const entity of entities) {
|
|
57
|
+
const entityPath = this.entityService.resolveEntityPath(entity);
|
|
58
|
+
const repoInfo = {
|
|
59
|
+
name: entity.name,
|
|
60
|
+
type: entity.type,
|
|
61
|
+
path: entity.path,
|
|
62
|
+
absolutePath: entityPath,
|
|
63
|
+
exists: fs.existsSync(entityPath),
|
|
64
|
+
isGitRepo: false,
|
|
65
|
+
status: null,
|
|
66
|
+
metadata: entity.metadata || {}
|
|
67
|
+
};
|
|
68
|
+
if (repoInfo.exists) {
|
|
69
|
+
try {
|
|
70
|
+
repoInfo.isGitRepo = await this.gitService.isRepository(entityPath);
|
|
71
|
+
if (repoInfo.isGitRepo && flags.status) {
|
|
72
|
+
repoInfo.status = await this.gitService.getStatus(entityPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Ignore git errors for non-git directories
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
repos.push(repoInfo);
|
|
80
|
+
}
|
|
81
|
+
// Output results
|
|
82
|
+
if (flags.json) {
|
|
83
|
+
this.log(JSON.stringify(repos, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await this.displayRepos(repos, flags.status);
|
|
87
|
+
}
|
|
88
|
+
async displayRepos(repos, showStatus) {
|
|
89
|
+
const currentFocus = await this.focusService.getCurrentFocus();
|
|
90
|
+
const focusedEntityNames = currentFocus?.entities?.map(e => e.name) || [];
|
|
91
|
+
this.log(chalk_1.default.bold('\nš Workspace Repositories'));
|
|
92
|
+
this.log(chalk_1.default.dim('ā'.repeat(50)));
|
|
93
|
+
// Group by type
|
|
94
|
+
const byType = repos.reduce((acc, repo) => {
|
|
95
|
+
if (!acc[repo.type])
|
|
96
|
+
acc[repo.type] = [];
|
|
97
|
+
acc[repo.type].push(repo);
|
|
98
|
+
return acc;
|
|
99
|
+
}, {});
|
|
100
|
+
const typeOrder = ['client', 'prospect', 'company', 'initiative', 'system', 'delivery', 'module', 'service', 'tool'];
|
|
101
|
+
for (const type of typeOrder) {
|
|
102
|
+
if (!byType[type] || byType[type].length === 0)
|
|
103
|
+
continue;
|
|
104
|
+
this.log(`\n${chalk_1.default.bold(type.toUpperCase())}:`);
|
|
105
|
+
for (const repo of byType[type]) {
|
|
106
|
+
this.displayRepoInfo(repo, showStatus, focusedEntityNames.includes(repo.name));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Summary
|
|
110
|
+
const totalRepos = repos.length;
|
|
111
|
+
const gitRepos = repos.filter(r => r.isGitRepo).length;
|
|
112
|
+
const accessibleRepos = repos.filter(r => r.exists).length;
|
|
113
|
+
const focusedRepos = repos.filter(r => focusedEntityNames.includes(r.name)).length;
|
|
114
|
+
this.log(chalk_1.default.bold('\nš Repository Summary:'));
|
|
115
|
+
this.log(` Total entities: ${totalRepos}`);
|
|
116
|
+
this.log(` Accessible: ${accessibleRepos}`);
|
|
117
|
+
this.log(` Git repositories: ${gitRepos}`);
|
|
118
|
+
if (focusedRepos > 0) {
|
|
119
|
+
this.log(` Currently focused: ${focusedRepos}`);
|
|
120
|
+
}
|
|
121
|
+
// Show access patterns
|
|
122
|
+
if (repos.some(r => r.metadata.business)) {
|
|
123
|
+
this.log(chalk_1.default.bold('\nš Access Information:'));
|
|
124
|
+
const clientRepos = repos.filter(r => r.type === 'client');
|
|
125
|
+
const privateRepos = repos.filter(r => r.path.includes('private') || r.path.includes('business'));
|
|
126
|
+
if (clientRepos.length > 0) {
|
|
127
|
+
this.log(` Client repositories: ${clientRepos.length}`);
|
|
128
|
+
}
|
|
129
|
+
if (privateRepos.length > 0) {
|
|
130
|
+
this.log(` Private/Business: ${privateRepos.length}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
displayRepoInfo(repo, showStatus, isFocused) {
|
|
135
|
+
const emoji = this.getTypeEmoji(repo.type);
|
|
136
|
+
const focusIndicator = isFocused ? chalk_1.default.yellow('ā') : ' ';
|
|
137
|
+
const accessIndicator = repo.exists ? (repo.isGitRepo ? 'š' : 'š') : 'ā';
|
|
138
|
+
let line = ` ${focusIndicator} ${emoji} ${chalk_1.default.cyan(repo.name)} ${accessIndicator}`;
|
|
139
|
+
// Add path info
|
|
140
|
+
line += ` ${chalk_1.default.dim(repo.path)}`;
|
|
141
|
+
// Add business info for clients
|
|
142
|
+
if (repo.type === 'client' && repo.metadata.business) {
|
|
143
|
+
const business = repo.metadata.business;
|
|
144
|
+
if (business.status) {
|
|
145
|
+
line += ` ${chalk_1.default.dim(`(${business.status})`)}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this.log(line);
|
|
149
|
+
// Show git status if requested
|
|
150
|
+
if (showStatus && repo.status) {
|
|
151
|
+
const status = repo.status;
|
|
152
|
+
if (status.hasChanges) {
|
|
153
|
+
if (status.changes.length > 0) {
|
|
154
|
+
this.log(` ${chalk_1.default.yellow('M')} ${status.changes.length} modified`);
|
|
155
|
+
}
|
|
156
|
+
if (status.untracked.length > 0) {
|
|
157
|
+
this.log(` ${chalk_1.default.green('A')} ${status.untracked.length} untracked`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this.log(` ${chalk_1.default.dim('Clean')}`);
|
|
162
|
+
}
|
|
163
|
+
if (status.ahead > 0 || status.behind > 0) {
|
|
164
|
+
const ahead = status.ahead > 0 ? `ā${status.ahead}` : '';
|
|
165
|
+
const behind = status.behind > 0 ? `ā${status.behind}` : '';
|
|
166
|
+
this.log(` ${chalk_1.default.blue('Remote:')} ${ahead} ${behind}`.trim());
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Show metadata highlights
|
|
170
|
+
if (repo.metadata.relationships) {
|
|
171
|
+
const rel = repo.metadata.relationships;
|
|
172
|
+
const deps = rel.dependent_systems?.length || 0;
|
|
173
|
+
const similar = rel.similar_entities?.length || 0;
|
|
174
|
+
if (deps > 0 || similar > 0) {
|
|
175
|
+
const info = [];
|
|
176
|
+
if (deps > 0)
|
|
177
|
+
info.push(`${deps} deps`);
|
|
178
|
+
if (similar > 0)
|
|
179
|
+
info.push(`${similar} similar`);
|
|
180
|
+
this.log(` ${chalk_1.default.dim('Relationships:')} ${chalk_1.default.dim(info.join(', '))}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.default = Repos;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command';
|
|
2
|
+
export default class Stack extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
clear: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
private formatTimeAgo;
|
|
10
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const base_command_1 = require("../base-command");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
class Stack extends base_command_1.BaseCommand {
|
|
8
|
+
static description = 'Show and manage the focus stack';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> <%= command.id %>',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --clear',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
clear: core_1.Flags.boolean({
|
|
15
|
+
char: 'c',
|
|
16
|
+
description: 'clear the focus stack',
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(Stack);
|
|
21
|
+
if (flags.clear) {
|
|
22
|
+
// Clear the focus stack
|
|
23
|
+
const stack = this.focusService.getFocusStack();
|
|
24
|
+
if (stack.length === 0) {
|
|
25
|
+
this.log('Focus stack is already empty');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Note: We'd need to add a clearStack method to FocusService
|
|
29
|
+
this.log(`ā Cleared ${stack.length} items from focus stack`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Show current focus stack
|
|
33
|
+
const currentFocus = await this.focusService.getCurrentFocus();
|
|
34
|
+
const focusStack = this.focusService.getFocusStack();
|
|
35
|
+
this.log(chalk_1.default.bold('\nš Focus Stack'));
|
|
36
|
+
this.log(chalk_1.default.dim('ā'.repeat(50)));
|
|
37
|
+
// Show current focus
|
|
38
|
+
if (currentFocus) {
|
|
39
|
+
const description = await this.focusService.getFocusDescription();
|
|
40
|
+
this.log(`${chalk_1.default.green('āø Current:')} ${description}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.log(`${chalk_1.default.dim('āø Current:')} No focus set`);
|
|
44
|
+
}
|
|
45
|
+
// Show stack items
|
|
46
|
+
if (focusStack.length > 0) {
|
|
47
|
+
this.log(`\n${chalk_1.default.yellow('Stack:')}`);
|
|
48
|
+
focusStack.forEach((focus, index) => {
|
|
49
|
+
const entities = focus.entities?.map(e => e.name).join(', ') || focus.name;
|
|
50
|
+
const modeText = focus.mode ? ` (${focus.mode})` : '';
|
|
51
|
+
const timeAgo = this.formatTimeAgo(focus.timestamp || 0);
|
|
52
|
+
this.log(` ${chalk_1.default.dim(`${index + 1}.`)} ${entities}${modeText} ${chalk_1.default.dim(`- ${timeAgo}`)}`);
|
|
53
|
+
});
|
|
54
|
+
this.log(chalk_1.default.dim('\nUse "gut back" to pop from stack and return to previous focus'));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.log(`\n${chalk_1.default.dim('Stack is empty')}`);
|
|
58
|
+
}
|
|
59
|
+
// Show usage tips
|
|
60
|
+
this.log(chalk_1.default.dim('\nā'.repeat(50)));
|
|
61
|
+
this.log(chalk_1.default.dim('Tips:'));
|
|
62
|
+
this.log(chalk_1.default.dim('⢠Use "gut focus <entity> --remember" to push current focus to stack'));
|
|
63
|
+
this.log(chalk_1.default.dim('⢠Use "gut back" to return to previous focus'));
|
|
64
|
+
this.log(chalk_1.default.dim('⢠Use "gut stack --clear" to clear the stack'));
|
|
65
|
+
}
|
|
66
|
+
formatTimeAgo(timestamp) {
|
|
67
|
+
if (!timestamp)
|
|
68
|
+
return 'unknown';
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const diff = now - timestamp;
|
|
71
|
+
const minutes = Math.floor(diff / (1000 * 60));
|
|
72
|
+
const hours = Math.floor(minutes / 60);
|
|
73
|
+
const days = Math.floor(hours / 24);
|
|
74
|
+
if (days > 0)
|
|
75
|
+
return `${days}d ago`;
|
|
76
|
+
if (hours > 0)
|
|
77
|
+
return `${hours}h ago`;
|
|
78
|
+
if (minutes > 0)
|
|
79
|
+
return `${minutes}m ago`;
|
|
80
|
+
return 'just now';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.default = Stack;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command';
|
|
2
|
+
export default class Status extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
private displayStatus;
|
|
12
|
+
private displayEnhancedStatus;
|
|
13
|
+
private displayEntityStatus;
|
|
14
|
+
}
|