claude-teammate 0.1.200 → 0.1.201
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 +8 -1
- package/src/claude/validators.js +7 -1
- package/src/claude.js +14 -5
- package/src/memory.js +1 -0
- package/src/worker/epic-memory.js +28 -2
- package/src/worker/jira-helpers.js +10 -2
- package/src/worker/jira-issue-support.js +2 -1
- package/src/worker/jira-issue-workflow.js +5 -0
package/package.json
CHANGED
package/src/claude/prompts.js
CHANGED
|
@@ -585,11 +585,18 @@ export function buildEpicMemoryCleanupSystemPrompt() {
|
|
|
585
585
|
"Remove obvious, redundant, contradicted, or one-off ticket details.",
|
|
586
586
|
LANGUAGE_PRESERVATION_RULES,
|
|
587
587
|
"Do not invent knowledge that is not grounded in the provided memory snapshot.",
|
|
588
|
+
"Also extract repo_branches: for each repository URL in the known repos list (or mentioned in the candidate updates), if the candidate updates specify the TARGET/BASE branch — the branch the PR or MR should merge INTO (e.g. 'tạo MR vào nhánh main', 'merge into develop', 'PR target is staging', 'nhánh chính là develop') — emit {url, branch} for it. Do NOT capture implementation/feature branch names (the short-lived branch being created for this task). Only include repos where a base/target branch is explicitly mentioned.",
|
|
589
|
+
"Also extract pr_repo_url: if the candidate updates indicate which specific repository should receive the pull request or merge request (e.g. 'tạo PR vào org/backend', 'create PR in org/frontend', 'MR goes to repo X'), set this to that repository's full URL from the known repos list. Set to empty string if not mentioned or ambiguous.",
|
|
588
590
|
"Return only structured output matching the schema."
|
|
589
591
|
].join(" ");
|
|
590
592
|
}
|
|
591
593
|
|
|
592
594
|
export function buildEpicMemoryCleanupUserPrompt(input) {
|
|
595
|
+
const knownRepos = (input.epicMemory?.repos || []).map((r) => r.url).filter(Boolean);
|
|
596
|
+
const reposLine = knownRepos.length > 0
|
|
597
|
+
? `\nKnown repos:\n${knownRepos.map((u) => `- ${u}`).join("\n")}`
|
|
598
|
+
: "";
|
|
599
|
+
|
|
593
600
|
return `Update this epic memory facts and guardrails and clean it up.
|
|
594
601
|
|
|
595
602
|
Reason:
|
|
@@ -597,7 +604,7 @@ ${input.reason || "(none)"}
|
|
|
597
604
|
|
|
598
605
|
Current epic memory:
|
|
599
606
|
${JSON.stringify(input.epicMemory || {}, null, 2)}
|
|
600
|
-
|
|
607
|
+
${reposLine}
|
|
601
608
|
Latest candidate updates:
|
|
602
609
|
${JSON.stringify(input.candidateUpdates || {}, null, 2)}
|
|
603
610
|
|
package/src/claude/validators.js
CHANGED
|
@@ -121,7 +121,13 @@ export function validateEpicMemoryCleanupResult(result) {
|
|
|
121
121
|
: [],
|
|
122
122
|
guardrails: Array.isArray(result.guardrails)
|
|
123
123
|
? result.guardrails.map((item) => String(item).trim()).filter(Boolean)
|
|
124
|
-
: []
|
|
124
|
+
: [],
|
|
125
|
+
repo_branches: Array.isArray(result.repo_branches)
|
|
126
|
+
? result.repo_branches
|
|
127
|
+
.filter((rb) => rb && typeof rb.url === "string" && rb.url.trim())
|
|
128
|
+
.map((rb) => ({ url: String(rb.url).trim(), branch: String(rb.branch || "").trim() }))
|
|
129
|
+
: [],
|
|
130
|
+
pr_repo_url: typeof result.pr_repo_url === "string" ? result.pr_repo_url.trim() : ""
|
|
125
131
|
};
|
|
126
132
|
}
|
|
127
133
|
|
package/src/claude.js
CHANGED
|
@@ -247,16 +247,25 @@ const EPIC_MEMORY_CLEANUP_SCHEMA = {
|
|
|
247
247
|
properties: {
|
|
248
248
|
facts: {
|
|
249
249
|
type: "array",
|
|
250
|
-
items: {
|
|
251
|
-
type: "string"
|
|
252
|
-
}
|
|
250
|
+
items: { type: "string" }
|
|
253
251
|
},
|
|
254
252
|
guardrails: {
|
|
253
|
+
type: "array",
|
|
254
|
+
items: { type: "string" }
|
|
255
|
+
},
|
|
256
|
+
repo_branches: {
|
|
255
257
|
type: "array",
|
|
256
258
|
items: {
|
|
257
|
-
type: "
|
|
259
|
+
type: "object",
|
|
260
|
+
additionalProperties: false,
|
|
261
|
+
properties: {
|
|
262
|
+
url: { type: "string" },
|
|
263
|
+
branch: { type: "string" }
|
|
264
|
+
},
|
|
265
|
+
required: ["url", "branch"]
|
|
258
266
|
}
|
|
259
|
-
}
|
|
267
|
+
},
|
|
268
|
+
pr_repo_url: { type: "string" }
|
|
260
269
|
},
|
|
261
270
|
required: ["facts", "guardrails"]
|
|
262
271
|
};
|
package/src/memory.js
CHANGED
|
@@ -195,6 +195,7 @@ function normalizeEpicMemoryData(data) {
|
|
|
195
195
|
}));
|
|
196
196
|
normalized.facts = [...new Set(normalized.facts.map((fact) => String(fact).trim()).filter(Boolean))];
|
|
197
197
|
normalized.guardrails = [...new Set(normalized.guardrails.map((guardrail) => String(guardrail).trim()).filter(Boolean))];
|
|
198
|
+
normalized.pr_repo_url = String(normalized.pr_repo_url || "").trim();
|
|
198
199
|
|
|
199
200
|
delete normalized.repo_url;
|
|
200
201
|
delete normalized.local_repo_path;
|
|
@@ -121,10 +121,16 @@ export async function saveMaintainedEpicMemory({
|
|
|
121
121
|
issueKey: issue.key,
|
|
122
122
|
logger
|
|
123
123
|
});
|
|
124
|
+
const updatedRepos = applyRepoBranchUpdates(
|
|
125
|
+
startingEpicMemory.repos || [],
|
|
126
|
+
cleaned.repo_branches || []
|
|
127
|
+
);
|
|
124
128
|
nextEpicMemory = normalizeEpicMemoryContent({
|
|
125
129
|
...startingEpicMemory,
|
|
126
130
|
facts: cleaned.facts || [],
|
|
127
|
-
guardrails: cleaned.guardrails || []
|
|
131
|
+
guardrails: cleaned.guardrails || [],
|
|
132
|
+
repos: updatedRepos,
|
|
133
|
+
pr_repo_url: cleaned.pr_repo_url || startingEpicMemory.pr_repo_url || ""
|
|
128
134
|
});
|
|
129
135
|
if (logger) {
|
|
130
136
|
await logger.info("Updated epic memory", {
|
|
@@ -230,10 +236,30 @@ function hasEpicMemoryContentChanges(current, next) {
|
|
|
230
236
|
JSON.stringify(dedupeTextValues(current?.facts || [])) !==
|
|
231
237
|
JSON.stringify(dedupeTextValues(next?.facts || [])) ||
|
|
232
238
|
JSON.stringify(dedupeTextValues(current?.guardrails || [])) !==
|
|
233
|
-
JSON.stringify(dedupeTextValues(next?.guardrails || []))
|
|
239
|
+
JSON.stringify(dedupeTextValues(next?.guardrails || [])) ||
|
|
240
|
+
(current?.pr_repo_url || "") !== (next?.pr_repo_url || "") ||
|
|
241
|
+
JSON.stringify(current?.repos || []) !== JSON.stringify(next?.repos || [])
|
|
234
242
|
);
|
|
235
243
|
}
|
|
236
244
|
|
|
245
|
+
function applyRepoBranchUpdates(existingRepos, repoBranches) {
|
|
246
|
+
if (!Array.isArray(repoBranches) || repoBranches.length === 0) {
|
|
247
|
+
return existingRepos;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let repos = [...existingRepos];
|
|
251
|
+
for (const rb of repoBranches) {
|
|
252
|
+
if (!rb.url) continue;
|
|
253
|
+
const idx = repos.findIndex((r) => r.url === rb.url);
|
|
254
|
+
if (idx === -1) {
|
|
255
|
+
repos.push({ url: rb.url, local_path: "", working_branch: rb.branch });
|
|
256
|
+
} else if (rb.branch) {
|
|
257
|
+
repos[idx] = { ...repos[idx], working_branch: rb.branch };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return repos;
|
|
261
|
+
}
|
|
262
|
+
|
|
237
263
|
export function buildReopenContext({ reopenedFromInReview, issueMemoryPath, epicMemoryPath, workerLogPath }) {
|
|
238
264
|
if (!reopenedFromInReview) {
|
|
239
265
|
return null;
|
|
@@ -234,7 +234,7 @@ export function filterReposForActiveForge(repos, forgeRegistry) {
|
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
export function selectIssueCreationRepo({ detail, issueMemory, repos }) {
|
|
237
|
+
export function selectIssueCreationRepo({ detail, issueMemory, repos, epicMemory }) {
|
|
238
238
|
if (!Array.isArray(repos) || repos.length === 0) {
|
|
239
239
|
return null;
|
|
240
240
|
}
|
|
@@ -244,6 +244,14 @@ export function selectIssueCreationRepo({ detail, issueMemory, repos }) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
const repoByUrl = new Map(repos.map((repo) => [String(repo.url || "").trim(), repo]));
|
|
247
|
+
|
|
248
|
+
if (epicMemory?.pr_repo_url) {
|
|
249
|
+
const prRepo = repoByUrl.get(String(epicMemory.pr_repo_url).trim());
|
|
250
|
+
if (prRepo) {
|
|
251
|
+
return prRepo;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
247
255
|
const latestExplicitRepo = getLatestExplicitIssueCreationRepo(detail, repoByUrl);
|
|
248
256
|
if (latestExplicitRepo) {
|
|
249
257
|
return latestExplicitRepo;
|
|
@@ -415,7 +423,7 @@ function ensureImplementationInstruction(body) {
|
|
|
415
423
|
return `${normalizedBody}\n\n---\n\n${IMPLEMENTATION_INSTRUCTION}`.trim();
|
|
416
424
|
}
|
|
417
425
|
|
|
418
|
-
const BRANCH_KEYWORD_RE = /\b(?:branch|nhánh)\s*[
|
|
426
|
+
const BRANCH_KEYWORD_RE = /\b(?:branch|nhánh)\s*[:=]\s*([a-zA-Z0-9][a-zA-Z0-9_\-./]*)/iu;
|
|
419
427
|
|
|
420
428
|
function extractRepoBranchPairs(text) {
|
|
421
429
|
const str = String(text || "");
|
|
@@ -159,6 +159,7 @@ export async function continueGitHubIssueCreation({
|
|
|
159
159
|
issueMemoryRecord,
|
|
160
160
|
issueMemory,
|
|
161
161
|
repos,
|
|
162
|
+
epicMemory = null,
|
|
162
163
|
logger,
|
|
163
164
|
claudeResult = null,
|
|
164
165
|
services
|
|
@@ -168,7 +169,7 @@ export async function continueGitHubIssueCreation({
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
const latestRepoSelectionReply = getLatestHumanRepoSelectionReply(detail, botUser, jira);
|
|
171
|
-
let selectedRepo = selectIssueCreationRepo({ detail, issueMemory, repos });
|
|
172
|
+
let selectedRepo = selectIssueCreationRepo({ detail, issueMemory, repos, epicMemory });
|
|
172
173
|
const latestRepoPrompt = getLatestRepoSelectionPromptComment(detail, botUser, jira);
|
|
173
174
|
if (!selectedRepo && latestRepoSelectionReply) {
|
|
174
175
|
const fallback = await services.runClaudeRepoSelection({
|
|
@@ -170,6 +170,10 @@ export async function processJiraIssue({
|
|
|
170
170
|
services
|
|
171
171
|
}));
|
|
172
172
|
|
|
173
|
+
// Re-derive after LLM may have updated epicMemory (repo branches, pr_repo_url).
|
|
174
|
+
// Regex derivation uses epicMemory.repos as base so LLM-set branches are preserved.
|
|
175
|
+
liveRepos = deriveTaskRepos(detail, epicMemory, forgeRegistry);
|
|
176
|
+
|
|
173
177
|
let reopenedFromInReview = false;
|
|
174
178
|
if (isInReviewStatus(detail.status) && hasNewHumanReplyWhileWaiting(detail, botUser, jira)) {
|
|
175
179
|
const transition = await jira.transitionIssueToStatus(detail.key, "In Progress");
|
|
@@ -460,6 +464,7 @@ export async function processJiraIssue({
|
|
|
460
464
|
issueMemoryRecord,
|
|
461
465
|
issueMemory,
|
|
462
466
|
repos: liveRepos,
|
|
467
|
+
epicMemory,
|
|
463
468
|
logger,
|
|
464
469
|
claudeResult,
|
|
465
470
|
services
|