@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,168 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import { getStatusEmoji, getPriorityEmoji } from '../../models/ticket.model.js';
|
|
5
|
+
import { TicketService } from '../../services/ticket.service.js';
|
|
6
|
+
export default class TicketGet extends BaseCommand {
|
|
7
|
+
static args = {
|
|
8
|
+
ticketId: Args.string({
|
|
9
|
+
description: 'ticket ID (e.g., PROJ-1234)',
|
|
10
|
+
name: 'ticketId',
|
|
11
|
+
required: true
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get details for a gut ticket';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234 --json'
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
json: Flags.boolean({
|
|
21
|
+
char: 'j',
|
|
22
|
+
description: 'output as JSON'
|
|
23
|
+
})
|
|
24
|
+
};
|
|
25
|
+
get requiresInit() {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(TicketGet);
|
|
30
|
+
const ticketService = new TicketService(this.configService);
|
|
31
|
+
if (!ticketService.isConfigured()) {
|
|
32
|
+
this.error('API not configured. Set GUT_API_ENDPOINT and GUT_TENANT_ID environment variables.');
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const ticket = await ticketService.getTicket(args.ticketId);
|
|
36
|
+
if (!ticket) {
|
|
37
|
+
this.error(`Ticket ${args.ticketId} not found`);
|
|
38
|
+
}
|
|
39
|
+
if (flags.json) {
|
|
40
|
+
this.log(JSON.stringify(ticket, null, 2));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.printTicketDetails(ticket);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
this.error(`Failed to get ticket: ${message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
printTicketDetails(ticket) {
|
|
51
|
+
const statusEmoji = getStatusEmoji(ticket.status);
|
|
52
|
+
const priorityEmoji = getPriorityEmoji(ticket.priority);
|
|
53
|
+
this.log('');
|
|
54
|
+
this.log(chalk.bold(`📋 ${ticket.ticketId}: ${ticket.summary}`));
|
|
55
|
+
this.log('');
|
|
56
|
+
// Status section
|
|
57
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
58
|
+
this.log(chalk.bold('Status'));
|
|
59
|
+
this.log(` ${statusEmoji} Status: ${ticket.status}`);
|
|
60
|
+
this.log(` 📍 Phase: ${ticket.phase}`);
|
|
61
|
+
this.log(` 📊 Confidence: ${this.formatConfidence(ticket.confidence)}`);
|
|
62
|
+
this.log(` ${priorityEmoji} Priority: ${ticket.priority}`);
|
|
63
|
+
this.log(` 🏷️ Type: ${ticket.type}`);
|
|
64
|
+
// Source section
|
|
65
|
+
this.log('');
|
|
66
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
67
|
+
this.log(chalk.bold('Source'));
|
|
68
|
+
this.log(` 🔗 Source: ${ticket.source.type}`);
|
|
69
|
+
this.log(` 🌐 URL: ${ticket.source.externalUrl}`);
|
|
70
|
+
if (ticket.source.projectKey) {
|
|
71
|
+
this.log(` 📁 Project: ${ticket.source.projectKey}`);
|
|
72
|
+
}
|
|
73
|
+
// Git section
|
|
74
|
+
this.log('');
|
|
75
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
76
|
+
this.log(chalk.bold('Git'));
|
|
77
|
+
this.log(` 🌿 Branch: ${ticket.branch}`);
|
|
78
|
+
// Entities section
|
|
79
|
+
if (ticket.requiredEntities.length > 0) {
|
|
80
|
+
this.log('');
|
|
81
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
82
|
+
this.log(chalk.bold('Required Entities'));
|
|
83
|
+
for (const entity of ticket.requiredEntities) {
|
|
84
|
+
this.log(` • ${entity}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (ticket.relatedEntities && ticket.relatedEntities.length > 0) {
|
|
88
|
+
this.log('');
|
|
89
|
+
this.log(chalk.bold('Related Entities'));
|
|
90
|
+
for (const entity of ticket.relatedEntities) {
|
|
91
|
+
this.log(` • ${chalk.dim(entity)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Affected files section
|
|
95
|
+
if (ticket.affectedFiles && ticket.affectedFiles.length > 0) {
|
|
96
|
+
this.log('');
|
|
97
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
98
|
+
this.log(chalk.bold('Affected Files'));
|
|
99
|
+
for (const file of ticket.affectedFiles.slice(0, 10)) {
|
|
100
|
+
this.log(` • ${file}`);
|
|
101
|
+
}
|
|
102
|
+
if (ticket.affectedFiles.length > 10) {
|
|
103
|
+
this.log(chalk.dim(` ... and ${ticket.affectedFiles.length - 10} more`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Labels section
|
|
107
|
+
if (ticket.labels.length > 0) {
|
|
108
|
+
this.log('');
|
|
109
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
110
|
+
this.log(chalk.bold('Labels'));
|
|
111
|
+
this.log(` ${ticket.labels.map(l => chalk.cyan(l)).join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
// Handoff section (if blocked)
|
|
114
|
+
if (ticket.handoff?.required) {
|
|
115
|
+
this.log('');
|
|
116
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
117
|
+
this.log(chalk.bold.red('⚠️ Handoff Required'));
|
|
118
|
+
if (ticket.handoff.reason) {
|
|
119
|
+
this.log(` Reason: ${ticket.handoff.reason}`);
|
|
120
|
+
}
|
|
121
|
+
if (ticket.handoff.triggeredAt) {
|
|
122
|
+
this.log(` Triggered: ${new Date(ticket.handoff.triggeredAt).toLocaleString()}`);
|
|
123
|
+
}
|
|
124
|
+
if (ticket.handoff.confidenceAtTrigger !== undefined) {
|
|
125
|
+
this.log(` Confidence at trigger: ${ticket.handoff.confidenceAtTrigger}%`);
|
|
126
|
+
}
|
|
127
|
+
// Previous hints
|
|
128
|
+
if (ticket.handoff.humanHints && ticket.handoff.humanHints.length > 0) {
|
|
129
|
+
this.log('');
|
|
130
|
+
this.log(chalk.bold(' Previous Hints:'));
|
|
131
|
+
for (const hint of ticket.handoff.humanHints) {
|
|
132
|
+
this.log(` • "${hint.hint}" (by ${hint.givenBy})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Suggested next steps
|
|
136
|
+
if (ticket.handoff.suggestedNextSteps && ticket.handoff.suggestedNextSteps.length > 0) {
|
|
137
|
+
this.log('');
|
|
138
|
+
this.log(chalk.bold(' Suggested Next Steps:'));
|
|
139
|
+
for (const step of ticket.handoff.suggestedNextSteps) {
|
|
140
|
+
this.log(` • ${step}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Description section
|
|
145
|
+
this.log('');
|
|
146
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
147
|
+
this.log(chalk.bold('Description'));
|
|
148
|
+
this.log(ticket.description || chalk.dim('No description'));
|
|
149
|
+
// Timestamps
|
|
150
|
+
this.log('');
|
|
151
|
+
this.log(chalk.dim('─'.repeat(60)));
|
|
152
|
+
this.log(chalk.dim(`Created: ${new Date(ticket.createdAt).toLocaleString()}`));
|
|
153
|
+
this.log(chalk.dim(`Updated: ${new Date(ticket.updatedAt).toLocaleString()}`));
|
|
154
|
+
this.log('');
|
|
155
|
+
}
|
|
156
|
+
formatConfidence(confidence) {
|
|
157
|
+
if (confidence >= 90) {
|
|
158
|
+
return chalk.green(`${confidence}%`);
|
|
159
|
+
}
|
|
160
|
+
if (confidence >= 70) {
|
|
161
|
+
return chalk.yellow(`${confidence}%`);
|
|
162
|
+
}
|
|
163
|
+
if (confidence >= 50) {
|
|
164
|
+
return chalk.hex('#FFA500')(`${confidence}%`);
|
|
165
|
+
}
|
|
166
|
+
return chalk.red(`${confidence}%`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class TicketHint extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
hint: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
ticketId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
editor: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'given-by': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
protected get requiresInit(): boolean;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Args, 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
|
+
import { getStatusEmoji } from '../../models/ticket.model.js';
|
|
7
|
+
import { TicketService } from '../../services/ticket.service.js';
|
|
8
|
+
export default class TicketHint extends BaseCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
hint: Args.string({
|
|
11
|
+
description: 'hint text to add (if not provided, will prompt)',
|
|
12
|
+
name: 'hint',
|
|
13
|
+
required: false
|
|
14
|
+
}),
|
|
15
|
+
ticketId: Args.string({
|
|
16
|
+
description: 'ticket ID to add hint to',
|
|
17
|
+
name: 'ticketId',
|
|
18
|
+
required: true
|
|
19
|
+
})
|
|
20
|
+
};
|
|
21
|
+
static description = 'Add a hint to a blocked or needs_clarity ticket to help AI retry';
|
|
22
|
+
static examples = [
|
|
23
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234 "The auth token is in the Authorization header"',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234 --editor',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234'
|
|
26
|
+
];
|
|
27
|
+
static flags = {
|
|
28
|
+
editor: Flags.boolean({
|
|
29
|
+
char: 'e',
|
|
30
|
+
description: 'open editor for hint text'
|
|
31
|
+
}),
|
|
32
|
+
'given-by': Flags.string({
|
|
33
|
+
char: 'g',
|
|
34
|
+
description: 'identifier of who is giving the hint',
|
|
35
|
+
default: process.env.USER || 'human'
|
|
36
|
+
}),
|
|
37
|
+
json: Flags.boolean({
|
|
38
|
+
char: 'j',
|
|
39
|
+
description: 'output as JSON'
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
get requiresInit() {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
async run() {
|
|
46
|
+
const { args, flags } = await this.parse(TicketHint);
|
|
47
|
+
const ticketService = new TicketService(this.configService);
|
|
48
|
+
if (!ticketService.isConfigured()) {
|
|
49
|
+
this.error('API not configured. Set GUT_API_ENDPOINT and GUT_TENANT_ID environment variables.');
|
|
50
|
+
}
|
|
51
|
+
// Get current ticket to show context
|
|
52
|
+
const spinner = ora('Getting ticket details...').start();
|
|
53
|
+
try {
|
|
54
|
+
const ticket = await ticketService.getTicket(args.ticketId);
|
|
55
|
+
spinner.stop();
|
|
56
|
+
if (!ticket) {
|
|
57
|
+
this.error(`Ticket ${args.ticketId} not found`);
|
|
58
|
+
}
|
|
59
|
+
// Show ticket context
|
|
60
|
+
if (!flags.json) {
|
|
61
|
+
this.log('');
|
|
62
|
+
this.log(chalk.bold(`📋 ${ticket.ticketId}: ${ticket.summary}`));
|
|
63
|
+
this.log(` ${getStatusEmoji(ticket.status)} Status: ${ticket.status}`);
|
|
64
|
+
if (ticket.handoff?.reason) {
|
|
65
|
+
this.log('');
|
|
66
|
+
this.log(chalk.bold.yellow('⚠️ Block Reason:'));
|
|
67
|
+
this.log(` ${ticket.handoff.reason}`);
|
|
68
|
+
}
|
|
69
|
+
if (ticket.handoff?.suggestedNextSteps && ticket.handoff.suggestedNextSteps.length > 0) {
|
|
70
|
+
this.log('');
|
|
71
|
+
this.log(chalk.bold('💡 Suggested Next Steps:'));
|
|
72
|
+
for (const step of ticket.handoff.suggestedNextSteps) {
|
|
73
|
+
this.log(` • ${step}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Show previous hints
|
|
77
|
+
if (ticket.handoff?.humanHints && ticket.handoff.humanHints.length > 0) {
|
|
78
|
+
this.log('');
|
|
79
|
+
this.log(chalk.bold('📝 Previous Hints:'));
|
|
80
|
+
for (const hint of ticket.handoff.humanHints) {
|
|
81
|
+
const date = new Date(hint.givenAt).toLocaleString();
|
|
82
|
+
this.log(` • "${hint.hint}"`);
|
|
83
|
+
this.log(chalk.dim(` by ${hint.givenBy} at ${date}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.log('');
|
|
87
|
+
}
|
|
88
|
+
// Get hint text
|
|
89
|
+
let hintText = args.hint;
|
|
90
|
+
if (!hintText) {
|
|
91
|
+
if (flags.editor) {
|
|
92
|
+
// Open editor for hint
|
|
93
|
+
const result = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: 'editor',
|
|
96
|
+
name: 'hint',
|
|
97
|
+
message: 'Enter your hint (this will open your editor):',
|
|
98
|
+
default: `# Add your hint below to help the AI understand what to do next.\n# The AI will retry with this context.\n\n`
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
hintText = result.hint.replace(/^#.*$/gm, '').trim();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Prompt for hint
|
|
105
|
+
const result = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: 'input',
|
|
108
|
+
name: 'hint',
|
|
109
|
+
message: 'Enter your hint:',
|
|
110
|
+
validate: (input) => input.trim().length > 0 || 'Hint cannot be empty'
|
|
111
|
+
}
|
|
112
|
+
]);
|
|
113
|
+
hintText = result.hint;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!hintText || hintText.trim().length === 0) {
|
|
117
|
+
this.error('Hint cannot be empty');
|
|
118
|
+
}
|
|
119
|
+
// Submit hint
|
|
120
|
+
spinner.start('Adding hint...');
|
|
121
|
+
const response = await ticketService.addHint(args.ticketId, hintText.trim(), flags['given-by']);
|
|
122
|
+
spinner.succeed('Hint added');
|
|
123
|
+
if (flags.json) {
|
|
124
|
+
this.log(JSON.stringify(response, null, 2));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.log('');
|
|
128
|
+
this.log(chalk.green('✓ Hint added successfully'));
|
|
129
|
+
this.log(` ${getStatusEmoji(response.status)} New status: ${response.status}`);
|
|
130
|
+
if (response.retryTriggered) {
|
|
131
|
+
this.log('');
|
|
132
|
+
this.log(chalk.cyan('🔄 Retry triggered - AI will process with your hint'));
|
|
133
|
+
this.log(chalk.dim(' Run `gut ticket get ' + args.ticketId + '` to check progress'));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.log('');
|
|
137
|
+
this.log(chalk.dim(' No automatic retry triggered'));
|
|
138
|
+
}
|
|
139
|
+
this.log('');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
spinner.fail('Failed');
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
this.error(`Failed to add hint: ${message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class Ticket extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
help: import("@oclif/core/interfaces").BooleanFlag<void>;
|
|
7
|
+
};
|
|
8
|
+
protected get requiresInit(): boolean;
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
export default class Ticket extends BaseCommand {
|
|
5
|
+
static description = 'Manage gut tickets from the ADHB (Autonomous Development with Human Backstop) system';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> ticket list',
|
|
8
|
+
'<%= config.bin %> ticket get PROJ-1234',
|
|
9
|
+
'<%= config.bin %> ticket focus PROJ-1234',
|
|
10
|
+
'<%= config.bin %> ticket hint PROJ-1234 "Check the config file"',
|
|
11
|
+
'<%= config.bin %> ticket sync PROJ-1234',
|
|
12
|
+
'<%= config.bin %> ticket update PROJ-1234 --status in_progress'
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
help: Flags.help({ char: 'h' })
|
|
16
|
+
};
|
|
17
|
+
get requiresInit() {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
async run() {
|
|
21
|
+
this.log('');
|
|
22
|
+
this.log(chalk.bold('🎫 Gut Ticket Commands'));
|
|
23
|
+
this.log('');
|
|
24
|
+
this.log('Manage tickets from the ADHB (Autonomous Development with Human Backstop) system.');
|
|
25
|
+
this.log('These commands interact with the gut backend API to manage AI-enriched tickets.');
|
|
26
|
+
this.log('');
|
|
27
|
+
this.log(chalk.bold('Available Commands:'));
|
|
28
|
+
this.log('');
|
|
29
|
+
this.log(` ${chalk.cyan('gut ticket list')} List all tickets`);
|
|
30
|
+
this.log(` ${chalk.cyan('gut ticket get <id>')} Get details for a specific ticket`);
|
|
31
|
+
this.log(` ${chalk.cyan('gut ticket focus <id>')} Focus on a ticket (download manifest, clone entities)`);
|
|
32
|
+
this.log(` ${chalk.cyan('gut ticket hint <id>')} Add a hint to help AI retry a blocked ticket`);
|
|
33
|
+
this.log(` ${chalk.cyan('gut ticket sync <id>')} Sync ticket state with external source`);
|
|
34
|
+
this.log(` ${chalk.cyan('gut ticket update <id>')} Update ticket status or confidence`);
|
|
35
|
+
this.log('');
|
|
36
|
+
this.log(chalk.bold('Workflow:'));
|
|
37
|
+
this.log('');
|
|
38
|
+
this.log(' 1. Tickets are created from JIRA/GitHub and enriched by AI');
|
|
39
|
+
this.log(' 2. Use `gut ticket list` to see available tickets');
|
|
40
|
+
this.log(' 3. Use `gut ticket focus <id>` to set up your workspace');
|
|
41
|
+
this.log(' 4. If blocked, use `gut ticket hint <id>` to help AI');
|
|
42
|
+
this.log(' 5. Use `gut ticket sync <id>` to sync status back to source');
|
|
43
|
+
this.log('');
|
|
44
|
+
this.log(chalk.bold('Configuration:'));
|
|
45
|
+
this.log('');
|
|
46
|
+
this.log(' Set these environment variables:');
|
|
47
|
+
this.log(` ${chalk.cyan('GUT_API_ENDPOINT')} - API base URL (e.g., https://api.devsquad.com)`);
|
|
48
|
+
this.log(` ${chalk.cyan('GUT_TENANT_ID')} - Your tenant ID`);
|
|
49
|
+
this.log(` ${chalk.cyan('GUT_AUTH_TOKEN')} - Authentication token (optional)`);
|
|
50
|
+
this.log('');
|
|
51
|
+
this.log(' Or create `.gut/api.json` in your workspace:');
|
|
52
|
+
this.log(chalk.dim(' {'));
|
|
53
|
+
this.log(chalk.dim(' "apiEndpoint": "https://api.devsquad.com",'));
|
|
54
|
+
this.log(chalk.dim(' "tenantId": "your-tenant-id"'));
|
|
55
|
+
this.log(chalk.dim(' }'));
|
|
56
|
+
this.log('');
|
|
57
|
+
this.log(chalk.dim('Run `gut ticket <command> --help` for more information on a specific command.'));
|
|
58
|
+
this.log('');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class TicketList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
protected get requiresInit(): boolean;
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private formatConfidence;
|
|
13
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
import { BaseCommand } from '../../base-command.js';
|
|
5
|
+
import { getStatusEmoji, getPriorityEmoji } 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 TicketList extends BaseCommand {
|
|
21
|
+
static description = 'List gut tickets';
|
|
22
|
+
static examples = [
|
|
23
|
+
'<%= config.bin %> <%= command.id %>',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> --status ready',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> --status blocked --limit 10',
|
|
26
|
+
'<%= config.bin %> <%= command.id %> --json'
|
|
27
|
+
];
|
|
28
|
+
static flags = {
|
|
29
|
+
json: Flags.boolean({
|
|
30
|
+
char: 'j',
|
|
31
|
+
description: 'output as JSON'
|
|
32
|
+
}),
|
|
33
|
+
limit: Flags.integer({
|
|
34
|
+
char: 'l',
|
|
35
|
+
default: 20,
|
|
36
|
+
description: 'maximum number of tickets to show'
|
|
37
|
+
}),
|
|
38
|
+
status: Flags.string({
|
|
39
|
+
char: 's',
|
|
40
|
+
description: `filter by status (${VALID_STATUSES.join(', ')})`,
|
|
41
|
+
options: VALID_STATUSES
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
get requiresInit() {
|
|
45
|
+
return false; // Ticket commands work without gut init
|
|
46
|
+
}
|
|
47
|
+
async run() {
|
|
48
|
+
const { flags } = await this.parse(TicketList);
|
|
49
|
+
const ticketService = new TicketService(this.configService);
|
|
50
|
+
if (!ticketService.isConfigured()) {
|
|
51
|
+
this.error('API not configured. Set GUT_API_ENDPOINT and GUT_TENANT_ID environment variables.');
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const response = await ticketService.listTickets({
|
|
55
|
+
status: flags.status,
|
|
56
|
+
limit: flags.limit
|
|
57
|
+
});
|
|
58
|
+
if (flags.json) {
|
|
59
|
+
this.log(JSON.stringify(response.tickets, null, 2));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (response.tickets.length === 0) {
|
|
63
|
+
this.log('No tickets found');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Create table
|
|
67
|
+
const table = new Table({
|
|
68
|
+
head: [
|
|
69
|
+
chalk.bold('ID'),
|
|
70
|
+
chalk.bold('Status'),
|
|
71
|
+
chalk.bold('Priority'),
|
|
72
|
+
chalk.bold('Summary'),
|
|
73
|
+
chalk.bold('Confidence'),
|
|
74
|
+
chalk.bold('Branch')
|
|
75
|
+
],
|
|
76
|
+
colWidths: [15, 18, 10, 40, 12, 30]
|
|
77
|
+
});
|
|
78
|
+
for (const ticket of response.tickets) {
|
|
79
|
+
const statusText = `${getStatusEmoji(ticket.status)} ${ticket.status}`;
|
|
80
|
+
const priorityText = `${getPriorityEmoji(ticket.priority)} ${ticket.priority}`;
|
|
81
|
+
const confidenceText = this.formatConfidence(ticket.confidence);
|
|
82
|
+
const summary = ticket.summary.length > 37
|
|
83
|
+
? ticket.summary.slice(0, 34) + '...'
|
|
84
|
+
: ticket.summary;
|
|
85
|
+
const branch = ticket.branch.length > 27
|
|
86
|
+
? '...' + ticket.branch.slice(-24)
|
|
87
|
+
: ticket.branch;
|
|
88
|
+
table.push([
|
|
89
|
+
ticket.ticketId,
|
|
90
|
+
statusText,
|
|
91
|
+
priorityText,
|
|
92
|
+
summary,
|
|
93
|
+
confidenceText,
|
|
94
|
+
branch
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
this.log('\n' + table.toString());
|
|
98
|
+
this.log(`\nShowing ${response.tickets.length} ticket(s)`);
|
|
99
|
+
if (response.nextToken) {
|
|
100
|
+
this.log(chalk.dim('More tickets available. Use --limit to fetch more.'));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
this.error(`Failed to list tickets: ${message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
formatConfidence(confidence) {
|
|
109
|
+
if (confidence >= 90) {
|
|
110
|
+
return chalk.green(`${confidence}%`);
|
|
111
|
+
}
|
|
112
|
+
if (confidence >= 70) {
|
|
113
|
+
return chalk.yellow(`${confidence}%`);
|
|
114
|
+
}
|
|
115
|
+
if (confidence >= 50) {
|
|
116
|
+
return chalk.hex('#FFA500')(`${confidence}%`); // Orange
|
|
117
|
+
}
|
|
118
|
+
return chalk.red(`${confidence}%`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class TicketSync extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
ticketId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
direction: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
protected get requiresInit(): boolean;
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { TicketService } from '../../services/ticket.service.js';
|
|
6
|
+
export default class TicketSync extends BaseCommand {
|
|
7
|
+
static args = {
|
|
8
|
+
ticketId: Args.string({
|
|
9
|
+
description: 'ticket ID to sync',
|
|
10
|
+
name: 'ticketId',
|
|
11
|
+
required: true
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
static description = 'Sync ticket state with external source (JIRA, GitHub, etc.)';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234 --direction push',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> PROJ-1234 --direction pull'
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
direction: Flags.string({
|
|
22
|
+
char: 'd',
|
|
23
|
+
default: 'push',
|
|
24
|
+
description: 'sync direction (push: gut -> source, pull: source -> gut)',
|
|
25
|
+
options: ['push', 'pull']
|
|
26
|
+
}),
|
|
27
|
+
json: Flags.boolean({
|
|
28
|
+
char: 'j',
|
|
29
|
+
description: 'output as JSON'
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
get requiresInit() {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
async run() {
|
|
36
|
+
const { args, flags } = await this.parse(TicketSync);
|
|
37
|
+
const ticketService = new TicketService(this.configService);
|
|
38
|
+
if (!ticketService.isConfigured()) {
|
|
39
|
+
this.error('API not configured. Set GUT_API_ENDPOINT and GUT_TENANT_ID environment variables.');
|
|
40
|
+
}
|
|
41
|
+
const direction = flags.direction;
|
|
42
|
+
const directionEmoji = direction === 'push' ? '⬆️' : '⬇️';
|
|
43
|
+
const directionText = direction === 'push'
|
|
44
|
+
? 'gut → source'
|
|
45
|
+
: 'source → gut';
|
|
46
|
+
const spinner = ora(`${directionEmoji} Syncing ticket (${directionText})...`).start();
|
|
47
|
+
try {
|
|
48
|
+
const response = await ticketService.syncTicket(args.ticketId, direction);
|
|
49
|
+
if (flags.json) {
|
|
50
|
+
spinner.stop();
|
|
51
|
+
this.log(JSON.stringify(response, null, 2));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (response.synced) {
|
|
55
|
+
spinner.succeed('Sync completed');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
spinner.warn('Sync completed with warnings');
|
|
59
|
+
}
|
|
60
|
+
this.log('');
|
|
61
|
+
this.log(chalk.bold(`📋 Ticket: ${response.ticketId}`));
|
|
62
|
+
this.log(` 🔗 Source: ${response.source.type}`);
|
|
63
|
+
this.log(` 🌐 URL: ${response.source.externalUrl}`);
|
|
64
|
+
if (response.actions.length > 0) {
|
|
65
|
+
this.log('');
|
|
66
|
+
this.log(chalk.bold('📝 Actions performed:'));
|
|
67
|
+
for (const action of response.actions) {
|
|
68
|
+
const isError = action.toLowerCase().includes('error');
|
|
69
|
+
const icon = isError ? chalk.red('✗') : chalk.green('✓');
|
|
70
|
+
this.log(` ${icon} ${action}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.log('');
|
|
75
|
+
this.log(chalk.dim(' No actions performed'));
|
|
76
|
+
}
|
|
77
|
+
this.log('');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
spinner.fail('Sync failed');
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
this.error(`Failed to sync ticket: ${message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class TicketUpdate extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
ticketId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
confidence: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
phase: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
protected get requiresInit(): boolean;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
private formatConfidence;
|
|
17
|
+
}
|