Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions src/commands/cleanup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,23 @@ describe('CleanupCommand', () => {
})

expect(logger.info).toHaveBeenCalledWith('Cleanup mode: issue')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #42...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #42...')
})

it('should handle issue mode with number 1', async () => {
await command.execute({
options: { issue: 1 }
})

expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #1...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #1...')
})

it('should handle issue mode with large number', async () => {
await command.execute({
options: { issue: 999 }
})

expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #999...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #999...')
})
})

Expand All @@ -157,7 +157,7 @@ describe('CleanupCommand', () => {
})

expect(logger.info).toHaveBeenCalledWith('Cleanup mode: issue')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #42...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #42...')
})

it('should detect "123" as issue number', async () => {
Expand All @@ -166,7 +166,7 @@ describe('CleanupCommand', () => {
options: {}
})

expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #123...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #123...')
})

it('should detect "1" as issue number', async () => {
Expand All @@ -175,7 +175,7 @@ describe('CleanupCommand', () => {
options: {}
})

expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #1...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #1...')
})

it('should detect "0" as issue number (edge case)', async () => {
Expand All @@ -184,7 +184,7 @@ describe('CleanupCommand', () => {
options: {}
})

expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #0...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #0...')
})

it('should parse numeric string to integer correctly', async () => {
Expand All @@ -194,7 +194,7 @@ describe('CleanupCommand', () => {
})

// Should parse as integer 7, not string "007"
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #7...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #7...')
})
})

Expand Down Expand Up @@ -223,7 +223,7 @@ describe('CleanupCommand', () => {
})

// Should use explicit issue flag (99), not auto-detected (42)
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #99...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #99...')
})
})

Expand Down Expand Up @@ -420,7 +420,7 @@ describe('CleanupCommand', () => {
})

// Should parse to integer 7
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #7...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #7...')
})


Expand Down Expand Up @@ -1029,7 +1029,7 @@ describe('CleanupCommand', () => {
options: { issue: 25 }
})

expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to GitHub issue/PR #25...')
expect(logger.info).toHaveBeenCalledWith('Finding worktrees related to issue/PR #25...')
expect(logger.info).toHaveBeenCalledWith('Found 2 worktree(s) related to issue/PR #25:')
})

Expand Down Expand Up @@ -1057,7 +1057,7 @@ describe('CleanupCommand', () => {
options: { issue: 25 }
})

expect(logger.warn).toHaveBeenCalledWith('No worktrees found for GitHub issue/PR #25')
expect(logger.warn).toHaveBeenCalledWith('No worktrees found for issue/PR #25')
})

it('should handle no matching worktrees found', async () => {
Expand All @@ -1068,7 +1068,7 @@ describe('CleanupCommand', () => {
options: { issue: 99999 }
})

expect(logger.warn).toHaveBeenCalledWith('No worktrees found for GitHub issue/PR #99999')
expect(logger.warn).toHaveBeenCalledWith('No worktrees found for issue/PR #99999')
expect(logger.info).toHaveBeenCalledWith('Searched for worktree paths containing: 99999, _pr_99999, issue-99999, etc.')
})

