@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,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,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
|
+
}
|