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 +1 -1
- package/src/commands/worker.js +75 -9
- package/src/repo.js +32 -1
package/package.json
CHANGED
package/src/commands/worker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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_.-]+)
|
|
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_.-]+)
|
|
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;
|