Expand Down
16 changes: 12 additions & 4 deletions src/commands/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DatabaseManager } from '../lib/DatabaseManager.js'
import { EnvironmentManager } from '../lib/EnvironmentManager.js'
import { CLIIsolationManager } from '../lib/CLIIsolationManager.js'
import { SettingsManager } from '../lib/SettingsManager.js'
import { IssueTrackerFactory } from '../lib/IssueTrackerFactory.js'
import { promptConfirmation } from '../utils/prompt.js'
import { IdentifierParser } from '../utils/IdentifierParser.js'
import { loadEnvIntoProcess } from '../utils/env.js'
Expand Down Expand Up @@ -47,6 +48,7 @@ export class CleanupCommand {
private readonly gitWorktreeManager: GitWorktreeManager
private resourceCleanup?: ResourceCleanup
private loomManager?: import('../lib/LoomManager.js').LoomManager
private settings?: import('../lib/SettingsManager.js').IloomSettings
private readonly identifierParser: IdentifierParser

constructor(
Expand Down Expand Up @@ -84,6 +86,7 @@ export class CleanupCommand {

const settingsManager = new SettingsManager()
const settings = await settingsManager.loadSettings()
this.settings = settings
const databaseUrlEnvVarName = settings.capabilities?.database?.databaseUrlEnvVarName ?? 'DATABASE_URL'

const environmentManager = new EnvironmentManager()
Expand Down Expand Up @@ -462,7 +465,12 @@ export class CleanupCommand {

const { force, dryRun } = parsed.options

getLogger().info(`Finding worktrees related to GitHub issue/PR #${issueNumber}...`)
// Use settings from ensureResourceCleanup (called via checkForChildLooms in execute())
// to get issue tracker provider type for formatting
const providerType = this.settings ? IssueTrackerFactory.getProviderName(this.settings) : 'github'
const formattedId = IssueTrackerFactory.formatIssueId(providerType, issueNumber)

getLogger().info(`Finding worktrees related to issue/PR ${formattedId}...`)

// Step 1: Get all worktrees and filter by path pattern
const worktrees = await this.gitWorktreeManager.listWorktrees()
Expand All @@ -479,7 +487,7 @@ export class CleanupCommand {
})

if (matchingWorktrees.length === 0) {
getLogger().warn(`No worktrees found for GitHub issue/PR #${issueNumber}`)
getLogger().warn(`No worktrees found for issue/PR ${formattedId}`)
getLogger().info(`Searched for worktree paths containing: ${issueNumber}, _pr_${issueNumber}, issue-${issueNumber}, etc.`)
return {
identifier: String(issueNumber),
Expand All @@ -500,7 +508,7 @@ export class CleanupCommand {
}))

// Step 3: Display preview
getLogger().info(`Found ${targets.length} worktree(s) related to issue/PR #${issueNumber}:`)
getLogger().info(`Found ${targets.length} worktree(s) related to issue/PR ${formattedId}:`)
for (const target of targets) {
getLogger().info(` Branch: ${target.branchName} (${target.worktreePath})`)
}
Expand Down Expand Up @@ -592,7 +600,7 @@ export class CleanupCommand {
}

// Step 7: Report statistics
getLogger().success(`Completed cleanup for issue/PR #${issueNumber}:`)
getLogger().success(`Completed cleanup for issue/PR ${formattedId}:`)
getLogger().info(` Worktrees removed: ${worktreesRemoved}`)
getLogger().info(` Branches deleted: ${branchesDeleted}`)
if (databaseBranchesDeletedList.length > 0) {
Expand Down
13 changes: 9 additions & 4 deletions src/commands/commit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ vi.mock('../lib/GitWorktreeManager.js')
vi.mock('../lib/SettingsManager.js')
vi.mock('../lib/MetadataManager.js')
vi.mock('../lib/ValidationRunner.js')
vi.mock('../mcp/IssueManagementProviderFactory.js')
vi.mock('../lib/IssueTrackerFactory.js', () => ({
IssueTrackerFactory: {
formatIssueId: vi.fn((provider: string, id: string | number) => {
if (provider === 'github') return `#${id}`
return String(id).toUpperCase()
}),
},
}))
vi.mock('../utils/git.js', () => ({
isValidGitRepo: vi.fn(),
getWorktreeRoot: vi.fn(),
Expand All @@ -23,7 +30,6 @@ vi.mock('../utils/git.js', () => ({

// Import mocked functions
import { isValidGitRepo, getWorktreeRoot, extractIssueNumber } from '../utils/git.js'
import { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'

describe('CommitCommand', () => {
let command: CommitCommand
Expand Down Expand Up @@ -63,8 +69,7 @@ describe('CommitCommand', () => {
originalIloomEnv = process.env.ILOOM
delete process.env.ILOOM

// Mock IssueManagementProviderFactory
vi.mocked(IssueManagementProviderFactory.create).mockReturnValue({ issuePrefix: '#' } as ReturnType<typeof IssueManagementProviderFactory.create>)
// IssueTrackerFactory.formatIssueId is already mocked via vi.mock above

// Create mock CommitManager
mockCommitManager = {
Expand Down
12 changes: 7 additions & 5 deletions src/commands/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CommitManager } from '../lib/CommitManager.js'
import { SettingsManager } from '../lib/SettingsManager.js'
import { MetadataManager } from '../lib/MetadataManager.js'
import { ValidationRunner } from '../lib/ValidationRunner.js'
import { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'
import { IssueTrackerFactory, type IssueTrackerProviderType } from '../lib/IssueTrackerFactory.js'
import { getLogger } from '../utils/logger-context.js'
import { extractIssueNumber, isValidGitRepo, getWorktreeRoot } from '../utils/git.js'
import type { CommitOptions } from '../types/index.js'
Expand Down Expand Up @@ -131,10 +131,9 @@ export class CommitCommand {
validationPassed = true
}

// Step 6: Load settings to get issue prefix
// Step 6: Load settings to get provider type for issue formatting
const settings = await this.settingsManager.loadSettings(worktreePath)
const providerType = settings.issueManagement?.provider ?? 'github'
const issuePrefix = IssueManagementProviderFactory.create(providerType).issuePrefix
const providerType = (settings.issueManagement?.provider ?? 'github') as IssueTrackerProviderType

// Determine whether to skip pre-commit hooks:
// - With --wip-commit: always skip hooks (quick WIP commit)
Expand All @@ -146,14 +145,17 @@ export class CommitCommand {
let commitMessage: string | undefined = input.message
if (input.wipCommit && !input.message) {
if (detected.issueNumber !== undefined) {
commitMessage = `WIP commit for Issue ${issuePrefix}${detected.issueNumber}`
commitMessage = `WIP commit for Issue ${IssueTrackerFactory.formatIssueId(providerType, detected.issueNumber)}`
} else {
commitMessage = 'WIP commit'
}
logger.debug(`Using hardcoded WIP message: ${commitMessage}`)
}

// Step 8: Build commit options
// Derive issuePrefix for CommitManager's downstream contract
// formatIssueId with empty string yields just the prefix (e.g., '#' for GitHub, '' for Linear)
const issuePrefix = IssueTrackerFactory.formatIssueId(providerType, '')
const commitOptions: CommitOptions = {
issuePrefix,
skipVerify: shouldSkipVerify,
Expand Down
1 change: 1 addition & 0 deletions src/commands/enhance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('EnhanceCommand', () => {
fetchIssue: vi.fn(),
getIssueUrl: vi.fn(),
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

// Create mock IssueEnhancementService
Expand Down
4 changes: 2 additions & 2 deletions src/commands/enhance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class EnhanceCommand {

// Step 2: Fetch issue to verify it exists
if (!isJsonMode) {
getLogger().info(`Fetching issue #${issueNumber}...`)
getLogger().info(`Fetching issue ${this.issueTracker.formatIssueId(issueNumber)}...`)
}
const issue = await this.issueTracker.fetchIssue(issueNumber, repo)
getLogger().debug('Issue fetched successfully', { number: issue.number, title: issue.title })
Expand Down Expand Up @@ -109,7 +109,7 @@ export class EnhanceCommand {
return
}

getLogger().success(`Issue #${issueNumber} enhanced successfully!`)
getLogger().success(`Issue ${this.issueTracker.formatIssueId(issueNumber)} enhanced successfully!`)
getLogger().info(`Enhanced specification available at: ${result.url}`)

// Prompt to open browser (unless --no-browser flag is set)
Expand Down
7 changes: 7 additions & 0 deletions src/commands/finish.pr-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe('FinishCommand - PR State Detection', () => {
fetchIssue: vi.fn(),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down Expand Up @@ -247,6 +248,7 @@ describe('FinishCommand - Open PR Workflow', () => {
fetchIssue: vi.fn(),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down Expand Up @@ -455,6 +457,7 @@ describe('FinishCommand - Child Loom GitHub PR Workflow', () => {
fetchIssue: vi.fn().mockResolvedValue(mockIssue),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down Expand Up @@ -586,6 +589,7 @@ describe('FinishCommand - Closed PR Workflow', () => {
fetchIssue: vi.fn(),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down Expand Up @@ -789,6 +793,7 @@ describe('FinishCommand - Merged PR Workflow', () => {
fetchIssue: vi.fn(),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down Expand Up @@ -883,6 +888,7 @@ describe('FinishCommand - Dry-Run Mode for PRs', () => {
fetchIssue: vi.fn(),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down Expand Up @@ -1037,6 +1043,7 @@ describe('FinishCommand - Issue Workflow (Regression Tests)', () => {
fetchIssue: vi.fn().mockResolvedValue(mockIssue),
supportsPullRequests: true,
providerName: 'github',
formatIssueId: vi.fn((id: string | number) => `#${id}`),
} as unknown as GitHubService

mockGitWorktreeManager = {
Expand Down
2 changes: 2 additions & 0 deletions src/commands/finish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ describe('FinishCommand', () => {
// Set IssueTracker interface properties
mockGitHubService.supportsPullRequests = true
mockGitHubService.providerName = 'github'
// Mock formatIssueId to return GitHub-style formatting
vi.mocked(mockGitHubService.formatIssueId).mockImplementation((id) => `#${id}`)
mockGitWorktreeManager = new GitWorktreeManager()
mockValidationRunner = new ValidationRunner()
mockCommitManager = new CommitManager()
Expand Down
4 changes: 2 additions & 2 deletions src/commands/finish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ export class FinishCommand {
// Validate issue state (warn if closed unless --force)
if (issue.state === 'closed' && !options.force) {
throw new Error(
`Issue #${parsed.number} is closed. Use --force to finish anyway.`
`Issue ${this.issueTracker.formatIssueId(parsed.number ?? 0)} is closed. Use --force to finish anyway.`
)
}

Expand Down Expand Up @@ -581,7 +581,7 @@ export class FinishCommand {
case 'pr':
return `PR #${parsed.number}${autoLabel}`
case 'issue':
return `Issue #${parsed.number}${autoLabel}`
return `Issue ${this.issueTracker.formatIssueId(parsed.number ?? 0)}${autoLabel}`
case 'branch':
return `Branch '${parsed.branchName}'${autoLabel}`
default:
Expand Down
2 changes: 2 additions & 0 deletions src/commands/ignite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,10 @@ export class IgniteCommand {
draftPrNumber?: number,
draftPrUrl?: string
): TemplateVariables {
const providerType = this.settings ? IssueTrackerFactory.getProviderName(this.settings) : 'github'
const variables: TemplateVariables = {
WORKSPACE_PATH: context.workspacePath,
ISSUE_PREFIX: IssueTrackerFactory.getIssuePrefix(providerType),
}

if (context.issueNumber !== undefined) {
Expand Down
4 changes: 4 additions & 0 deletions src/commands/plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ vi.mock('../lib/SettingsManager.js', () => ({
vi.mock('../lib/IssueTrackerFactory.js', () => ({
IssueTrackerFactory: {
getProviderName: vi.fn().mockReturnValue('github'),
formatIssueId: vi.fn((provider: string, id: string | number) => {
if (provider === 'github') return `#${id}`
return String(id).toUpperCase()
}),
},
}))
vi.mock('../utils/logger.js', () => ({
Expand Down
Loading