UNPKG

@morodomi/ait3

Version:

AIT³ Development Platform - AI + Ticket + Test + Tool driven development methodology

102 lines (101 loc) 3.57 kB
import { SlugUtils } from './utils.js'; import { STYLES } from './styles.js'; import { TicketNotFoundError, ValidationError } from './errors.js'; import { FLOW_MESSAGES } from './flow-messages.js'; import { formatTicketDisplay } from './utils/location-utils.js'; /** * Generate ticket file location based on status and ticket info * For local tickets: returns file path * For GitHub tickets: returns URL */ export function getTicketLocation(ticketId, title, status = 'doing', ticket) { // If we have a ticket object with location, use that if (ticket?.location) { return ticket.location.path || ticket.location.url || ''; } // Otherwise, generate local path (backward compatibility) // Normalize ticket ID to remove # prefix for GitHub IDs const normalizedId = ticketId.replace(/^#/, ''); const ticketSlug = SlugUtils.titleToSlug(title); return `.tickets/${status}/${normalizedId}-${ticketSlug}.md`; } /** * Generate git commit message for flow phases */ export function generateCommitMessage(phase, ticketId, title) { const lowerTitle = title.toLowerCase(); switch (phase) { case 'planning': return `planning(#${ticketId}): ${lowerTitle} design`; case 'test': return `test(#${ticketId}): comprehensive test suite for ${lowerTitle}`; case 'feat': return `feat(#${ticketId}): implement ${lowerTitle}`; case 'refactor': return `refactor(#${ticketId}): optimize ${lowerTitle} implementation`; default: return `${phase}(#${ticketId}): ${lowerTitle}`; } } /** * Generate formatted ticket location header for flow commands */ export function formatTicketHeader(ticketId, title, phase, ticket, services) { let locationDisplay = ''; if (ticket && services) { locationDisplay = formatTicketDisplay(ticket, services.ticketService); } else { // Fallback to old behavior locationDisplay = getTicketLocation(ticketId, title, 'doing'); } return `${STYLES.bold(phase)} for Ticket #${ticketId}: ${title}\n${STYLES.info('LOCATION')}: ${STYLES.info(locationDisplay)}`; } /** * Generate commit action for Next Action sections */ export function formatCommitAction(phase, ticketId, title) { const commitMessage = generateCommitMessage(phase, ticketId, title); return `└─ Commit ${phase}:\n └─ ${STYLES.code(`git commit -m "${commitMessage}"`)}`; } /** * Get a ticket by ID or throw TicketNotFoundError */ export async function getTicketOrThrow(ticketId, services) { try { const ticket = await services.ticketService.getTicket(ticketId); if (!ticket) { throw new TicketNotFoundError(ticketId); } return ticket; } catch (error) { if (error instanceof TicketNotFoundError) { throw error; } throw new TicketNotFoundError(ticketId); } } /** * Validate that a ticket is not completed */ export function validateNotCompleted(ticket) { if (ticket.status === 'done') { throw new ValidationError(FLOW_MESSAGES.TICKET_ALREADY_COMPLETED(ticket.id)); } } /** * Validate that a ticket is in progress */ export function validateInProgress(ticket) { if (ticket.status !== 'doing') { throw new ValidationError(FLOW_MESSAGES.TICKET_NOT_IN_PROGRESS(ticket.id)); } } /** * Validate that a ticket is in progress and not completed */ export function validateTicketForFlow(ticket) { validateNotCompleted(ticket); validateInProgress(ticket); }