@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,124 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { BaseCommand } from '../base-command.js';
|
|
5
|
+
export default class Checkout extends BaseCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
branch: Args.string({
|
|
8
|
+
description: 'Branch name to checkout or create',
|
|
9
|
+
required: true,
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
static description = 'Checkout or create branches in focused repositories';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> main',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> -b feature/new-feature',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> -b PRD-123 --from main',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
'create-branch': Flags.boolean({
|
|
20
|
+
char: 'b',
|
|
21
|
+
default: false,
|
|
22
|
+
description: 'Create a new branch',
|
|
23
|
+
}),
|
|
24
|
+
force: Flags.boolean({
|
|
25
|
+
char: 'f',
|
|
26
|
+
default: false,
|
|
27
|
+
description: 'Force checkout (discard local changes)',
|
|
28
|
+
}),
|
|
29
|
+
from: Flags.string({
|
|
30
|
+
description: 'Base branch to create from (use with -b)',
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { args, flags } = await this.parse(Checkout);
|
|
36
|
+
const { branch } = args;
|
|
37
|
+
const focusedEntities = await this.focusService.getFocusedEntities();
|
|
38
|
+
if (focusedEntities.length === 0) {
|
|
39
|
+
this.error('No entities are focused. Use "gut focus <entity>" first.');
|
|
40
|
+
}
|
|
41
|
+
const action = flags['create-branch'] ? 'Creating branch' : 'Checking out';
|
|
42
|
+
this.log(chalk.bold(`\n🔀 ${action} "${chalk.cyan(branch)}" in focused entities\n`));
|
|
43
|
+
const results = {
|
|
44
|
+
alreadyOnBranch: [],
|
|
45
|
+
created: [],
|
|
46
|
+
failed: [],
|
|
47
|
+
switched: [],
|
|
48
|
+
};
|
|
49
|
+
for (const entity of focusedEntities) {
|
|
50
|
+
const spinner = ora(`${action} in ${chalk.cyan(entity.name)}`).start();
|
|
51
|
+
try {
|
|
52
|
+
// Get current branch
|
|
53
|
+
const currentBranch = await this.gitService.getCurrentBranch(entity.path);
|
|
54
|
+
// Check if already on target branch
|
|
55
|
+
if (currentBranch === branch && !flags['create-branch']) {
|
|
56
|
+
spinner.info(chalk.blue(`${entity.name}: Already on branch "${branch}"`));
|
|
57
|
+
results.alreadyOnBranch.push(entity.name);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (flags['create-branch']) {
|
|
61
|
+
// If --from is specified, checkout that branch first
|
|
62
|
+
if (flags.from) {
|
|
63
|
+
await this.gitService.checkout(entity.path, flags.from);
|
|
64
|
+
}
|
|
65
|
+
// Create and checkout new branch
|
|
66
|
+
await this.gitService.createBranch(entity.path, branch, true);
|
|
67
|
+
spinner.succeed(chalk.green(`✓ ${entity.name}: Created and checked out "${branch}"`));
|
|
68
|
+
results.created.push(entity.name);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Switch to existing branch
|
|
72
|
+
if (flags.force) {
|
|
73
|
+
await this.gitService.exec(['checkout', '-f', branch], { cwd: entity.path });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await this.gitService.checkout(entity.path, branch);
|
|
77
|
+
}
|
|
78
|
+
spinner.succeed(chalk.green(`✓ ${entity.name}: Switched to "${branch}"`));
|
|
79
|
+
results.switched.push(entity.name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
84
|
+
if (errorMessage.includes('already exists')) {
|
|
85
|
+
spinner.fail(chalk.red(`✗ ${entity.name}: Branch "${branch}" already exists`));
|
|
86
|
+
results.failed.push({ error: `Branch already exists`, repo: entity.name });
|
|
87
|
+
}
|
|
88
|
+
else if (errorMessage.includes('did not match any')) {
|
|
89
|
+
spinner.fail(chalk.red(`✗ ${entity.name}: Branch "${branch}" not found`));
|
|
90
|
+
results.failed.push({ error: `Branch not found`, repo: entity.name });
|
|
91
|
+
}
|
|
92
|
+
else if (errorMessage.includes('local changes')) {
|
|
93
|
+
spinner.fail(chalk.red(`✗ ${entity.name}: Local changes would be overwritten`));
|
|
94
|
+
results.failed.push({ error: 'Local changes would be overwritten. Commit or stash first, or use --force', repo: entity.name });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
spinner.fail(chalk.red(`✗ ${entity.name}: ${errorMessage}`));
|
|
98
|
+
results.failed.push({ error: errorMessage, repo: entity.name });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Summary
|
|
103
|
+
this.log(chalk.bold('\n📊 Checkout Summary'));
|
|
104
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
105
|
+
if (results.created.length > 0) {
|
|
106
|
+
this.log(chalk.green(`✓ Created: ${results.created.length} entities`));
|
|
107
|
+
}
|
|
108
|
+
if (results.switched.length > 0) {
|
|
109
|
+
this.log(chalk.green(`✓ Switched: ${results.switched.length} entities`));
|
|
110
|
+
}
|
|
111
|
+
if (results.alreadyOnBranch.length > 0) {
|
|
112
|
+
this.log(chalk.blue(`○ Already on branch: ${results.alreadyOnBranch.length} entities`));
|
|
113
|
+
}
|
|
114
|
+
if (results.failed.length > 0) {
|
|
115
|
+
this.log(chalk.red(`✗ Failed: ${results.failed.length} entities`));
|
|
116
|
+
for (const failure of results.failed) {
|
|
117
|
+
this.log(chalk.red(` - ${failure.repo}: ${failure.error}`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (results.created.length > 0) {
|
|
121
|
+
this.log(chalk.dim('\nTip: Use "gut push -u" to push new branches to remote'));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command.js';
|
|
2
|
+
export default class Commit extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
amend: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { BaseCommand } from '../base-command.js';
|
|
6
|
+
export default class Commit extends BaseCommand {
|
|
7
|
+
static description = 'Commit changes in focused repositories';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> -m "Fix bug"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --message "Add feature"',
|
|
11
|
+
'<%= config.bin %> <%= command.id %>', // Interactive mode
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
all: Flags.boolean({
|
|
15
|
+
char: 'a',
|
|
16
|
+
default: false,
|
|
17
|
+
description: 'Stage all changes before committing',
|
|
18
|
+
}),
|
|
19
|
+
amend: Flags.boolean({
|
|
20
|
+
default: false,
|
|
21
|
+
description: 'Amend the previous commit',
|
|
22
|
+
}),
|
|
23
|
+
message: Flags.string({
|
|
24
|
+
char: 'm',
|
|
25
|
+
description: 'Commit message',
|
|
26
|
+
required: false,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { flags } = await this.parse(Commit);
|
|
31
|
+
const focusedEntities = await this.focusService.getFocusedEntities();
|
|
32
|
+
if (focusedEntities.length === 0) {
|
|
33
|
+
this.error('No entities are focused. Use "gut focus <entity>" first.');
|
|
34
|
+
}
|
|
35
|
+
let { message } = flags;
|
|
36
|
+
// Interactive prompt for commit message if not provided
|
|
37
|
+
if (!message && !flags.amend) {
|
|
38
|
+
const response = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
message: 'Enter commit message:',
|
|
41
|
+
name: 'message',
|
|
42
|
+
type: 'input',
|
|
43
|
+
validate(input) {
|
|
44
|
+
if (!input || input.trim().length === 0) {
|
|
45
|
+
return 'Commit message cannot be empty';
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
message = response.message;
|
|
52
|
+
}
|
|
53
|
+
this.log(chalk.bold('\n💾 Committing changes in focused entities\n'));
|
|
54
|
+
const results = {
|
|
55
|
+
failed: [],
|
|
56
|
+
noChanges: [],
|
|
57
|
+
success: [],
|
|
58
|
+
};
|
|
59
|
+
for (const entity of focusedEntities) {
|
|
60
|
+
const spinner = ora(`Committing in ${chalk.cyan(entity.name)}`).start();
|
|
61
|
+
try {
|
|
62
|
+
// Check if there are changes to commit
|
|
63
|
+
const status = await this.gitService.getStatus(entity.path);
|
|
64
|
+
const hasChanges = status.changes.length > 0 || status.untracked.length > 0;
|
|
65
|
+
if (!hasChanges && !flags.amend) {
|
|
66
|
+
spinner.info(chalk.yellow(`${entity.name}: No changes to commit`));
|
|
67
|
+
results.noChanges.push(entity.name);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Build commit options
|
|
71
|
+
const options = {
|
|
72
|
+
all: flags.all,
|
|
73
|
+
amend: flags.amend,
|
|
74
|
+
};
|
|
75
|
+
// Amend without changing message, or commit with new message
|
|
76
|
+
await (!message && flags.amend
|
|
77
|
+
? this.gitService.exec(['commit', '--amend', '--no-edit'], { cwd: entity.path })
|
|
78
|
+
: this.gitService.commit(entity.path, message || '', options));
|
|
79
|
+
spinner.succeed(chalk.green(`✓ ${entity.name}: Committed successfully`));
|
|
80
|
+
results.success.push(entity.name);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
84
|
+
spinner.fail(chalk.red(`✗ ${entity.name}: ${errorMessage}`));
|
|
85
|
+
results.failed.push({ error: errorMessage, repo: entity.name });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Summary
|
|
89
|
+
this.log(chalk.bold('\n📊 Commit Summary'));
|
|
90
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
91
|
+
if (results.success.length > 0) {
|
|
92
|
+
this.log(chalk.green(`✓ Success: ${results.success.length} entities`));
|
|
93
|
+
}
|
|
94
|
+
if (results.noChanges.length > 0) {
|
|
95
|
+
this.log(chalk.yellow(`○ No changes: ${results.noChanges.length} entities`));
|
|
96
|
+
}
|
|
97
|
+
if (results.failed.length > 0) {
|
|
98
|
+
this.log(chalk.red(`✗ Failed: ${results.failed.length} entities`));
|
|
99
|
+
for (const failure of results.failed) {
|
|
100
|
+
this.log(chalk.red(` - ${failure.repo}: ${failure.error}`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (results.success.length > 0) {
|
|
104
|
+
this.log(chalk.dim('\nTip: Use "gut push" to push commits to remote'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { BaseCommand } from '../base-command.js';
|
|
4
|
+
export default class Context extends BaseCommand {
|
|
5
|
+
static description = 'Show current focus context with entity details';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %>',
|
|
8
|
+
];
|
|
9
|
+
async run() {
|
|
10
|
+
await this.parse(Context);
|
|
11
|
+
const focusedEntities = await this.focusService.getFocusedEntities();
|
|
12
|
+
if (focusedEntities.length === 0) {
|
|
13
|
+
this.log(chalk.yellow('No entities are currently focused'));
|
|
14
|
+
this.log(chalk.dim('Use "gut focus <entity>" to set focus'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const workspaceRoot = this.configService.getWorkspaceRoot();
|
|
18
|
+
this.log(chalk.bold('\n📍 Current Focus Context'));
|
|
19
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
20
|
+
for (const entity of focusedEntities) {
|
|
21
|
+
this.log(`\n${chalk.green('▸')} ${chalk.bold(entity.name)}`);
|
|
22
|
+
this.log(` ${chalk.dim('Type:')} ${entity.type}`);
|
|
23
|
+
this.log(` ${chalk.dim('Path:')} ${path.relative(workspaceRoot, entity.path)}`);
|
|
24
|
+
}
|
|
25
|
+
this.log(chalk.dim('\n─'.repeat(50)));
|
|
26
|
+
this.log(`${chalk.dim('Total focused entities:')} ${focusedEntities.length}`);
|
|
27
|
+
const history = await this.configService.getHistory();
|
|
28
|
+
if (history.length > 1) {
|
|
29
|
+
this.log(`${chalk.dim('Previous focus:')} ${history[1].entities.join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { BaseCommand } from '../base-command.js';
|
|
3
|
+
export default class Contexts extends BaseCommand {
|
|
4
|
+
static description = 'List all available contexts and focus history';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %>',
|
|
7
|
+
];
|
|
8
|
+
async run() {
|
|
9
|
+
await this.parse(Contexts);
|
|
10
|
+
// Show current focus
|
|
11
|
+
const currentFocus = await this.focusService.getCurrentFocus();
|
|
12
|
+
if (currentFocus) {
|
|
13
|
+
this.log(chalk.bold('\n🎯 Current Focus'));
|
|
14
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
15
|
+
const description = await this.focusService.getFocusDescription();
|
|
16
|
+
this.log(`${chalk.green('▸')} ${description}`);
|
|
17
|
+
if (currentFocus.mode) {
|
|
18
|
+
this.log(` ${chalk.dim('Mode:')} ${currentFocus.mode}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Show focus stack
|
|
22
|
+
const focusStack = this.focusService.getFocusStack();
|
|
23
|
+
if (focusStack.length > 0) {
|
|
24
|
+
this.log(chalk.bold('\n📚 Focus Stack'));
|
|
25
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
26
|
+
for (const [index, focus] of focusStack.entries()) {
|
|
27
|
+
const entities = focus.entities?.map(e => e.name).join(', ') || focus.name;
|
|
28
|
+
const modeText = focus.mode ? ` (${focus.mode})` : '';
|
|
29
|
+
this.log(`${chalk.yellow(`${index + 1}.`)} ${entities}${modeText}`);
|
|
30
|
+
}
|
|
31
|
+
this.log(chalk.dim('\nUse "gut back" to return to previous focus'));
|
|
32
|
+
}
|
|
33
|
+
// Show recent history
|
|
34
|
+
const history = this.configService.getHistory();
|
|
35
|
+
if (history.length > 0) {
|
|
36
|
+
this.log(chalk.bold('\n📋 Recent Focus History'));
|
|
37
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
38
|
+
const recentHistory = history.slice(-5).reverse();
|
|
39
|
+
for (const [index, entry] of recentHistory.entries()) {
|
|
40
|
+
const timeAgo = this.formatTimeAgo(entry.timestamp);
|
|
41
|
+
const entities = entry.entities.join(', ');
|
|
42
|
+
this.log(`${chalk.dim(`${index + 1}.`)} ${entities} ${chalk.dim(`(${timeAgo})`)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Show available entities by type
|
|
46
|
+
const entities = this.entityService.getAllEntities();
|
|
47
|
+
if (entities.length > 0) {
|
|
48
|
+
this.log(chalk.bold('\n🏢 Available Entities'));
|
|
49
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
50
|
+
const displayedTypes = ['client', 'prospect', 'company', 'initiative', 'system'];
|
|
51
|
+
for (const type of displayedTypes) {
|
|
52
|
+
const entitiesOfType = entities.filter(e => e.type === type);
|
|
53
|
+
if (entitiesOfType.length > 0) {
|
|
54
|
+
this.log(`\n${chalk.bold(type.toUpperCase())}:`);
|
|
55
|
+
for (const entity of entitiesOfType) {
|
|
56
|
+
const emoji = this.getTypeEmoji(entity.type);
|
|
57
|
+
this.log(` ${emoji} ${entity.name}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Show other types
|
|
62
|
+
const otherEntities = entities.filter(e => !displayedTypes.includes(e.type));
|
|
63
|
+
if (otherEntities.length > 0) {
|
|
64
|
+
this.log(`\n${chalk.bold('OTHER')}:`);
|
|
65
|
+
for (const entity of otherEntities) {
|
|
66
|
+
const emoji = this.getTypeEmoji(entity.type);
|
|
67
|
+
this.log(` ${emoji} ${entity.name} (${entity.type})`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.log(chalk.dim('\n─'.repeat(50)));
|
|
72
|
+
this.log(chalk.dim('Use "gut focus <entity>" or "gut focus <type> <name>" to set focus'));
|
|
73
|
+
}
|
|
74
|
+
formatTimeAgo(timestamp) {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const diff = now - timestamp;
|
|
77
|
+
const minutes = Math.floor(diff / (1000 * 60));
|
|
78
|
+
const hours = Math.floor(minutes / 60);
|
|
79
|
+
const days = Math.floor(hours / 24);
|
|
80
|
+
if (days > 0)
|
|
81
|
+
return `${days}d ago`;
|
|
82
|
+
if (hours > 0)
|
|
83
|
+
return `${hours}h ago`;
|
|
84
|
+
if (minutes > 0)
|
|
85
|
+
return `${minutes}m ago`;
|
|
86
|
+
return 'just now';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command.js';
|
|
2
|
+
export default class Deps 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
|
+
run(): Promise<void>;
|
|
9
|
+
private analyzeDependencies;
|
|
10
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { BaseCommand } from '../base-command.js';
|
|
4
|
+
export default class Deps extends BaseCommand {
|
|
5
|
+
static args = {
|
|
6
|
+
entity: Args.string({
|
|
7
|
+
description: 'Entity name to analyze dependencies for',
|
|
8
|
+
name: 'entity',
|
|
9
|
+
required: false,
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
static description = 'Show dependencies of current focus or specified entity';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %>',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> mindtools',
|
|
16
|
+
];
|
|
17
|
+
async run() {
|
|
18
|
+
const { args } = await this.parse(Deps);
|
|
19
|
+
let targetEntities = [];
|
|
20
|
+
if (args.entity) {
|
|
21
|
+
// Analyze specific entity
|
|
22
|
+
const entity = this.entityService.findEntity(args.entity);
|
|
23
|
+
if (!entity) {
|
|
24
|
+
this.error(`Entity '${args.entity}' not found`);
|
|
25
|
+
}
|
|
26
|
+
targetEntities = [entity];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Analyze current focus
|
|
30
|
+
targetEntities = await this.focusService.getFocusedEntities();
|
|
31
|
+
if (targetEntities.length === 0) {
|
|
32
|
+
this.error('No entities in focus. Use "gut focus <entity>" first or specify an entity name.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
this.log(chalk.bold('\n🔗 Dependencies Analysis'));
|
|
36
|
+
this.log(chalk.dim('─'.repeat(50)));
|
|
37
|
+
for (const entity of targetEntities) {
|
|
38
|
+
this.log(`\n${chalk.green('▸')} ${chalk.bold(entity.name)} (${entity.type})`);
|
|
39
|
+
const dependencies = await this.analyzeDependencies(entity);
|
|
40
|
+
if (dependencies.direct.length > 0) {
|
|
41
|
+
this.log(`\n ${chalk.yellow('Direct Dependencies:')}`);
|
|
42
|
+
for (const dep of dependencies.direct) {
|
|
43
|
+
this.log(` ${this.getTypeEmoji(dep.type)} ${dep.name} (${dep.type})`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (dependencies.system.length > 0) {
|
|
47
|
+
this.log(`\n ${chalk.blue('System Dependencies:')}`);
|
|
48
|
+
for (const dep of dependencies.system) {
|
|
49
|
+
this.log(` ${this.getTypeEmoji(dep.type)} ${dep.name} (${dep.type})`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (dependencies.inferred.length > 0) {
|
|
53
|
+
this.log(`\n ${chalk.dim('Inferred Dependencies:')}`);
|
|
54
|
+
for (const dep of dependencies.inferred) {
|
|
55
|
+
this.log(` ${this.getTypeEmoji(dep.type)} ${dep.name} (${dep.type}) ${chalk.dim('- pattern based')}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (dependencies.direct.length === 0 && dependencies.system.length === 0 && dependencies.inferred.length === 0) {
|
|
59
|
+
this.log(` ${chalk.dim('No dependencies found')}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.log(chalk.dim('\n─'.repeat(50)));
|
|
63
|
+
this.log(chalk.dim('Dependencies are determined from entity metadata and usage patterns'));
|
|
64
|
+
}
|
|
65
|
+
async analyzeDependencies(entity) {
|
|
66
|
+
const allEntities = this.entityService.getAllEntities();
|
|
67
|
+
// Direct dependencies from metadata
|
|
68
|
+
const directDeps = [];
|
|
69
|
+
if (entity.metadata?.relationships?.dependent_systems) {
|
|
70
|
+
for (const depName of entity.metadata.relationships.dependent_systems) {
|
|
71
|
+
const depEntity = allEntities.find(e => e.name === depName || e.path.includes(depName));
|
|
72
|
+
if (depEntity) {
|
|
73
|
+
directDeps.push(depEntity);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// System dependencies (all entities depend on systems)
|
|
78
|
+
const systemDeps = allEntities.filter(e => e.type === 'system' && e.name !== entity.name);
|
|
79
|
+
// Inferred dependencies based on patterns
|
|
80
|
+
const inferredDeps = [];
|
|
81
|
+
// Clients typically depend on shared design systems
|
|
82
|
+
if (entity.type === 'client') {
|
|
83
|
+
const designSystem = allEntities.find(e => e.name.includes('design') || e.name.includes('shared') || e.type === 'module');
|
|
84
|
+
if (designSystem && !directDeps.includes(designSystem) && !systemDeps.includes(designSystem)) {
|
|
85
|
+
inferredDeps.push(designSystem);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Prospects might depend on similar client patterns
|
|
89
|
+
if (entity.type === 'prospect') {
|
|
90
|
+
const similarClients = allEntities.filter(e => e.type === 'client'
|
|
91
|
+
&& entity.metadata?.relationships?.similar_entities?.includes(e.name));
|
|
92
|
+
inferredDeps.push(...similarClients);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
direct: directDeps,
|
|
96
|
+
inferred: inferredDeps,
|
|
97
|
+
system: systemDeps.slice(0, 3), // Limit to avoid clutter
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class AddEntity extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
type: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
path: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
repo: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { BaseCommand } from '../../base-command.js';
|
|
5
|
+
/** Entity types that support automatic directory creation */
|
|
6
|
+
const DIRECTORY_CREATING_ENTITY_TYPES = ['client', 'company', 'initiative', 'prospect', 'system'];
|
|
7
|
+
export default class AddEntity extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
name: Args.string({
|
|
10
|
+
description: 'Entity name',
|
|
11
|
+
name: 'name',
|
|
12
|
+
required: true,
|
|
13
|
+
}),
|
|
14
|
+
type: Args.string({
|
|
15
|
+
description: 'Entity type',
|
|
16
|
+
name: 'type',
|
|
17
|
+
options: ['client', 'prospect', 'company', 'initiative', 'system', 'delivery', 'module', 'service', 'tool'],
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
static description = 'Add a new entity to the workspace';
|
|
22
|
+
static examples = [
|
|
23
|
+
'<%= config.bin %> <%= command.id %> delivery my-app --path ./apps/my-app',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> module my-lib --path ./libs/my-lib --remote git@github.com:org/my-lib.git',
|
|
25
|
+
];
|
|
26
|
+
static flags = {
|
|
27
|
+
description: Flags.string({
|
|
28
|
+
char: 'd',
|
|
29
|
+
description: 'entity description',
|
|
30
|
+
}),
|
|
31
|
+
path: Flags.string({
|
|
32
|
+
char: 'p',
|
|
33
|
+
description: 'path to entity',
|
|
34
|
+
required: true,
|
|
35
|
+
}),
|
|
36
|
+
remote: Flags.string({
|
|
37
|
+
char: 'r',
|
|
38
|
+
description: 'git remote URL',
|
|
39
|
+
}),
|
|
40
|
+
repo: Flags.string({
|
|
41
|
+
description: 'repository name (for GitLab/GitHub)',
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
async run() {
|
|
45
|
+
const { args, flags } = await this.parse(AddEntity);
|
|
46
|
+
// Validate entity name
|
|
47
|
+
if (!this.entityService.validateEntityName(args.name)) {
|
|
48
|
+
this.error('Entity name must be alphanumeric with hyphens or underscores');
|
|
49
|
+
}
|
|
50
|
+
// Check if entity already exists
|
|
51
|
+
if (this.entityService.findEntity(args.name)) {
|
|
52
|
+
this.error(`Entity '${args.name}' already exists`);
|
|
53
|
+
}
|
|
54
|
+
// Validate and resolve path
|
|
55
|
+
const entityPath = path.isAbsolute(flags.path)
|
|
56
|
+
? flags.path
|
|
57
|
+
: path.join(this.configService.getWorkspaceRoot(), flags.path);
|
|
58
|
+
// Create directory if it doesn't exist (especially for new entity types)
|
|
59
|
+
if (!fs.existsSync(entityPath)) {
|
|
60
|
+
const supportsDirectoryCreation = DIRECTORY_CREATING_ENTITY_TYPES.includes(args.type);
|
|
61
|
+
if (supportsDirectoryCreation) {
|
|
62
|
+
fs.mkdirSync(entityPath, { recursive: true });
|
|
63
|
+
this.log(`✓ Created directory: ${flags.path}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.error(`Path '${flags.path}' does not exist`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!fs.statSync(entityPath).isDirectory()) {
|
|
70
|
+
this.error(`Path '${flags.path}' is not a directory`);
|
|
71
|
+
}
|
|
72
|
+
// Check if it's a git repository
|
|
73
|
+
const isGitRepo = await this.gitService.isRepository(entityPath);
|
|
74
|
+
// Get remote URL if it's a git repo and no remote provided
|
|
75
|
+
let remoteUrl = flags.remote;
|
|
76
|
+
if (isGitRepo && !remoteUrl) {
|
|
77
|
+
remoteUrl = await this.gitService.getRemoteUrl(entityPath) || undefined;
|
|
78
|
+
}
|
|
79
|
+
// Create entity
|
|
80
|
+
const entity = {
|
|
81
|
+
description: flags.description,
|
|
82
|
+
name: args.name,
|
|
83
|
+
path: flags.path.startsWith('./') ? flags.path : `./${flags.path}`,
|
|
84
|
+
remote: remoteUrl,
|
|
85
|
+
repo: flags.repo,
|
|
86
|
+
type: args.type,
|
|
87
|
+
};
|
|
88
|
+
// Add entity
|
|
89
|
+
await this.entityService.addEntity(entity.name, entity.type, entity.path, entity.remote || entity.repo);
|
|
90
|
+
this.log(`✅ Added ${args.type} entity '${args.name}'`);
|
|
91
|
+
this.log(` Path: ${entity.path}`);
|
|
92
|
+
if (entity.remote) {
|
|
93
|
+
this.log(` Remote: ${entity.remote}`);
|
|
94
|
+
}
|
|
95
|
+
if (entity.description) {
|
|
96
|
+
this.log(` Description: ${entity.description}`);
|
|
97
|
+
}
|
|
98
|
+
this.log('\nYou can now:');
|
|
99
|
+
this.log(` • Focus on this entity: gut focus ${args.name}`);
|
|
100
|
+
this.log(` • Clone it elsewhere: gut entity clone ${args.name}`);
|
|
101
|
+
this.log(' • View all entities: gut entity list');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class CloneAll extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
depth: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
parallel: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'skip-existing': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
private detectWorkspaceBranch;
|
|
14
|
+
private cloneEntity;
|
|
15
|
+
private cloneInParallel;
|
|
16
|
+
private cloneSequentially;
|
|
17
|
+
private printSummary;
|
|
18
|
+
}
|