Merge branch 'takt/#112/add-closes-issue-to-pr' into develop
This commit is contained in:
commit
e3be883a7f
@ -10,7 +10,7 @@ import { buildPrBody } from '../infra/github/pr.js';
|
||||
import type { GitHubIssue } from '../infra/github/types.js';
|
||||
|
||||
describe('buildPrBody', () => {
|
||||
it('should build body with issue and report', () => {
|
||||
it('should build body with single issue and report', () => {
|
||||
const issue: GitHubIssue = {
|
||||
number: 99,
|
||||
title: 'Add login feature',
|
||||
@ -19,7 +19,7 @@ describe('buildPrBody', () => {
|
||||
comments: [],
|
||||
};
|
||||
|
||||
const result = buildPrBody(issue, 'Piece `default` completed.');
|
||||
const result = buildPrBody([issue], 'Piece `default` completed.');
|
||||
|
||||
expect(result).toContain('## Summary');
|
||||
expect(result).toContain('Implement username/password authentication.');
|
||||
@ -37,7 +37,7 @@ describe('buildPrBody', () => {
|
||||
comments: [],
|
||||
};
|
||||
|
||||
const result = buildPrBody(issue, 'Done.');
|
||||
const result = buildPrBody([issue], 'Done.');
|
||||
|
||||
expect(result).toContain('Fix bug');
|
||||
expect(result).toContain('Closes #10');
|
||||
@ -51,4 +51,30 @@ describe('buildPrBody', () => {
|
||||
expect(result).toContain('Task completed.');
|
||||
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 { 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 { executePipeline } from '../../features/pipeline/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;
|
||||
if (issueFromOption) {
|
||||
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);
|
||||
} catch (e) {
|
||||
error(getErrorMessage(e));
|
||||
@ -75,17 +81,27 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
}
|
||||
|
||||
if (task && isDirectTask(task)) {
|
||||
// isDirectTask() returns true only for issue references
|
||||
let resolvedTask: string;
|
||||
// isDirectTask() returns true only for issue references (e.g., "#6" or "#1 #2")
|
||||
try {
|
||||
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) {
|
||||
error(getErrorMessage(e));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -96,7 +96,7 @@ function buildPipelinePrBody(
|
||||
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);
|
||||
if (shouldCreatePr) {
|
||||
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, {
|
||||
branch,
|
||||
title: task.length > 100 ? `${task.slice(0, 97)}...` : task,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import type { Language } from '../../../core/models/index.js';
|
||||
import type { ProviderType } from '../../../infra/providers/index.js';
|
||||
import type { GitHubIssue } from '../../../infra/github/index.js';
|
||||
|
||||
/** Result of piece execution */
|
||||
export interface PieceExecutionResult {
|
||||
@ -93,4 +94,6 @@ export interface SelectAndExecuteOptions {
|
||||
interactiveUserInput?: boolean;
|
||||
/** Interactive mode result metadata for NDJSON logging */
|
||||
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[] = [];
|
||||
|
||||
parts.push('## Summary');
|
||||
if (issue) {
|
||||
if (issues && issues.length > 0) {
|
||||
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('');
|
||||
@ -86,9 +88,9 @@ export function buildPrBody(issue: GitHubIssue | undefined, report: string): str
|
||||
parts.push('');
|
||||
parts.push(report);
|
||||
|
||||
if (issue) {
|
||||
if (issues && issues.length > 0) {
|
||||
parts.push('');
|
||||
parts.push(`Closes #${issue.number}`);
|
||||
parts.push(issues.map((issue) => `Closes #${issue.number}`).join('\n'));
|
||||
}
|
||||
|
||||
return parts.join('\n');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user