@hyperdrive.bot/gut 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +1048 -1
  2. package/dist/base-command.d.ts +22 -0
  3. package/dist/base-command.js +99 -0
  4. package/dist/commands/add.d.ts +14 -0
  5. package/dist/commands/add.js +70 -0
  6. package/dist/commands/affected.d.ts +23 -0
  7. package/dist/commands/affected.js +323 -0
  8. package/dist/commands/audit.d.ts +33 -0
  9. package/dist/commands/audit.js +594 -0
  10. package/dist/commands/back.d.ts +6 -0
  11. package/dist/commands/back.js +29 -0
  12. package/dist/commands/checkout.d.ts +14 -0
  13. package/dist/commands/checkout.js +124 -0
  14. package/dist/commands/commit.d.ts +11 -0
  15. package/dist/commands/commit.js +107 -0
  16. package/dist/commands/context.d.ts +6 -0
  17. package/dist/commands/context.js +32 -0
  18. package/dist/commands/contexts.d.ts +7 -0
  19. package/dist/commands/contexts.js +88 -0
  20. package/dist/commands/deps.d.ts +10 -0
  21. package/dist/commands/deps.js +100 -0
  22. package/dist/commands/entity/add.d.ts +16 -0
  23. package/dist/commands/entity/add.js +103 -0
  24. package/dist/commands/entity/clone-all.d.ts +18 -0
  25. package/dist/commands/entity/clone-all.js +166 -0
  26. package/dist/commands/entity/clone.d.ts +17 -0
  27. package/dist/commands/entity/clone.js +132 -0
  28. package/dist/commands/entity/list.d.ts +11 -0
  29. package/dist/commands/entity/list.js +80 -0
  30. package/dist/commands/entity/remove.d.ts +12 -0
  31. package/dist/commands/entity/remove.js +54 -0
  32. package/dist/commands/extract.d.ts +35 -0
  33. package/dist/commands/extract.js +483 -0
  34. package/dist/commands/focus.d.ts +19 -0
  35. package/dist/commands/focus.js +137 -0
  36. package/dist/commands/graph.d.ts +18 -0
  37. package/dist/commands/graph.js +273 -0
  38. package/dist/commands/init.d.ts +11 -0
  39. package/dist/commands/init.js +75 -0
  40. package/dist/commands/insights.d.ts +21 -0
  41. package/dist/commands/insights.js +465 -0
  42. package/dist/commands/patterns.d.ts +40 -0
  43. package/dist/commands/patterns.js +405 -0
  44. package/dist/commands/pull.d.ts +11 -0
  45. package/dist/commands/pull.js +121 -0
  46. package/dist/commands/push.d.ts +11 -0
  47. package/dist/commands/push.js +97 -0
  48. package/dist/commands/quick-setup.d.ts +20 -0
  49. package/dist/commands/quick-setup.js +417 -0
  50. package/dist/commands/recent.d.ts +9 -0
  51. package/dist/commands/recent.js +51 -0
  52. package/dist/commands/related.d.ts +23 -0
  53. package/dist/commands/related.js +255 -0
  54. package/dist/commands/repos.d.ts +17 -0
  55. package/dist/commands/repos.js +184 -0
  56. package/dist/commands/stack.d.ts +10 -0
  57. package/dist/commands/stack.js +78 -0
  58. package/dist/commands/status.d.ts +13 -0
  59. package/dist/commands/status.js +193 -0
  60. package/dist/commands/sync.d.ts +11 -0
  61. package/dist/commands/sync.js +139 -0
  62. package/dist/commands/ticket/focus.d.ts +20 -0
  63. package/dist/commands/ticket/focus.js +217 -0
  64. package/dist/commands/ticket/get.d.ts +15 -0
  65. package/dist/commands/ticket/get.js +168 -0
  66. package/dist/commands/ticket/hint.d.ts +16 -0
  67. package/dist/commands/ticket/hint.js +147 -0
  68. package/dist/commands/ticket/index.d.ts +10 -0
  69. package/dist/commands/ticket/index.js +60 -0
  70. package/dist/commands/ticket/list.d.ts +13 -0
  71. package/dist/commands/ticket/list.js +120 -0
  72. package/dist/commands/ticket/sync.d.ts +14 -0
  73. package/dist/commands/ticket/sync.js +85 -0
  74. package/dist/commands/ticket/update.d.ts +17 -0
  75. package/dist/commands/ticket/update.js +142 -0
  76. package/dist/commands/unfocus.d.ts +6 -0
  77. package/dist/commands/unfocus.js +19 -0
  78. package/dist/commands/used-by.d.ts +13 -0
  79. package/dist/commands/used-by.js +110 -0
  80. package/dist/commands/workspace.d.ts +22 -0
  81. package/dist/commands/workspace.js +372 -0
  82. package/dist/index.d.ts +14 -0
  83. package/dist/index.js +16 -0
  84. package/dist/models/entity.model.d.ts +234 -0
  85. package/dist/models/entity.model.js +1 -0
  86. package/dist/models/ticket.model.d.ts +117 -0
  87. package/dist/models/ticket.model.js +43 -0
  88. package/dist/services/auth.service.d.ts +15 -0
  89. package/dist/services/auth.service.js +26 -0
  90. package/dist/services/config.service.d.ts +34 -0
  91. package/dist/services/config.service.js +234 -0
  92. package/dist/services/entity.service.d.ts +20 -0
  93. package/dist/services/entity.service.js +127 -0
  94. package/dist/services/focus.service.d.ts +71 -0
  95. package/dist/services/focus.service.js +614 -0
  96. package/dist/services/git.service.d.ts +39 -0
  97. package/dist/services/git.service.js +188 -0
  98. package/dist/services/gut-api.service.d.ts +53 -0
  99. package/dist/services/gut-api.service.js +99 -0
  100. package/dist/services/ticket.service.d.ts +84 -0
  101. package/dist/services/ticket.service.js +207 -0
  102. package/dist/utils/display.d.ts +26 -0
  103. package/dist/utils/display.js +145 -0
  104. package/dist/utils/filesystem.d.ts +32 -0
  105. package/dist/utils/filesystem.js +198 -0
  106. package/dist/utils/index.d.ts +13 -0
  107. package/dist/utils/index.js +14 -0
  108. package/dist/utils/validation.d.ts +22 -0
  109. package/dist/utils/validation.js +192 -0
  110. package/oclif.manifest.json +2006 -0
  111. package/package.json +11 -2
