claude-teammate 0.1.259 → 0.1.261

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-teammate",
3
- "version": "0.1.259",
3
+ "version": "0.1.261",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/repo.js CHANGED
@@ -8,6 +8,10 @@ import { buildGitEnvForRepoUrl, parseGitHubRepoUrl, parseRepoUrl } from "./forge
8
8
  // tasks from racing to create the same worktree for the same branch.
9
9
  const worktreeLocks = new Map();
10
10
 
11
+ // In-process lock: maps checkoutPath → Promise to prevent concurrent git clones
12
+ // to the same directory (race condition when two issues share the same repo).
13
+ const cloneInProgress = new Map();
14
+
11
15
  const execFileAsync = promisify(execFile);
12
16
 
13
17
  export class RepoCheckoutNotReadyError extends Error {
@@ -37,14 +41,28 @@ export async function ensureLocalRepo(repoUrl, reposDir) {
37
41
  await forceRemoveCheckout(checkoutPath);
38
42
  }
39
43
 
40
- await mkdir(path.dirname(checkoutPath), { recursive: true });
41
- await execFileAsync("git", ["clone", repo.cloneUrl, checkoutPath], {
42
- maxBuffer: 10 * 1024 * 1024,
43
- env: buildGitEnvForRepoUrl(repo.cloneUrl)
44
- }).catch(async (err) => {
45
- await forceRemoveCheckout(checkoutPath).catch(() => {});
46
- throw err;
47
- });
44
+ if (cloneInProgress.has(checkoutPath)) {
45
+ await cloneInProgress.get(checkoutPath);
46
+ return checkoutPath;
47
+ }
48
+
49
+ const clonePromise = (async () => {
50
+ await mkdir(path.dirname(checkoutPath), { recursive: true });
51
+ await execFileAsync("git", ["clone", repo.cloneUrl, checkoutPath], {
52
+ maxBuffer: 10 * 1024 * 1024,
53
+ env: buildGitEnvForRepoUrl(repo.cloneUrl)
54
+ }).catch(async (err) => {
55
+ await forceRemoveCheckout(checkoutPath).catch(() => {});
56
+ throw err;
57
+ });
58
+ })();
59
+
60
+ cloneInProgress.set(checkoutPath, clonePromise);
61
+ try {
62
+ await clonePromise;
63
+ } finally {
64
+ cloneInProgress.delete(checkoutPath);
65
+ }
48
66
 
49
67
  return checkoutPath;
50
68
  }
@@ -79,14 +97,28 @@ export async function ensureReviewRepo(repoUrl, reviewReposDir) {
79
97
  await forceRemoveCheckout(checkoutPath);
80
98
  }
81
99
 
82
- await mkdir(path.dirname(checkoutPath), { recursive: true });
83
- await execFileAsync("git", ["clone", repo.cloneUrl, checkoutPath], {
84
- maxBuffer: 10 * 1024 * 1024,
85
- env: buildGitEnvForRepoUrl(repo.cloneUrl)
86
- }).catch(async (err) => {
87
- await forceRemoveCheckout(checkoutPath).catch(() => {});
88
- throw err;
89
- });
100
+ if (cloneInProgress.has(checkoutPath)) {
101
+ await cloneInProgress.get(checkoutPath);
102
+ return checkoutPath;
103
+ }
104
+
105
+ const clonePromise = (async () => {
106
+ await mkdir(path.dirname(checkoutPath), { recursive: true });
107
+ await execFileAsync("git", ["clone", repo.cloneUrl, checkoutPath], {
108
+ maxBuffer: 10 * 1024 * 1024,
109
+ env: buildGitEnvForRepoUrl(repo.cloneUrl)
110
+ }).catch(async (err) => {
111
+ await forceRemoveCheckout(checkoutPath).catch(() => {});
112
+ throw err;
113
+ });
114
+ })();
115
+
116
+ cloneInProgress.set(checkoutPath, clonePromise);
117
+ try {
118
+ await clonePromise;
119
+ } finally {
120
+ cloneInProgress.delete(checkoutPath);
121
+ }
90
122
 
91
123
  return checkoutPath;
92
124
  }
@@ -369,6 +369,8 @@ export async function processJiraIssue({
369
369
 
370
370
  if (repoCheckoutUpdated) {
371
371
  await saveProgress("Repository ready. Analyzing requirements...");
372
+ epicMemory.repos = liveRepos;
373
+ epicMemory = await services.saveEpicMemory(epicMemoryRecord.filePath, detail, epicMemory);
372
374
  }
373
375
 
374
376
  const linkedSource = await services.fetchLinkedForgeState({
@@ -522,6 +524,8 @@ export async function processJiraIssue({
522
524
 
523
525
  if (claudeResult.decision === "needs_clarification") {
524
526
  issueMemory.progress_comment_id = null;
527
+ issueMemory.code_change_input_id = latestInput.id;
528
+ issueMemory = await services.saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
525
529
  await ensureJiraComment(detail, jira, botUser, formatClarificationQuestions(claudeResult.questions));
526
530
  await logger.info("Clarification requested from Jira", {
527
531
  issue: detail.key,