create-claude-workspace 2.3.29 → 2.3.31
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.
|
@@ -74,6 +74,14 @@ export function getPRStatus(cwd, platform, branch) {
|
|
|
74
74
|
}
|
|
75
75
|
return getGitLabMRStatus(cwd, branch);
|
|
76
76
|
}
|
|
77
|
+
/** Like getPRStatus but uses the MR/PR number directly (more reliable than branch name lookup). */
|
|
78
|
+
export function getPRStatusByNumber(cwd, platform, prNumber) {
|
|
79
|
+
const id = String(prNumber);
|
|
80
|
+
if (platform === 'github') {
|
|
81
|
+
return getGitHubPRStatus(cwd, id);
|
|
82
|
+
}
|
|
83
|
+
return getGitLabMRStatus(cwd, id);
|
|
84
|
+
}
|
|
77
85
|
function getGitHubPRStatus(cwd, branch) {
|
|
78
86
|
const output = run('gh', [
|
|
79
87
|
'pr', 'view', branch,
|
package/dist/scheduler/loop.mjs
CHANGED
|
@@ -4,12 +4,13 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
6
|
import { parseTodoMd, updateTaskCheckbox } from './tasks/parser.mjs';
|
|
7
|
-
import { fetchOpenIssues, issueToTask, updateIssueStatus, isTriaged, updateIssueBody, addIssueLabels, closeIssue } from './tasks/issue-source.mjs';
|
|
7
|
+
import { fetchOpenIssues, issueToTask, updateIssueStatus, isTriaged, updateIssueBody, addIssueLabels, closeIssue, commentOnIssue } from './tasks/issue-source.mjs';
|
|
8
|
+
import { createPlatformIssue } from './git/issue-creator.mjs';
|
|
8
9
|
import { buildGraph, getParallelBatches, isPhaseComplete, getNextPhase, isProjectComplete } from './tasks/queue.mjs';
|
|
9
10
|
import { writeState, appendEvent, createEvent, rotateLog } from './state/state.mjs';
|
|
10
11
|
import { recordSession, getSession, clearSession } from './state/session.mjs';
|
|
11
12
|
import { createWorktree, commitInWorktree, getChangedFiles, cleanupWorktree, mergeToMain, syncMain, pushWorktree, forcePushWorktree, rebaseOnMain, cleanMainForMerge, popStash, getMainBranch, listWorktrees, listOrphanedWorktrees, isBranchMerged, getCurrentBranch, hasUncommittedChanges, deleteBranchRemote, cleanMergedBranches, pruneRemoteBranches, findStaleUnmergedBranches, } from './git/manager.mjs';
|
|
12
|
-
import { createPR, getPRStatus, getPRComments, mergePR, closePR } from './git/pr-manager.mjs';
|
|
13
|
+
import { createPR, getPRStatus, getPRStatusByNumber, getPRComments, mergePR, closePR } from './git/pr-manager.mjs';
|
|
13
14
|
import { scanAgents } from './agents/health-checker.mjs';
|
|
14
15
|
import { detectCIPlatform, fetchFailureLogs } from './git/ci-watcher.mjs';
|
|
15
16
|
import { createRelease } from './git/release.mjs';
|
|
@@ -88,6 +89,7 @@ export async function runIteration(deps) {
|
|
|
88
89
|
}
|
|
89
90
|
try {
|
|
90
91
|
tasks = fetchOpenIssues(projectDir, platform)
|
|
92
|
+
.filter(i => !i.labels.includes('status::triaged')) // skip original request issues (task issues are separate)
|
|
91
93
|
.map(i => issueToTask(i, state.currentPhase));
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
@@ -483,16 +485,40 @@ async function runTaskPipeline(task, workerId, agents, deps) {
|
|
|
483
485
|
}
|
|
484
486
|
catch { /* ignore parse errors */ }
|
|
485
487
|
}
|
|
486
|
-
//
|
|
488
|
+
// Create a NEW task issue with the structured spec, link back to the original request
|
|
487
489
|
if (ciPlatformTriage !== 'none' && issueNum) {
|
|
488
490
|
try {
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
491
|
+
const taskLabels = [`type::${task.type}`, `complexity::${task.complexity}`, 'status::todo'];
|
|
492
|
+
const taskBody = `> Task created from request #${issueNum}\n\n${analystResult.output}`;
|
|
493
|
+
const created = createPlatformIssue(projectDir, ciPlatformTriage, {
|
|
494
|
+
title: task.title,
|
|
495
|
+
body: taskBody,
|
|
496
|
+
labels: taskLabels,
|
|
497
|
+
});
|
|
498
|
+
if (created) {
|
|
499
|
+
// Mark original issue as triaged and link to the task issue
|
|
500
|
+
commentOnIssue(projectDir, ciPlatformTriage, issueNum, `Triaged → task issue #${created.number}`);
|
|
501
|
+
addIssueLabels(projectDir, ciPlatformTriage, issueNum, ['status::triaged']);
|
|
502
|
+
// Switch the pipeline to track the new task issue
|
|
503
|
+
const newId = `#${created.number}`;
|
|
504
|
+
task.id = newId;
|
|
505
|
+
task.issueMarker = newId;
|
|
506
|
+
pipeline.taskId = newId;
|
|
507
|
+
// Re-register pipeline under new ID
|
|
508
|
+
delete state.pipelines[`#${issueNum}`];
|
|
509
|
+
state.pipelines[newId] = pipeline;
|
|
510
|
+
logger.info(`[${newId}] Created task issue from request #${issueNum}`);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
// Fallback: update original issue if creation failed
|
|
514
|
+
updateIssueBody(projectDir, ciPlatformTriage, issueNum, analystResult.output);
|
|
515
|
+
const newLabels = [`type::${task.type}`, `complexity::${task.complexity}`, 'status::todo'];
|
|
516
|
+
addIssueLabels(projectDir, ciPlatformTriage, issueNum, newLabels);
|
|
517
|
+
logger.warn(`[${task.id}] Failed to create task issue — updated original instead`);
|
|
518
|
+
}
|
|
493
519
|
}
|
|
494
520
|
catch (err) {
|
|
495
|
-
logger.warn(`[${task.id}] Failed to update issue: ${err.message?.split('\n')[0]}`);
|
|
521
|
+
logger.warn(`[${task.id}] Failed to create/update issue: ${err.message?.split('\n')[0]}`);
|
|
496
522
|
}
|
|
497
523
|
}
|
|
498
524
|
}
|
|
@@ -882,7 +908,25 @@ export async function recoverOrphanedWorktrees(projectDir, state, logger, _deps)
|
|
|
882
908
|
continue;
|
|
883
909
|
}
|
|
884
910
|
const branch = getCurrentBranch(worktreePath);
|
|
885
|
-
const
|
|
911
|
+
const hasChanges = hasUncommittedChanges(worktreePath);
|
|
912
|
+
const changedFromMain = getChangedFiles(worktreePath);
|
|
913
|
+
const hasCommits = changedFromMain.length > 0;
|
|
914
|
+
// Check platform PR status before making any merge decisions
|
|
915
|
+
let prMerged = false;
|
|
916
|
+
let prOpen = false;
|
|
917
|
+
if (ciPlatform !== 'none') {
|
|
918
|
+
try {
|
|
919
|
+
const prStatus = getPRStatus(projectDir, ciPlatform, branch);
|
|
920
|
+
prMerged = prStatus.status === 'merged';
|
|
921
|
+
prOpen = prStatus.status === 'open';
|
|
922
|
+
}
|
|
923
|
+
catch { /* no PR exists */ }
|
|
924
|
+
}
|
|
925
|
+
// Only consider truly merged if:
|
|
926
|
+
// 1. PR was explicitly merged on the platform, OR
|
|
927
|
+
// 2. Branch has unique commits AND they're all on main (git ancestry) AND no open PR
|
|
928
|
+
const gitMerged = hasCommits && !prOpen && isBranchMerged(projectDir, branch);
|
|
929
|
+
const alreadyMerged = prMerged || gitMerged;
|
|
886
930
|
if (alreadyMerged) {
|
|
887
931
|
logger.info(`[recovery] ${branch} already merged — cleaning up`);
|
|
888
932
|
cleanupWorktree(projectDir, worktreePath, branch);
|
|
@@ -890,14 +934,10 @@ export async function recoverOrphanedWorktrees(projectDir, state, logger, _deps)
|
|
|
890
934
|
closeRecoveredIssue(projectDir, branch, logger);
|
|
891
935
|
continue;
|
|
892
936
|
}
|
|
893
|
-
const hasChanges = hasUncommittedChanges(worktreePath);
|
|
894
|
-
const changedFromMain = getChangedFiles(worktreePath);
|
|
895
|
-
const hasCommits = changedFromMain.length > 0;
|
|
896
937
|
if (!hasCommits && !hasChanges) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
continue;
|
|
938
|
+
// Branch exists but has no work yet — re-inject at the beginning of the pipeline
|
|
939
|
+
// (agent may have crashed before making any changes, issue is still valid)
|
|
940
|
+
logger.info(`[recovery] ${branch} has no changes yet — re-injecting from start`);
|
|
901
941
|
}
|
|
902
942
|
// Auto-commit any uncommitted changes so the pipeline can work with them
|
|
903
943
|
if (hasChanges) {
|
|
@@ -907,7 +947,8 @@ export async function recoverOrphanedWorktrees(projectDir, state, logger, _deps)
|
|
|
907
947
|
// Determine the right pipeline step to resume from
|
|
908
948
|
const issueNum = extractIssueFromBranch(branch);
|
|
909
949
|
const taskId = issueNum ? `#${issueNum}` : branch;
|
|
910
|
-
const
|
|
950
|
+
const hasWork = hasCommits || hasChanges;
|
|
951
|
+
const step = hasWork ? diagnoseStep(projectDir, branch, ciPlatform) : 'plan';
|
|
911
952
|
logger.info(`[recovery] ${branch} → re-injecting into pipeline at step "${step}"`);
|
|
912
953
|
// Register in pipelines so the normal task loop picks it up
|
|
913
954
|
state.pipelines[taskId] = {
|
|
@@ -1189,7 +1230,8 @@ async function checkPRWatch(taskId, pipeline, projectDir, agents, deps) {
|
|
|
1189
1230
|
return 'failed';
|
|
1190
1231
|
const branch = pipeline.branchSlug;
|
|
1191
1232
|
try {
|
|
1192
|
-
|
|
1233
|
+
// Use PR number (more reliable than branch name lookup which can fail on glab)
|
|
1234
|
+
const prStatus = getPRStatusByNumber(projectDir, ciPlatform, pipeline.prState.prNumber);
|
|
1193
1235
|
if (prStatus.status === 'merged')
|
|
1194
1236
|
return 'merged';
|
|
1195
1237
|
// PR was closed (not merged) — try local merge as fallback before giving up
|
|
@@ -217,6 +217,15 @@ export function addIssueLabels(cwd, platform, issueNumber, labels) {
|
|
|
217
217
|
run('glab', ['issue', 'update', num, '--label', labels.join(',')], cwd);
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
+
export function commentOnIssue(cwd, platform, issueNumber, comment) {
|
|
221
|
+
const num = String(issueNumber);
|
|
222
|
+
if (platform === 'github') {
|
|
223
|
+
run('gh', ['issue', 'comment', num, '--body', comment], cwd);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
run('glab', ['issue', 'note', num, '--message', comment], cwd);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
220
229
|
export function closeIssue(cwd, platform, issueNumber, comment) {
|
|
221
230
|
const num = String(issueNumber);
|
|
222
231
|
if (comment) {
|