@@ -0,0 +1,142 @@
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
+ import { getStatusEmoji } from '../../models/ticket.model.js';
6
+ import { TicketService } from '../../services/ticket.service.js';
7
+ const VALID_STATUSES = [
8
+ 'enriching',
9
+ 'needs_clarity',
10
+ 'ready',
11
+ 'in_progress',
12
+ 'testing',
13
+ 'in_review',
14
+ 'blocked',
15
+ 'ready_to_merge',
16
+ 'deploying',
17
+ 'validating',
18
+ 'done'
19
+ ];
20
+ export default class TicketUpdate extends BaseCommand {
21
+ static args = {
22
+ ticketId: Args.string({
23
+ description: 'ticket ID to update',
24
+ name: 'ticketId',
25
+ required: true
26
+ })
27
+ };
28
+ static description = 'Update a ticket status or confidence';
29
+ static examples = [
30
+ '<%= config.bin %> <%= command.id %> PROJ-1234 --status in_progress',
31
+ '<%= config.bin %> <%= command.id %> PROJ-1234 --status testing --confidence 85',
32
+ '<%= config.bin %> <%= command.id %> PROJ-1234 --phase implementation'
33
+ ];
34
+ static flags = {
35
+ confidence: Flags.integer({
36
+ char: 'c',
37
+ description: 'new confidence score (0-100)'
38
+ }),
39
+ json: Flags.boolean({
40
+ char: 'j',
41
+ description: 'output as JSON'
42
+ }),
43
+ phase: Flags.string({
44
+ char: 'p',
45
+ description: 'new workflow phase',
46
+ options: ['enrichment', 'planning', 'implementation', 'testing', 'review', 'merge', 'deployment', 'validation']
47
+ }),
48
+ status: Flags.string({
49
+ char: 's',
50
+ description: `new ticket status (${VALID_STATUSES.join(', ')})`,
51
+ options: VALID_STATUSES
52
+ })
53
+ };
54
+ get requiresInit() {
55
+ return false;
56
+ }
57
+ async run() {
58
+ const { args, flags } = await this.parse(TicketUpdate);
59
+ const ticketService = new TicketService(this.configService);
60
+ if (!ticketService.isConfigured()) {
61
+ this.error('API not configured. Set GUT_API_ENDPOINT and GUT_TENANT_ID environment variables.');
62
+ }
63
+ // Validate that at least one update flag is provided
64
+ if (!flags.status && !flags.phase && flags.confidence === undefined) {
65
+ this.error('At least one of --status, --phase, or --confidence is required');
66
+ }
67
+ // Validate confidence range
68
+ if (flags.confidence !== undefined && (flags.confidence < 0 || flags.confidence > 100)) {
69
+ this.error('Confidence must be between 0 and 100');
70
+ }
71
+ // Get current ticket for comparison
72
+ const spinner = ora('Getting current ticket state...').start();
73
+ try {
74
+ const currentTicket = await ticketService.getTicket(args.ticketId);
75
+ if (!currentTicket) {
76
+ spinner.fail();
77
+ this.error(`Ticket ${args.ticketId} not found`);
78
+ }
79
+ spinner.text = 'Updating ticket...';
80
+ // Build update payload
81
+ const updates = {};
82
+ if (flags.status)
83
+ updates.status = flags.status;
84
+ if (flags.phase)
85
+ updates.phase = flags.phase;
86
+ if (flags.confidence !== undefined)
87
+ updates.confidence = flags.confidence;
88
+ const updatedTicket = await ticketService.updateTicket(args.ticketId, updates);
89
+ spinner.succeed('Ticket updated');
90
+ if (flags.json) {
91
+ this.log(JSON.stringify(updatedTicket, null, 2));
92
+ return;
93
+ }
94
+ this.log('');
95
+ this.log(chalk.bold(`šŸ“‹ ${updatedTicket.ticketId}: ${updatedTicket.summary}`));
96
+ this.log('');
97
+ // Show changes
98
+ this.log(chalk.bold('Changes:'));
99
+ if (flags.status && currentTicket.status !== updatedTicket.status) {
100
+ this.log(` Status: ${getStatusEmoji(currentTicket.status)} ${currentTicket.status} → ${getStatusEmoji(updatedTicket.status)} ${updatedTicket.status}`);
101
+ }
102
+ if (flags.phase && currentTicket.phase !== updatedTicket.phase) {
103
+ this.log(` Phase: ${currentTicket.phase} → ${updatedTicket.phase}`);
104
+ }
105
+ if (flags.confidence !== undefined && currentTicket.confidence !== updatedTicket.confidence) {
106
+ this.log(` Confidence: ${this.formatConfidence(currentTicket.confidence)} → ${this.formatConfidence(updatedTicket.confidence)}`);
107
+ }
108
+ this.log('');
109
+ this.log(chalk.green('āœ“ Update successful'));
110
+ this.log('');
111
+ }
112
+ catch (error) {
113
+ spinner.fail('Update failed');
114
+ const message = error instanceof Error ? error.message : String(error);
115
+ // Check for invalid transition error
116
+ if (message.includes('Invalid status transition')) {
117
+ this.log('');
118
+ this.log(chalk.red('Error: Invalid status transition'));
119
+ this.log('');
120
+ this.log('The requested status transition is not allowed.');
121
+ this.log('Valid transitions depend on the current status.');
122
+ this.log('');
123
+ this.log('Run `gut ticket get ' + args.ticketId + '` to see current status.');
124
+ this.log('');
125
+ this.exit(1);
126
+ }
127
+ this.error(`Failed to update ticket: ${message}`);
128
+ }
129
+ }
130
+ formatConfidence(confidence) {
131
+ if (confidence >= 90) {
132
+ return chalk.green(`${confidence}%`);
133
+ }
134
+ if (confidence >= 70) {
135
+ return chalk.yellow(`${confidence}%`);
136
+ }
137
+ if (confidence >= 50) {
138
+ return chalk.hex('#FFA500')(`${confidence}%`);
139
+ }
140
+ return chalk.red(`${confidence}%`);
141
+ }
142
+ }
@@ -0,0 +1,6 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Unfocus extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,19 @@
1
+ import chalk from 'chalk';
2
+ import { BaseCommand } from '../base-command.js';
3
+ export default class Unfocus extends BaseCommand {
4
+ static description = 'Clear the current focus';
5
+ static examples = [
6
+ '<%= config.bin %> <%= command.id %>',
7
+ ];
8
+ async run() {
9
+ await this.parse(Unfocus);
10
+ const focusedEntities = await this.focusService.getFocusedEntities();
11
+ if (focusedEntities.length === 0) {
12
+ this.log(chalk.yellow('No entities are currently focused'));
13
+ return;
14
+ }
15
+ await this.focusService.clearFocus();
16
+ const entities = focusedEntities.map(e => e.name).join(', ');
17
+ this.log(chalk.green(`āœ“ Cleared focus from: ${entities}`));
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class UsedBy 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 analyzeUsage;
10
+ private findClientDependents;
11
+ private findDirectDependents;
12
+ private findInferredDependents;
13
+ }
@@ -0,0 +1,110 @@
1
+ import { Args } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { BaseCommand } from '../base-command.js';
4
+ export default class UsedBy extends BaseCommand {
5
+ static args = {
6
+ entity: Args.string({
7
+ description: 'Entity name to analyze usage for',
8
+ name: 'entity',
9
+ required: false,
10
+ }),
11
+ };
12
+ static description = 'Show what entities depend on current focus or specified entity';
13
+ static examples = [
14
+ '<%= config.bin %> <%= command.id %>',
15
+ '<%= config.bin %> <%= command.id %> api',
16
+ ];
17
+ async run() {
18
+ const { args } = await this.parse(UsedBy);
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šŸ”„ Usage 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 usage = await this.analyzeUsage(entity);
40
+ if (usage.direct.length > 0) {
41
+ this.log(`\n ${chalk.yellow('Direct Dependents:')}`);
42
+ for (const dep of usage.direct) {
43
+ this.log(` ${this.getTypeEmoji(dep.type)} ${dep.name} (${dep.type})`);
44
+ }
45
+ }
46
+ if (usage.clients.length > 0) {
47
+ this.log(`\n ${chalk.blue('Client Dependencies:')}`);
48
+ for (const dep of usage.clients) {
49
+ this.log(` ${this.getTypeEmoji(dep.type)} ${dep.name} (${dep.type})`);
50
+ }
51
+ }
52
+ if (usage.inferred.length > 0) {
53
+ this.log(`\n ${chalk.dim('Inferred Usage:')}`);
54
+ for (const dep of usage.inferred) {
55
+ this.log(` ${this.getTypeEmoji(dep.type)} ${dep.name} (${dep.type}) ${chalk.dim('- pattern based')}`);
56
+ }
57
+ }
58
+ if (usage.direct.length === 0 && usage.clients.length === 0 && usage.inferred.length === 0) {
59
+ this.log(` ${chalk.dim('No dependents found')}`);
60
+ }
61
+ // Show impact assessment
62
+ const totalDependents = usage.direct.length + usage.clients.length + usage.inferred.length;
63
+ if (totalDependents > 0) {
64
+ this.log(`\n ${chalk.cyan('Impact Assessment:')}`);
65
+ this.log(` ${chalk.dim('Total dependents:')} ${totalDependents}`);
66
+ const criticalDependents = usage.direct.filter(e => e.type === 'client').length;
67
+ if (criticalDependents > 0) {
68
+ this.log(` ${chalk.red('Critical client dependencies:')} ${criticalDependents}`);
69
+ }
70
+ }
71
+ }
72
+ this.log(chalk.dim('\n─'.repeat(50)));
73
+ this.log(chalk.dim('Usage analysis helps assess impact of changes'));
74
+ }
75
+ async analyzeUsage(entity) {
76
+ const allEntities = this.entityService.getAllEntities();
77
+ const directDependents = this.findDirectDependents(allEntities, entity.name);
78
+ const clientDependents = this.findClientDependents(allEntities, entity.type);
79
+ const inferredDependents = this.findInferredDependents(allEntities, entity, clientDependents, directDependents);
80
+ return {
81
+ clients: clientDependents.filter(e => !directDependents.includes(e)),
82
+ direct: directDependents,
83
+ inferred: inferredDependents.slice(0, 5),
84
+ };
85
+ }
86
+ findClientDependents(allEntities, entityType) {
87
+ const systemTypes = ['system', 'module', 'service'];
88
+ if (systemTypes.includes(entityType)) {
89
+ return allEntities.filter(e => e.type === 'client');
90
+ }
91
+ return [];
92
+ }
93
+ findDirectDependents(allEntities, entityName) {
94
+ return allEntities.filter(e => e.metadata?.relationships?.dependent_systems?.includes(entityName)
95
+ || e.metadata?.relationships?.similar_entities?.includes(entityName));
96
+ }
97
+ findInferredDependents(allEntities, entity, clientDependents, directDependents) {
98
+ const inferredDependents = [];
99
+ const isExcluded = (e) => clientDependents.includes(e) || directDependents.includes(e);
100
+ if (entity.name.includes('design') || entity.name.includes('shared') || entity.type === 'module') {
101
+ const potentialUsers = allEntities.filter(e => (e.type === 'client' || e.type === 'delivery') && !isExcluded(e));
102
+ inferredDependents.push(...potentialUsers);
103
+ }
104
+ if (entity.name.includes('api') || entity.type === 'service') {
105
+ const apiUsers = allEntities.filter(e => e.type === 'client' && !isExcluded(e));
106
+ inferredDependents.push(...apiUsers);
107
+ }
108
+ return inferredDependents;
109
+ }
110
+ }
@@ -0,0 +1,22 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Workspace extends BaseCommand {
3
+ static args: {
4
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ 'entity-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ private generateContextsConfig;
14
+ private generateEntityMetadata;
15
+ private generateEntityMetadataContent;
16
+ private generateWorkspaceConfig;
17
+ private initWorkspaceStructure;
18
+ private readDirectoryItems;
19
+ private renderDirectoryItem;
20
+ private showDirectoryTree;
21
+ private showWorkspaceStructure;
22
+ }
@@ -0,0 +1,372 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import * as fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { BaseCommand } from '../base-command.js';
6
+ export default class Workspace extends BaseCommand {
7
+ static args = {
8
+ action: Args.string({
9
+ description: 'Action to perform (init, structure, generate-metadata)',
10
+ name: 'action',
11
+ options: ['init', 'structure', 'generate-metadata'],
12
+ required: true,
13
+ }),
14
+ };
15
+ static description = 'Manage workspace structure and entity metadata';
16
+ static examples = [
17
+ '<%= config.bin %> <%= command.id %> init',
18
+ '<%= config.bin %> <%= command.id %> structure',
19
+ '<%= config.bin %> <%= command.id %> generate-metadata',
20
+ ];
21
+ static flags = {
22
+ 'entity-type': Flags.string({
23
+ char: 't',
24
+ description: 'entity type for metadata generation',
25
+ options: ['client', 'prospect', 'company', 'initiative', 'system'],
26
+ }),
27
+ force: Flags.boolean({
28
+ char: 'f',
29
+ description: 'force overwrite existing files',
30
+ }),
31
+ };
32
+ async run() {
33
+ const { args, flags } = await this.parse(Workspace);
34
+ switch (args.action) {
35
+ case 'generate-metadata': {
36
+ await this.generateEntityMetadata(flags['entity-type'], flags.force);
37
+ break;
38
+ }
39
+ case 'init': {
40
+ await this.initWorkspaceStructure(flags.force);
41
+ break;
42
+ }
43
+ case 'structure': {
44
+ await this.showWorkspaceStructure();
45
+ break;
46
+ }
47
+ default: {
48
+ this.error(`Unknown action: ${args.action}`);
49
+ }
50
+ }
51
+ }
52
+ generateContextsConfig() {
53
+ return `# Context Definitions for AI and Focus Management
54
+ # Generated on ${new Date().toISOString()}
55
+
56
+ contexts:
57
+ # Example client context
58
+ # clients:
59
+ # mindtools:
60
+ # type: "client"
61
+ # description: "MindTools React platform - 24M+ users"
62
+ # status: "active_development"
63
+ #
64
+ # repositories:
65
+ # primary: "entities/clients/mindtools/delivery"
66
+ # private: "entities/clients/mindtools/business"
67
+ # related: ["systems/api", "systems/shared/design-system"]
68
+ #
69
+ # focus_modes:
70
+ # delivery:
71
+ # include: ["delivery/repos/", "systems/shared/design-system/"]
72
+ # exclude: ["business/contracts/"]
73
+ # ai_persona: "senior_react_developer"
74
+ # suggested_duration: "1h 30m"
75
+ #
76
+ # strategy:
77
+ # include: ["delivery/", "business/", "insights/"]
78
+ # exclude: []
79
+ # ai_persona: "ceo_strategic_advisor"
80
+ # suggested_duration: "2h 30m"
81
+
82
+ # Focus mode definitions
83
+ focus_modes:
84
+ delivery:
85
+ description: "Technical development and implementation"
86
+ typical_duration: "1-2 hours"
87
+ ai_persona: "senior_developer"
88
+
89
+ strategy:
90
+ description: "Business strategy and planning"
91
+ typical_duration: "2-3 hours"
92
+ ai_persona: "strategic_advisor"
93
+
94
+ audit:
95
+ description: "Review and compliance analysis"
96
+ typical_duration: "45-90 minutes"
97
+ ai_persona: "business_auditor"
98
+
99
+ debug:
100
+ description: "Problem investigation and resolution"
101
+ typical_duration: "30-60 minutes"
102
+ ai_persona: "debugging_specialist"
103
+
104
+ research:
105
+ description: "Market and technical research"
106
+ typical_duration: "1-2 hours"
107
+ ai_persona: "research_analyst"
108
+
109
+ proposal:
110
+ description: "Proposal preparation and client communication"
111
+ typical_duration: "2-4 hours"
112
+ ai_persona: "business_consultant"
113
+ `;
114
+ }
115
+ async generateEntityMetadata(entityType, force) {
116
+ const entities = entityType
117
+ ? this.entityService.getEntitiesByType(entityType)
118
+ : this.entityService.getAllEntities();
119
+ if (entities.length === 0) {
120
+ this.error('No entities found. Add entities first with "gut entity add"');
121
+ }
122
+ this.log(chalk.bold('\nšŸ“ Generating Entity Metadata'));
123
+ this.log(chalk.dim('─'.repeat(50)));
124
+ for (const entity of entities) {
125
+ const entityPath = this.entityService.resolveEntityPath(entity);
126
+ const metadataPath = path.join(entityPath, '.entity.yaml');
127
+ if (fs.existsSync(metadataPath) && !force) {
128
+ this.log(` ${chalk.dim(entity.name)} (metadata already exists)`);
129
+ continue;
130
+ }
131
+ // Ensure entity directory exists
132
+ if (!fs.existsSync(entityPath)) {
133
+ fs.mkdirSync(entityPath, { recursive: true });
134
+ }
135
+ const metadata = this.generateEntityMetadataContent(entity);
136
+ fs.writeFileSync(metadataPath, metadata);
137
+ this.log(`āœ“ Generated metadata for ${chalk.green(entity.name)}`);
138
+ }
139
+ this.log(chalk.dim('\n─'.repeat(50)));
140
+ this.log(chalk.green('āœ… Entity metadata generated'));
141
+ }
142
+ generateEntityMetadataContent(entity) {
143
+ const now = new Date().toISOString();
144
+ return `# Entity Metadata for ${entity.name}
145
+ # Generated on ${now}
146
+
147
+ entity:
148
+ type: "${entity.type}"
149
+ name: "${entity.name}"
150
+ description: "${entity.description || `${entity.type} entity: ${entity.name}`}"
151
+ created: "${now}"
152
+
153
+ # Business information (for client/prospect entities)
154
+ business:
155
+ # primary_contact: ""
156
+ # contract_value: ""
157
+ # team_size: 0
158
+ # priority: "medium" # low, medium, high
159
+ # status: "active"
160
+
161
+ # Relationships with other entities
162
+ relationships:
163
+ # similar_entities: []
164
+ # dependent_systems: ["systems/api"]
165
+ # related_initiatives: []
166
+
167
+ # Working patterns and preferences
168
+ working_patterns:
169
+ # typical_focus_duration: "2h"
170
+ # common_paired_entities: []
171
+ # strategic_review_frequency: "weekly"
172
+ # productive_times: ["9-11am", "2-4pm"]
173
+
174
+ # AI context configuration
175
+ ai_context:
176
+ # persona: "senior_developer"
177
+ # knowledge_scope: "complete_technical_context"
178
+ # decision_authority: "technical_implementation"
179
+
180
+ # Focus intelligence (learned from usage)
181
+ focus_intelligence:
182
+ # optimal_session_duration: ""
183
+ # average_interruptions: 0
184
+ # effectiveness_metrics:
185
+ # commits_per_session: 0
186
+ # lines_changed_per_session: 0
187
+ # issues_resolved_per_session: 0
188
+
189
+ # Repository information
190
+ repository:
191
+ path: "${entity.path}"
192
+ # remote: "${entity.repository || ''}"
193
+ # branch: "main"
194
+ # last_sync: ""
195
+ `;
196
+ }
197
+ generateWorkspaceConfig() {
198
+ return `# DevSquad Context Operating System - Workspace Configuration
199
+ # Generated on ${new Date().toISOString()}
200
+
201
+ workspace:
202
+ version: "1.0"
203
+ name: "DevSquad Workspace"
204
+ description: "Context-aware development workspace"
205
+
206
+ structure:
207
+ entities_dir: "entities"
208
+ systems_dir: "systems"
209
+ focus_dir: "focus"
210
+
211
+ settings:
212
+ default_focus_mode: "delivery"
213
+ auto_context_generation: true
214
+ focus_session_tracking: true
215
+
216
+ integrations:
217
+ git:
218
+ auto_sync: false
219
+ multi_repo_operations: true
220
+ ai:
221
+ context_generation: true
222
+ persona_switching: true
223
+ jira:
224
+ enabled: false
225
+ base_url: ""
226
+
227
+ # Entity type definitions
228
+ entity_types:
229
+ client:
230
+ description: "Active client relationships"
231
+ default_modes: ["delivery", "strategy", "audit"]
232
+
233
+ prospect:
234
+ description: "Potential client relationships"
235
+ default_modes: ["research", "proposal"]
236
+
237
+ company:
238
+ description: "DevSquad group companies"
239
+ default_modes: ["strategy", "audit"]
240
+
241
+ initiative:
242
+ description: "Strategic cross-entity initiatives"
243
+ default_modes: ["strategy", "delivery"]
244
+
245
+ system:
246
+ description: "Supporting systems and infrastructure"
247
+ default_modes: ["delivery", "debug"]
248
+ `;
249
+ }
250
+ async initWorkspaceStructure(force) {
251
+ const workspaceRoot = this.configService.getWorkspaceRoot();
252
+ this.log(chalk.bold('\nšŸ—ļø Initializing Workspace Structure'));
253
+ this.log(chalk.dim('─'.repeat(50)));
254
+ // Create directory structure
255
+ const directories = [
256
+ 'entities/clients',
257
+ 'entities/prospects',
258
+ 'entities/companies',
259
+ 'entities/initiatives',
260
+ 'systems/api',
261
+ 'systems/tools',
262
+ 'systems/shared',
263
+ 'focus/current',
264
+ 'focus/recent',
265
+ 'focus/breadcrumbs',
266
+ 'focus/stack',
267
+ ];
268
+ for (const dir of directories) {
269
+ const fullPath = path.join(workspaceRoot, dir);
270
+ if (fs.existsSync(fullPath)) {
271
+ this.log(` ${chalk.dim(dir)} (already exists)`);
272
+ }
273
+ else {
274
+ fs.mkdirSync(fullPath, { recursive: true });
275
+ this.log(`āœ“ Created ${chalk.green(dir)}`);
276
+ }
277
+ }
278
+ // Create workspace configuration
279
+ const workspaceConfigPath = path.join(workspaceRoot, '.workspace', 'config.yaml');
280
+ const workspaceConfigDir = path.dirname(workspaceConfigPath);
281
+ if (!fs.existsSync(workspaceConfigDir)) {
282
+ fs.mkdirSync(workspaceConfigDir, { recursive: true });
283
+ }
284
+ if (!fs.existsSync(workspaceConfigPath) || force) {
285
+ const workspaceConfig = this.generateWorkspaceConfig();
286
+ fs.writeFileSync(workspaceConfigPath, workspaceConfig);
287
+ this.log(`āœ“ Created ${chalk.green('.workspace/config.yaml')}`);
288
+ }
289
+ else {
290
+ this.log(` ${chalk.dim('.workspace/config.yaml')} (already exists)`);
291
+ }
292
+ // Create contexts configuration
293
+ const contextsConfigPath = path.join(workspaceRoot, '.workspace', 'contexts.yaml');
294
+ if (!fs.existsSync(contextsConfigPath) || force) {
295
+ const contextsConfig = this.generateContextsConfig();
296
+ fs.writeFileSync(contextsConfigPath, contextsConfig);
297
+ this.log(`āœ“ Created ${chalk.green('.workspace/contexts.yaml')}`);
298
+ }
299
+ else {
300
+ this.log(` ${chalk.dim('.workspace/contexts.yaml')} (already exists)`);
301
+ }
302
+ this.log(chalk.dim('\n─'.repeat(50)));
303
+ this.log(chalk.green('āœ… Workspace structure initialized'));
304
+ this.log(chalk.dim('Next steps:'));
305
+ this.log(chalk.dim('• Add entities: gut entity add <name> <type> <path>'));
306
+ this.log(chalk.dim('• Generate metadata: gut workspace generate-metadata'));
307
+ this.log(chalk.dim('• Set focus: gut focus client <name>'));
308
+ }
309
+ readDirectoryItems(dirPath) {
310
+ try {
311
+ return fs.readdirSync(dirPath)
312
+ .filter(item => !item.startsWith('.') || item === '.workspace')
313
+ .sort();
314
+ }
315
+ catch {
316
+ return [];
317
+ }
318
+ }
319
+ renderDirectoryItem(itemPath, item, options) {
320
+ try {
321
+ const stats = fs.statSync(itemPath);
322
+ const { currentPrefix, depth, maxDepth, nextPrefix, prefix } = options;
323
+ if (stats.isDirectory()) {
324
+ this.log(`${prefix}${currentPrefix}${chalk.blue(item)}/`);
325
+ this.showDirectoryTree(itemPath, nextPrefix, depth + 1, maxDepth);
326
+ }
327
+ else {
328
+ this.log(`${prefix}${currentPrefix}${item}`);
329
+ }
330
+ }
331
+ catch {
332
+ // Skip items we can't access
333
+ }
334
+ }
335
+ showDirectoryTree(dirPath, prefix, depth, maxDepth) {
336
+ if (depth > maxDepth)
337
+ return;
338
+ const items = this.readDirectoryItems(dirPath);
339
+ for (const [index, item] of items.entries()) {
340
+ const itemPath = path.join(dirPath, item);
341
+ const isLast = index === items.length - 1;
342
+ const currentPrefix = isLast ? '└── ' : 'ā”œā”€ā”€ ';
343
+ const nextPrefix = prefix + (isLast ? ' ' : '│ ');
344
+ this.renderDirectoryItem(itemPath, item, {
345
+ currentPrefix, depth, maxDepth, nextPrefix, prefix,
346
+ });
347
+ }
348
+ }
349
+ async showWorkspaceStructure() {
350
+ const workspaceRoot = this.configService.getWorkspaceRoot();
351
+ this.log(chalk.bold('\nšŸ“ Workspace Structure'));
352
+ this.log(chalk.dim('─'.repeat(50)));
353
+ // Show directory tree
354
+ this.showDirectoryTree(workspaceRoot, '', 0, 2);
355
+ // Show entities summary
356
+ const entities = this.entityService.getAllEntities();
357
+ if (entities.length > 0) {
358
+ this.log(chalk.bold('\nšŸ¢ Entities Summary'));
359
+ this.log(chalk.dim('─'.repeat(50)));
360
+ const byType = {};
361
+ for (const entity of entities) {
362
+ if (!byType[entity.type])
363
+ byType[entity.type] = 0;
364
+ byType[entity.type]++;
365
+ }
366
+ for (const [type, count] of Object.entries(byType)) {
367
+ const emoji = this.getTypeEmoji(type);
368
+ this.log(`${emoji} ${type}: ${count}`);
369
+ }
370
+ }
371
+ }
372
+ }