claude-teammate 0.1.183 → 0.1.185
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/package.json +1 -1
- package/src/claude/prompts.js +80 -0
- package/src/claude.js +66 -0
- package/src/worker/jira-issue-support.js +241 -0
- package/src/worker/jira-issue-workflow.js +28 -340
- package/src/worker/jira-no-code.js +142 -0
- package/src/worker/pull-request-workflow-support.js +427 -0
- package/src/worker/pull-request-workflow.js +14 -331
- package/src/worker/pull-request.js +58 -0
package/package.json
CHANGED
package/src/claude/prompts.js
CHANGED
|
@@ -349,6 +349,86 @@ Instructions:
|
|
|
349
349
|
- If blocked, return result=stuck with that summary explaining why it did not work.`;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
export function buildGitHubPRPlanBreakdownSystemPrompt() {
|
|
353
|
+
return [
|
|
354
|
+
"You are breaking a large GitHub pull request implementation plan into a sequenced list of small, atomic steps.",
|
|
355
|
+
"Each step must be self-contained: a single developer can implement it in isolation by reading only that step description.",
|
|
356
|
+
"Steps must be ordered by dependency — steps that others depend on come first.",
|
|
357
|
+
"Each step description must be specific enough to act on without reading the full PR body: name the exact files, classes, functions, or fields to change and what the change is.",
|
|
358
|
+
"Aim for 3-8 steps. Do not produce trivial steps or steps that only say 'test' or 'verify'.",
|
|
359
|
+
"Return only structured output matching the provided schema."
|
|
360
|
+
].join(" ");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function buildGitHubPRPlanBreakdownUserPrompt(input) {
|
|
364
|
+
return `Break this pull request plan into ordered implementation steps.
|
|
365
|
+
|
|
366
|
+
Pull request title:
|
|
367
|
+
${input.pullRequest.title}
|
|
368
|
+
|
|
369
|
+
Pull request description:
|
|
370
|
+
${input.pullRequest.body || "(none)"}
|
|
371
|
+
|
|
372
|
+
Epic memory snapshot:
|
|
373
|
+
${JSON.stringify(input.memory?.epic || {}, null, 2)}
|
|
374
|
+
|
|
375
|
+
Instructions:
|
|
376
|
+
- Produce an ordered list of atomic implementation steps.
|
|
377
|
+
- Each step must be specific enough to implement without reading this full description again: name the exact files, classes, methods, or enum values involved.
|
|
378
|
+
- Steps must be ordered by dependency (earlier steps must not depend on later ones).
|
|
379
|
+
- Aim for 3-8 steps covering the full scope of the plan.
|
|
380
|
+
- Do not include steps for committing, pushing, or testing unless the test is part of the plan.`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function buildGitHubPRImplementationStepSystemPrompt() {
|
|
384
|
+
return [
|
|
385
|
+
"You are implementing a single step of a larger GitHub pull request.",
|
|
386
|
+
"You must work directly in the provided repository path and branch.",
|
|
387
|
+
"If additional accessible repository paths are provided, you may read from them for source context, but only edit the primary repository unless the step explicitly says otherwise.",
|
|
388
|
+
"First ensure the repository is on the provided branch.",
|
|
389
|
+
"Implement only the specific step described — do not implement other parts of the larger plan.",
|
|
390
|
+
"Read the epic memory snapshot before making changes so you can reuse known repo-specific knowledge and avoid repeating past mistakes.",
|
|
391
|
+
"Run only the commands needed to inspect, edit, and test this specific step.",
|
|
392
|
+
"When editing code or markup, smart quotes and em/en dashes are not allowed; replace them if encountered.",
|
|
393
|
+
LANGUAGE_PRESERVATION_RULES,
|
|
394
|
+
"Do not run git add, git commit, or git push. The outer worker will handle all git operations.",
|
|
395
|
+
"If you are blocked on this step, return result=stuck with a concise reason.",
|
|
396
|
+
"Return only structured output matching the provided schema."
|
|
397
|
+
].join(" ");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function buildGitHubPRImplementationStepUserPrompt(input) {
|
|
401
|
+
return `Implement this specific step of a pull request.
|
|
402
|
+
|
|
403
|
+
Pull request number: ${input.pullRequest.number}
|
|
404
|
+
Pull request title:
|
|
405
|
+
${input.pullRequest.title}
|
|
406
|
+
|
|
407
|
+
Repository path:
|
|
408
|
+
${input.repoPath}
|
|
409
|
+
|
|
410
|
+
Additional accessible repository paths:
|
|
411
|
+
${formatAdditionalRepoPaths(input.repoPaths, input.repoPath)}
|
|
412
|
+
|
|
413
|
+
Branch to use:
|
|
414
|
+
${input.branchName}
|
|
415
|
+
|
|
416
|
+
Epic memory snapshot:
|
|
417
|
+
${JSON.stringify(input.memory?.epic || {}, null, 2)}
|
|
418
|
+
|
|
419
|
+
Step to implement:
|
|
420
|
+
${input.step}
|
|
421
|
+
|
|
422
|
+
Instructions:
|
|
423
|
+
- Checkout the branch above in this repository.
|
|
424
|
+
- Implement only the step described above. Do not implement other parts of the plan.
|
|
425
|
+
- Smart quotes and em/en dashes are not allowed. Use: smart quotes -> ", em/en dashes -> -.
|
|
426
|
+
- Do not commit or push; leave modified files in the working tree.
|
|
427
|
+
- Return summary as 1-2 sentences describing what changed in this step.
|
|
428
|
+
- If successful, return result=implemented.
|
|
429
|
+
- If blocked, return result=stuck with a concise reason.`;
|
|
430
|
+
}
|
|
431
|
+
|
|
352
432
|
export function buildGitHubPRCommentReviewSystemPrompt() {
|
|
353
433
|
return [
|
|
354
434
|
"You are triaging a human comment on an existing GitHub pull request.",
|
package/src/claude.js
CHANGED
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
buildGitHubIssueReviewUserPrompt,
|
|
33
33
|
buildGitHubPRImplementationSystemPrompt,
|
|
34
34
|
buildGitHubPRImplementationUserPrompt,
|
|
35
|
+
buildGitHubPRPlanBreakdownSystemPrompt,
|
|
36
|
+
buildGitHubPRPlanBreakdownUserPrompt,
|
|
37
|
+
buildGitHubPRImplementationStepSystemPrompt,
|
|
38
|
+
buildGitHubPRImplementationStepUserPrompt,
|
|
35
39
|
buildGitHubPRCommentReviewSystemPrompt,
|
|
36
40
|
buildGitHubPRCommentReviewUserPrompt,
|
|
37
41
|
buildGitHubPRReviewSystemPrompt,
|
|
@@ -614,6 +618,68 @@ export async function runClaudeImplementation(input) {
|
|
|
614
618
|
);
|
|
615
619
|
}
|
|
616
620
|
|
|
621
|
+
const GITHUB_PR_PLAN_BREAKDOWN_SCHEMA = {
|
|
622
|
+
type: "object",
|
|
623
|
+
additionalProperties: false,
|
|
624
|
+
properties: {
|
|
625
|
+
steps: {
|
|
626
|
+
type: "array",
|
|
627
|
+
items: { type: "string" },
|
|
628
|
+
minItems: 1
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
required: ["steps"]
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
export async function runClaudeImplementationPlanBreakdown(input) {
|
|
635
|
+
return invokeClaudeTask(
|
|
636
|
+
GITHUB_PR_PLAN_BREAKDOWN_SCHEMA,
|
|
637
|
+
buildGitHubPRPlanBreakdownSystemPrompt(),
|
|
638
|
+
buildGitHubPRPlanBreakdownUserPrompt(input),
|
|
639
|
+
{
|
|
640
|
+
model: input.model,
|
|
641
|
+
permissionMode: "default",
|
|
642
|
+
effort: "low",
|
|
643
|
+
extraArgs: ["--strict-mcp-config", "--tools", ""],
|
|
644
|
+
runOpts: {
|
|
645
|
+
timeout: input.timeoutMs || DEFAULT_TIMEOUT_MS,
|
|
646
|
+
issueKey: input.issueKey,
|
|
647
|
+
logger: input.logger,
|
|
648
|
+
phase: "implementation-plan-breakdown"
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export async function runClaudeImplementationStep(input) {
|
|
655
|
+
if (!input.repoPath) {
|
|
656
|
+
throw new Error("Claude implementation step requires a local repository path.");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
await clearLocalPlaywrightMcpConfig(input.repoPath);
|
|
660
|
+
|
|
661
|
+
return invokeClaudeTask(
|
|
662
|
+
GITHUB_PR_IMPLEMENTATION_SCHEMA,
|
|
663
|
+
buildGitHubPRImplementationStepSystemPrompt(),
|
|
664
|
+
buildGitHubPRImplementationStepUserPrompt(input),
|
|
665
|
+
{
|
|
666
|
+
model: input.model,
|
|
667
|
+
permissionMode: input.permissionMode,
|
|
668
|
+
effort: "medium",
|
|
669
|
+
extraArgs: ["--tools", "Bash,Read,Grep,Glob,Edit,Write"],
|
|
670
|
+
validate: validateGitHubImplementationResult,
|
|
671
|
+
runOpts: {
|
|
672
|
+
cwd: input.repoPath,
|
|
673
|
+
timeout: input.timeoutMs || LONG_TASK_TIMEOUT_MS,
|
|
674
|
+
onSpawn: input.onSpawn,
|
|
675
|
+
issueKey: input.issueKey,
|
|
676
|
+
logger: input.logger,
|
|
677
|
+
phase: "implementation-step"
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
617
683
|
export async function runClaudePullRequestCommentReview(input) {
|
|
618
684
|
if (!input.repoPath) {
|
|
619
685
|
throw new Error("GitHub pull request comment review requires a local repository path.");
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { deriveEpicMemoryPath } from "../memory.js";
|
|
2
|
+
import {
|
|
3
|
+
buildGitHubIssueBody,
|
|
4
|
+
buildGitHubIssueTitle,
|
|
5
|
+
ensureJiraComment,
|
|
6
|
+
getLatestHumanRepoSelectionReply,
|
|
7
|
+
isRepoSelectionPromptComment,
|
|
8
|
+
selectIssueCreationRepo
|
|
9
|
+
} from "./jira-helpers.js";
|
|
10
|
+
import { dedupeGitHubIssues } from "./forge-sync.js";
|
|
11
|
+
import {
|
|
12
|
+
compareCommentsNewestFirst,
|
|
13
|
+
parseOptionalInt,
|
|
14
|
+
toErrorMessage
|
|
15
|
+
} from "./utils.js";
|
|
16
|
+
|
|
17
|
+
const REPO_SELECTION_HANDLED_PREFIX = "[tm8:repo-selection:handled-comment=";
|
|
18
|
+
|
|
19
|
+
export function hasExpandedJiraDetail(issue) {
|
|
20
|
+
return typeof issue?.descriptionText === "string" && Array.isArray(issue?.comments);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function shouldFetchJiraDetail(issue, { isToDoStatus, isInProgressStatus, isInReviewStatus }) {
|
|
24
|
+
return (
|
|
25
|
+
isToDoStatus(issue?.status) ||
|
|
26
|
+
isInProgressStatus(issue?.status) ||
|
|
27
|
+
isInReviewStatus(issue?.status)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getLatestHumanJiraComment(detail, botUser, jira) {
|
|
32
|
+
const comments = Array.isArray(detail?.comments) ? detail.comments : [];
|
|
33
|
+
return [...comments]
|
|
34
|
+
.filter((comment) => !String(comment?.bodyText || "").startsWith("[Status]"))
|
|
35
|
+
.sort(compareCommentsNewestFirst)
|
|
36
|
+
.find((comment) => !jira.isBotAuthor(comment.author, botUser)) || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function persistJiraCommentMemoryIfNeeded({
|
|
40
|
+
detail,
|
|
41
|
+
latestComment,
|
|
42
|
+
issueMemoryRecord,
|
|
43
|
+
issueMemory,
|
|
44
|
+
epicMemory,
|
|
45
|
+
projectRoot,
|
|
46
|
+
config,
|
|
47
|
+
logger,
|
|
48
|
+
services
|
|
49
|
+
}) {
|
|
50
|
+
if (!latestComment?.id || !String(latestComment.bodyText || "").trim()) {
|
|
51
|
+
return { issueMemory, epicMemory };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (String(issueMemory?.last_jira_memory_comment_id || "") === String(latestComment.id)) {
|
|
55
|
+
return { issueMemory, epicMemory };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
epicMemory = await services.saveMaintainedEpicMemory({
|
|
59
|
+
filePath: deriveEpicMemoryPath(projectRoot, config.JIRA_BASE_URL, detail),
|
|
60
|
+
issue: detail,
|
|
61
|
+
epicMemory,
|
|
62
|
+
projectRoot,
|
|
63
|
+
permissionMode: config.CLAUDE_PERMISSION_MODE,
|
|
64
|
+
timeoutMs: parseOptionalInt(config.CLAUDE_TIMEOUT_MS),
|
|
65
|
+
reason: "Captured durable guidance from latest Jira human comment.",
|
|
66
|
+
candidateUpdates: {
|
|
67
|
+
epic_guardrails: [String(latestComment.bodyText || "").trim()]
|
|
68
|
+
},
|
|
69
|
+
logger
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
issueMemory.last_jira_memory_comment_id = String(latestComment.id);
|
|
73
|
+
issueMemory = await services.saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
74
|
+
return { issueMemory, epicMemory };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function handleJiraCliError({
|
|
78
|
+
error,
|
|
79
|
+
detail,
|
|
80
|
+
jira,
|
|
81
|
+
botUser,
|
|
82
|
+
issueMemoryRecord,
|
|
83
|
+
issueMemory,
|
|
84
|
+
saveMemory,
|
|
85
|
+
logger,
|
|
86
|
+
label,
|
|
87
|
+
jiraCliRetryIntervalMs,
|
|
88
|
+
markClaudeUsageLimit
|
|
89
|
+
}) {
|
|
90
|
+
if (error.hitUsageLimit) {
|
|
91
|
+
markClaudeUsageLimit();
|
|
92
|
+
}
|
|
93
|
+
if (error.promptTooLong) {
|
|
94
|
+
await ensureJiraComment(detail, jira, botUser, "The task description or accumulated context is too large for Claude to process in one go. Please break this task into smaller subtasks or reduce the amount of data referenced.");
|
|
95
|
+
return issueMemory;
|
|
96
|
+
}
|
|
97
|
+
const retryAfter = new Date(Date.now() + jiraCliRetryIntervalMs).toISOString();
|
|
98
|
+
const next = { ...issueMemory, retry_after: retryAfter };
|
|
99
|
+
const saved = await saveMemory(issueMemoryRecord.filePath, detail, next);
|
|
100
|
+
await ensureJiraComment(detail, jira, botUser, "Claude CLI is temporarily unavailable (token limit or API error). This task will be retried automatically in 15 minutes. No action needed.");
|
|
101
|
+
await logger.error(`${label} (CLI error, retry scheduled)`, { issue: detail.key, error });
|
|
102
|
+
return saved;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function formatClarificationQuestions(questions) {
|
|
106
|
+
const cleanedQuestions = questions.filter(Boolean);
|
|
107
|
+
if (cleanedQuestions.length === 0) {
|
|
108
|
+
return "I need a bit more detail before I can plan the implementation.";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return `I need a bit more detail before I can plan the implementation:\n\n${cleanedQuestions
|
|
112
|
+
.map((question) => `- ${question}`)
|
|
113
|
+
.join("\n")}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildRepoSelectionComment(repos) {
|
|
117
|
+
const choices = repos.map((repo) => `- ${repo.url}`).join("\n");
|
|
118
|
+
return `[tm8:repo-selection]\nI found multiple candidate repositories for this task. Please reply with the exact repo URL I should use:\n${choices}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getLatestRepoSelectionPromptComment(detail, botUser, jira) {
|
|
122
|
+
const comments = Array.isArray(detail?.comments) ? [...detail.comments] : [];
|
|
123
|
+
return comments
|
|
124
|
+
.filter((comment) =>
|
|
125
|
+
jira.isBotAuthor(comment?.author, botUser) &&
|
|
126
|
+
isRepoSelectionPromptComment(comment?.bodyText || "")
|
|
127
|
+
)
|
|
128
|
+
.sort(compareCommentsNewestFirst)[0] || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getHandledRepoSelectionCommentId(bodyText) {
|
|
132
|
+
const match = String(bodyText || "").match(/\[tm8:repo-selection:handled-comment=([^\]\s]+)\]/u);
|
|
133
|
+
return String(match?.[1] || "").trim();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function markRepoSelectionReplyHandled(detail, jira, promptComment, repos, handledCommentId) {
|
|
137
|
+
if (!promptComment?.id || typeof jira.updateComment !== "function") {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const handledId = String(handledCommentId || "").trim();
|
|
142
|
+
if (!handledId || getHandledRepoSelectionCommentId(promptComment.bodyText || "") === handledId) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const nextBody = `${buildRepoSelectionComment(repos)}\n${REPO_SELECTION_HANDLED_PREFIX}${handledId}]`;
|
|
147
|
+
await jira.updateComment(detail.key, promptComment.id, nextBody);
|
|
148
|
+
promptComment.bodyText = nextBody;
|
|
149
|
+
promptComment.updated = new Date().toISOString();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function continueGitHubIssueCreation({
|
|
153
|
+
detail,
|
|
154
|
+
jira,
|
|
155
|
+
botUser,
|
|
156
|
+
forgeRegistry,
|
|
157
|
+
projectRoot,
|
|
158
|
+
config,
|
|
159
|
+
issueMemoryRecord,
|
|
160
|
+
issueMemory,
|
|
161
|
+
repos,
|
|
162
|
+
logger,
|
|
163
|
+
claudeResult = null,
|
|
164
|
+
services
|
|
165
|
+
}) {
|
|
166
|
+
if (!claudeResult) {
|
|
167
|
+
throw new Error("GitHub issue creation requires the live clarification result.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const latestRepoSelectionReply = getLatestHumanRepoSelectionReply(detail, botUser, jira);
|
|
171
|
+
let selectedRepo = selectIssueCreationRepo({ detail, issueMemory, repos });
|
|
172
|
+
const latestRepoPrompt = getLatestRepoSelectionPromptComment(detail, botUser, jira);
|
|
173
|
+
if (!selectedRepo && latestRepoSelectionReply) {
|
|
174
|
+
const fallback = await services.runClaudeRepoSelection({
|
|
175
|
+
issue: detail,
|
|
176
|
+
repos,
|
|
177
|
+
cwd: projectRoot,
|
|
178
|
+
permissionMode: config.CLAUDE_PERMISSION_MODE,
|
|
179
|
+
timeoutMs: parseOptionalInt(config.CLAUDE_TIMEOUT_MS),
|
|
180
|
+
issueKey: detail.key,
|
|
181
|
+
logger
|
|
182
|
+
});
|
|
183
|
+
selectedRepo = repos.find((repo) => repo.url === fallback.repo_url) || null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!selectedRepo) {
|
|
187
|
+
if (latestRepoSelectionReply && latestRepoPrompt) {
|
|
188
|
+
await markRepoSelectionReplyHandled(detail, jira, latestRepoPrompt, repos, latestRepoSelectionReply.id);
|
|
189
|
+
}
|
|
190
|
+
await ensureJiraComment(detail, jira, botUser, buildRepoSelectionComment(repos));
|
|
191
|
+
issueMemory.last_error = "Multiple repositories match this task. Waiting for explicit repo selection.";
|
|
192
|
+
return services.saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
if (selectedRepo.local_path) {
|
|
197
|
+
await services.pullLatest(selectedRepo.local_path);
|
|
198
|
+
}
|
|
199
|
+
const provider = forgeRegistry.getClientForRepo(selectedRepo.url);
|
|
200
|
+
const existingGitHubIssue = await provider.findIssueByJiraKey(selectedRepo.url, detail.key);
|
|
201
|
+
const githubIssue = existingGitHubIssue ?? await provider.createIssue(selectedRepo.url, {
|
|
202
|
+
title: buildGitHubIssueTitle(detail, claudeResult),
|
|
203
|
+
body: buildGitHubIssueBody(detail, claudeResult, selectedRepo.url)
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
issueMemory.github_issues.push({
|
|
207
|
+
repo_url: selectedRepo.url,
|
|
208
|
+
local_path: selectedRepo.local_path || "",
|
|
209
|
+
url: githubIssue.url,
|
|
210
|
+
number: String(githubIssue.number),
|
|
211
|
+
branch_name: "",
|
|
212
|
+
pr_url: "",
|
|
213
|
+
pr_number: ""
|
|
214
|
+
});
|
|
215
|
+
issueMemory.epic_key = issueMemory.epic_key || detail.epicKey || detail.key;
|
|
216
|
+
issueMemory.epic_url = issueMemory.epic_url || detail.epicUrl || detail.url;
|
|
217
|
+
issueMemory.github_issues = dedupeGitHubIssues(issueMemory.github_issues);
|
|
218
|
+
issueMemory = await services.saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
219
|
+
|
|
220
|
+
const successComment = existingGitHubIssue
|
|
221
|
+
? `Using existing repository issue: [#${githubIssue.number}](${githubIssue.url})`
|
|
222
|
+
: `Created repository issue: [#${githubIssue.number}](${githubIssue.url})`;
|
|
223
|
+
await ensureJiraComment(detail, jira, botUser, successComment);
|
|
224
|
+
issueMemory.last_error = "";
|
|
225
|
+
issueMemory = await services.saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
226
|
+
|
|
227
|
+
await logger.info(existingGitHubIssue ? "Repository issue already exists" : "Repository issue created", {
|
|
228
|
+
issue: detail.key,
|
|
229
|
+
repo: selectedRepo.url,
|
|
230
|
+
number: githubIssue.number
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const message = toErrorMessage(error);
|
|
234
|
+
await ensureJiraComment(detail, jira, botUser, `Failed to create repository issue for ${selectedRepo.url}: ${message}`);
|
|
235
|
+
issueMemory.last_error = message;
|
|
236
|
+
issueMemory = await services.saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return issueMemory;
|
|
241
|
+
}
|