takt: fix-pr-issue-number
This commit is contained in:
parent
52c927e6f1
commit
73db206c9a
@ -10,7 +10,7 @@ import { buildPrBody } from '../infra/github/pr.js';
|
|||||||
import type { GitHubIssue } from '../infra/github/types.js';
|
import type { GitHubIssue } from '../infra/github/types.js';
|
||||||
|
|
||||||
describe('buildPrBody', () => {
|
describe('buildPrBody', () => {
|
||||||
it('should build body with issue and report', () => {
|
it('should build body with single issue and report', () => {
|
||||||
const issue: GitHubIssue = {
|
const issue: GitHubIssue = {
|
||||||
number: 99,
|
number: 99,
|
||||||
title: 'Add login feature',
|
title: 'Add login feature',
|
||||||
@ -19,7 +19,7 @@ describe('buildPrBody', () => {
|
|||||||
comments: [],
|
comments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = buildPrBody(issue, 'Piece `default` completed.');
|
const result = buildPrBody([issue], 'Piece `default` completed.');
|
||||||
|
|
||||||
expect(result).toContain('## Summary');
|
expect(result).toContain('## Summary');
|
||||||
expect(result).toContain('Implement username/password authentication.');
|
expect(result).toContain('Implement username/password authentication.');
|
||||||
@ -37,7 +37,7 @@ describe('buildPrBody', () => {
|
|||||||
comments: [],
|
comments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = buildPrBody(issue, 'Done.');
|
const result = buildPrBody([issue], 'Done.');
|
||||||
|
|
||||||
expect(result).toContain('Fix bug');
|
expect(result).toContain('Fix bug');
|
||||||
expect(result).toContain('Closes #10');
|
expect(result).toContain('Closes #10');
|
||||||
@ -51,4 +51,30 @@ describe('buildPrBody', () => {
|
|||||||
expect(result).toContain('Task completed.');
|
expect(result).toContain('Task completed.');
|
||||||
expect(result).not.toContain('Closes');
|
expect(result).not.toContain('Closes');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support multiple issues', () => {
|
||||||
|
const issues: GitHubIssue[] = [
|
||||||
|
{
|
||||||
|
number: 1,
|
||||||
|
title: 'First issue',
|
||||||
|
body: 'First issue body.',
|
||||||
|
labels: [],
|
||||||
|
comments: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: 2,
|
||||||
|
title: 'Second issue',
|
||||||
|
body: 'Second issue body.',
|
||||||
|
labels: [],
|
||||||
|
comments: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = buildPrBody(issues, 'Done.');
|
||||||
|
|
||||||
|
expect(result).toContain('## Summary');
|
||||||
|
expect(result).toContain('First issue body.');
|
||||||
|
expect(result).toContain('Closes #1');
|
||||||
|
expect(result).toContain('Closes #2');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import { info, error } from '../../shared/ui/index.js';
|
import { info, error } from '../../shared/ui/index.js';
|
||||||
import { getErrorMessage } from '../../shared/utils/index.js';
|
import { getErrorMessage } from '../../shared/utils/index.js';
|
||||||
import { resolveIssueTask } from '../../infra/github/index.js';
|
import { fetchIssue, formatIssueAsTask, checkGhCli, parseIssueNumbers } from '../../infra/github/index.js';
|
||||||
import { selectAndExecuteTask, determinePiece, saveTaskFromInteractive, createIssueFromTask, type SelectAndExecuteOptions } from '../../features/tasks/index.js';
|
import { selectAndExecuteTask, determinePiece, saveTaskFromInteractive, createIssueFromTask, type SelectAndExecuteOptions } from '../../features/tasks/index.js';
|
||||||
import { executePipeline } from '../../features/pipeline/index.js';
|
import { executePipeline } from '../../features/pipeline/index.js';
|
||||||
import { interactiveMode } from '../../features/interactive/index.js';
|
import { interactiveMode } from '../../features/interactive/index.js';
|
||||||
@ -65,7 +65,13 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
|||||||
const issueFromOption = opts.issue as number | undefined;
|
const issueFromOption = opts.issue as number | undefined;
|
||||||
if (issueFromOption) {
|
if (issueFromOption) {
|
||||||
try {
|
try {
|
||||||
const resolvedTask = resolveIssueTask(`#${issueFromOption}`);
|
const ghStatus = checkGhCli();
|
||||||
|
if (!ghStatus.available) {
|
||||||
|
throw new Error(ghStatus.error);
|
||||||
|
}
|
||||||
|
const issue = fetchIssue(issueFromOption);
|
||||||
|
const resolvedTask = formatIssueAsTask(issue);
|
||||||
|
selectOptions.issues = [issue];
|
||||||
await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides);
|
await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(getErrorMessage(e));
|
error(getErrorMessage(e));
|
||||||
@ -75,17 +81,27 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (task && isDirectTask(task)) {
|
if (task && isDirectTask(task)) {
|
||||||
// isDirectTask() returns true only for issue references
|
// isDirectTask() returns true only for issue references (e.g., "#6" or "#1 #2")
|
||||||
let resolvedTask: string;
|
|
||||||
try {
|
try {
|
||||||
info('Fetching GitHub Issue...');
|
info('Fetching GitHub Issue...');
|
||||||
resolvedTask = resolveIssueTask(task);
|
const ghStatus = checkGhCli();
|
||||||
|
if (!ghStatus.available) {
|
||||||
|
throw new Error(ghStatus.error);
|
||||||
|
}
|
||||||
|
// Parse all issue numbers from task (supports "#6" and "#1 #2")
|
||||||
|
const tokens = task.trim().split(/\s+/);
|
||||||
|
const issueNumbers = parseIssueNumbers(tokens);
|
||||||
|
if (issueNumbers.length === 0) {
|
||||||
|
throw new Error(`Invalid issue reference: ${task}`);
|
||||||
|
}
|
||||||
|
const issues = issueNumbers.map((n) => fetchIssue(n));
|
||||||
|
const resolvedTask = issues.map(formatIssueAsTask).join('\n\n---\n\n');
|
||||||
|
selectOptions.issues = issues;
|
||||||
|
await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(getErrorMessage(e));
|
error(getErrorMessage(e));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -96,7 +96,7 @@ function buildPipelinePrBody(
|
|||||||
report,
|
report,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return buildPrBody(issue, report);
|
return buildPrBody(issue ? [issue] : undefined, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -168,7 +168,7 @@ export async function selectAndExecuteTask(
|
|||||||
const shouldCreatePr = options?.autoPr === true || await confirm('Create pull request?', false);
|
const shouldCreatePr = options?.autoPr === true || await confirm('Create pull request?', false);
|
||||||
if (shouldCreatePr) {
|
if (shouldCreatePr) {
|
||||||
info('Creating pull request...');
|
info('Creating pull request...');
|
||||||
const prBody = buildPrBody(undefined, `Piece \`${pieceIdentifier}\` completed successfully.`);
|
const prBody = buildPrBody(options?.issues, `Piece \`${pieceIdentifier}\` completed successfully.`);
|
||||||
const prResult = createPullRequest(execCwd, {
|
const prResult = createPullRequest(execCwd, {
|
||||||
branch,
|
branch,
|
||||||
title: task.length > 100 ? `${task.slice(0, 97)}...` : task,
|
title: task.length > 100 ? `${task.slice(0, 97)}...` : task,
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import type { Language } from '../../../core/models/index.js';
|
import type { Language } from '../../../core/models/index.js';
|
||||||
import type { ProviderType } from '../../../infra/providers/index.js';
|
import type { ProviderType } from '../../../infra/providers/index.js';
|
||||||
|
import type { GitHubIssue } from '../../../infra/github/index.js';
|
||||||
|
|
||||||
/** Result of piece execution */
|
/** Result of piece execution */
|
||||||
export interface PieceExecutionResult {
|
export interface PieceExecutionResult {
|
||||||
@ -93,4 +94,6 @@ export interface SelectAndExecuteOptions {
|
|||||||
interactiveUserInput?: boolean;
|
interactiveUserInput?: boolean;
|
||||||
/** Interactive mode result metadata for NDJSON logging */
|
/** Interactive mode result metadata for NDJSON logging */
|
||||||
interactiveMetadata?: InteractiveMetadata;
|
interactiveMetadata?: InteractiveMetadata;
|
||||||
|
/** GitHub Issues to associate with the PR (adds "Closes #N" for each issue) */
|
||||||
|
issues?: GitHubIssue[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,15 +70,17 @@ export function createPullRequest(cwd: string, options: CreatePrOptions): Create
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build PR body from issue and execution report.
|
* Build PR body from issues and execution report.
|
||||||
|
* Supports multiple issues (adds "Closes #N" for each).
|
||||||
*/
|
*/
|
||||||
export function buildPrBody(issue: GitHubIssue | undefined, report: string): string {
|
export function buildPrBody(issues: GitHubIssue[] | undefined, report: string): string {
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
|
|
||||||
parts.push('## Summary');
|
parts.push('## Summary');
|
||||||
if (issue) {
|
if (issues && issues.length > 0) {
|
||||||
parts.push('');
|
parts.push('');
|
||||||
parts.push(issue.body || issue.title);
|
// Use the first issue's body/title for summary
|
||||||
|
parts.push(issues[0]!.body || issues[0]!.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
parts.push('');
|
parts.push('');
|
||||||
@ -86,9 +88,9 @@ export function buildPrBody(issue: GitHubIssue | undefined, report: string): str
|
|||||||
parts.push('');
|
parts.push('');
|
||||||
parts.push(report);
|
parts.push(report);
|
||||||
|
|
||||||
if (issue) {
|
if (issues && issues.length > 0) {
|
||||||
parts.push('');
|
parts.push('');
|
||||||
parts.push(`Closes #${issue.number}`);
|
parts.push(issues.map((issue) => `Closes #${issue.number}`).join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join('\n');
|
return parts.join('\n');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user