claude-teammate 0.1.13 → 0.1.15

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.13",
3
+ "version": "0.1.15",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,7 @@ import {
32
32
  listMissingRepoPaths,
33
33
  parseGitHubRepoUrl,
34
34
  prepareRepoForBranch,
35
+ RepoCheckoutNotReadyError,
35
36
  validateOrEnsureLocalRepo
36
37
  } from "../repo.js";
37
38
  import {
@@ -206,7 +207,18 @@ export async function runWorkerCommand({ projectRoot }) {
206
207
  let trackedIssueCount = 0;
207
208
 
208
209
  for (const repo of repos) {
209
- repo.local_path = await validateOrEnsureLocalRepo(repo.url, runtimePaths.reposDir, repo.local_path);
210
+ try {
211
+ repo.local_path = await validateOrEnsureLocalRepo(repo.url, runtimePaths.reposDir, repo.local_path);
212
+ } catch (error) {
213
+ if (isRepoCheckoutNotReadyError(error)) {
214
+ await logger.info("Skipping GitHub repo until checkout is ready", {
215
+ repo: repo.url,
216
+ localPath: error.localPath || repo.local_path
217
+ });
218
+ continue;
219
+ }
220
+ throw error;
221
+ }
210
222
  const issues = await github.listIssues(repo.url);
211
223
  const botIssues = issues.filter((issue) => isGitHubBotAuthor(issue.author, githubBotUser));
212
224
  trackedIssueCount += botIssues.length;
@@ -258,7 +270,18 @@ export async function runWorkerCommand({ projectRoot }) {
258
270
  let trackedPrCount = 0;
259
271
 
260
272
  for (const repo of repos) {
261
- repo.local_path = await validateOrEnsureLocalRepo(repo.url, runtimePaths.reposDir, repo.local_path);
273
+ try {
274
+ repo.local_path = await validateOrEnsureLocalRepo(repo.url, runtimePaths.reposDir, repo.local_path);
275
+ } catch (error) {
276
+ if (isRepoCheckoutNotReadyError(error)) {
277
+ await logger.info("Skipping draft PR repo until checkout is ready", {
278
+ repo: repo.url,
279
+ localPath: error.localPath || repo.local_path
280
+ });
281
+ continue;
282
+ }
283
+ throw error;
284
+ }
262
285
 
263
286
  const pullRequests = await github.listPullRequests(repo.url);
264
287
  const botPullRequests = pullRequests.filter(
@@ -403,7 +426,22 @@ async function processJiraIssue({ issue, jira, github, botUser, config, projectR
403
426
 
404
427
  let repoCheckoutUpdated = false;
405
428
  for (const repo of epicMemory.repos) {
406
- const validatedLocalPath = await validateOrEnsureLocalRepo(repo.url, runtimePaths.reposDir, repo.local_path);
429
+ let validatedLocalPath;
430
+ try {
431
+ validatedLocalPath = await validateOrEnsureLocalRepo(repo.url, runtimePaths.reposDir, repo.local_path);
432
+ } catch (error) {
433
+ if (isRepoCheckoutNotReadyError(error)) {
434
+ issueMemory.last_error = `Repository checkout is not ready yet for ${repo.url}. The worker will retry automatically.`;
435
+ issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
436
+ await logger.info("Skipping Jira issue until checkout is ready", {
437
+ issue: detail.key,
438
+ repo: repo.url,
439
+ localPath: error.localPath || repo.local_path
440
+ });
441
+ return buildIssueState(detail, epicMemory, issueMemory, null, issueMemory.last_error);
442
+ }
443
+ throw error;
444
+ }
407
445
  if (validatedLocalPath !== repo.local_path) {
408
446
  repo.local_path = validatedLocalPath;
409
447
  repoCheckoutUpdated = true;
@@ -2153,12 +2191,12 @@ export function collectRequiredRepoPathsForPullRequest(pullRequestBody, epicMemo
2153
2191
  continue;
2154
2192
  }
2155
2193
 
2156
- const copyFromMatch = /copy\s+`?([A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*\/?)`?\s+from\s+`?([A-Za-z0-9_.-]+)`?/iu.exec(trimmedLine);
2194
+ const copyFromMatch = /copy\s+`?([A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*)\/?`?\s+from\s+`?([A-Za-z0-9_.-]+)`?/iu.exec(trimmedLine);
2157
2195
  if (copyFromMatch) {
2158
2196
  appendRequirement(copyFromMatch[2], copyFromMatch[1]);
2159
2197
  }
2160
2198
 
2161
- const copyPathMatch = /copy\s+`?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*\/?)`?/iu.exec(trimmedLine);
2199
+ const copyPathMatch = /copy\s+`?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*)\/?`?/iu.exec(trimmedLine);
2162
2200
  if (copyPathMatch) {
2163
2201
  appendRequirement(copyPathMatch[1], copyPathMatch[2]);
2164
2202
  }
@@ -2169,14 +2207,17 @@ export function collectRequiredRepoPathsForPullRequest(pullRequestBody, epicMemo
2169
2207
  }
2170
2208
 
2171
2209
  const [, repoIdentifier, body] = labeledLineMatch;
2172
- for (const pathMatch of body.matchAll(/\b([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*\/?)\b/gu)) {
2173
- appendRequirement(repoIdentifier, pathMatch[1]);
2174
- }
2175
-
2176
2210
  const sourceModuleMatch = /\b([A-Za-z0-9_]+)\b(?=\s+is the source module\b)/iu.exec(body);
2177
2211
  if (sourceModuleMatch) {
2178
2212
  appendRequirement(repoIdentifier, sourceModuleMatch[1]);
2179
2213
  }
2214
+
2215
+ const templatePathMatch = /\b([A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+){2,})\/?\b/gu;
2216
+ for (const match of body.matchAll(templatePathMatch)) {
2217
+ if (looksLikeRepoRelativePath(match[1])) {
2218
+ appendRequirement(repoIdentifier, match[1]);
2219
+ }
2220
+ }
2180
2221
  }
2181
2222
 
2182
2223
  return requirements;
@@ -2234,6 +2275,17 @@ function normalizeRepoAlias(value) {
2234
2275
  return String(value || "").trim().toLowerCase();
2235
2276
  }
2236
2277
 
2278
+ function isRepoCheckoutNotReadyError(error) {
2279
+ return Boolean(
2280
+ error &&
2281
+ typeof error === "object" &&
2282
+ (
2283
+ error instanceof RepoCheckoutNotReadyError ||
2284
+ error.code === "REPO_CHECKOUT_NOT_READY"
2285
+ )
2286
+ );
2287
+ }
2288
+
2237
2289
  function normalizeRepoRelativePath(value) {
2238
2290
  const normalized = String(value || "").trim().replace(/^\/+/u, "").replace(/\/+$/u, "");
2239
2291
  if (!normalized || normalized.startsWith("http://") || normalized.startsWith("https://")) {
@@ -2242,6 +2294,20 @@ function normalizeRepoRelativePath(value) {
2242
2294
  return normalized;
2243
2295
  }
2244
2296
 
2297
+ function looksLikeRepoRelativePath(value) {
2298
+ const normalized = normalizeRepoRelativePath(value);
2299
+ if (!normalized) {
2300
+ return false;
2301
+ }
2302
+
2303
+ const segments = normalized.split("/");
2304
+ if (segments.length < 2) {
2305
+ return false;
2306
+ }
2307
+
2308
+ return segments.length >= 3 || normalized.includes("_") || normalized.includes(".");
2309
+ }
2310
+
2245
2311
  function formatRepoAccessBlocker({ repoEntry, repoUrl, reason, missingPaths = [] }) {
2246
2312
  const repoLabel = repoEntry?.local_path || repoEntry?.url || "unknown repo";
2247
2313
 
package/src/repo.js CHANGED
@@ -1,4 +1,4 @@
1
- import { access, mkdir } from "node:fs/promises";
1
+ import { access, mkdir, readdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
4
  import process from "node:process";
@@ -6,6 +6,16 @@ import { promisify } from "node:util";
6
6
 
7
7
  const execFileAsync = promisify(execFile);
8
8
 
9
+ export class RepoCheckoutNotReadyError extends Error {
10
+ constructor(message, details = {}) {
11
+ super(message);
12
+ this.name = "RepoCheckoutNotReadyError";
13
+ this.code = "REPO_CHECKOUT_NOT_READY";
14
+ this.repoUrl = details.repoUrl || "";
15
+ this.localPath = details.localPath || "";
16
+ }
17
+ }
18
+
9
19
  export async function ensureLocalRepo(repoUrl, reposDir) {
10
20
  const repo = parseGitHubRepoUrl(repoUrl);
11
21
  const checkoutPath = path.join(reposDir, repo.owner, repo.name);
@@ -28,6 +38,19 @@ export async function validateOrEnsureLocalRepo(repoUrl, reposDir, localPath = "
28
38
  return candidatePath;
29
39
  }
30
40
 
41
+ if (candidatePath && await pathExists(candidatePath)) {
42
+ const entries = await safeReadDir(candidatePath);
43
+ if (entries.length > 0) {
44
+ throw new RepoCheckoutNotReadyError(
45
+ `Repository checkout is not ready yet at ${candidatePath}.`,
46
+ {
47
+ repoUrl,
48
+ localPath: candidatePath
49
+ }
50
+ );
51
+ }
52
+ }
53
+
31
54
  return ensureLocalRepo(repoUrl, reposDir);
32
55
  }
33
56
 
@@ -114,6 +137,14 @@ async function pathExists(targetPath) {
114
137
  }
115
138
  }
116
139
 
140
+ async function safeReadDir(targetPath) {
141
+ try {
142
+ return await readdir(targetPath);
143
+ } catch {
144
+ return [];
145
+ }
146
+ }
147
+
117
148
  async function isUsableGitCheckout(repoPath) {
118
149
  if (!repoPath) {
119
150
  return false;