@hyperdrive.bot/gut 0.1.6 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +1 -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 +17 -0
  25. package/dist/commands/entity/clone-all.js +127 -0
  26. package/dist/commands/entity/clone.d.ts +15 -0
  27. package/dist/commands/entity/clone.js +106 -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 +2008 -0
  111. 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,6 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Context extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -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,7 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Contexts extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ private formatTimeAgo;
7
+ }
@@ -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,17 @@
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, 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 cloneEntity;
14
+ private cloneInParallel;
15
+ private cloneSequentially;
16
+ private printSummary;
17
+ }