create-claude-workspace 2.3.28 → 2.3.30

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.
@@ -4,7 +4,8 @@ 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';
@@ -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) {
@@ -410,7 +412,7 @@ async function runTaskPipeline(task, workerId, agents, deps) {
410
412
  pipeline.workerId = workerId;
411
413
  state.pipelines[task.id] = pipeline;
412
414
  // Determine which steps to skip (for resumed pipelines)
413
- const skipTo = resumeStep ?? 'plan';
415
+ const skipTo = resumeStep ?? 'triage';
414
416
  const stepOrder = ['triage', 'plan', 'implement', 'test', 'review', 'rework', 'commit', 'pr-create', 'pr-watch', 'merge'];
415
417
  const skipToIndex = stepOrder.indexOf(skipTo);
416
418
  const shouldSkip = (step) => stepOrder.indexOf(step) < skipToIndex;
@@ -483,16 +485,40 @@ async function runTaskPipeline(task, workerId, agents, deps) {
483
485
  }
484
486
  catch { /* ignore parse errors */ }
485
487
  }
486
- // Update issue on platform with structured spec + labels
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
- updateIssueBody(projectDir, ciPlatformTriage, issueNum, analystResult.output);
490
- const newLabels = [`type::${task.type}`, `complexity::${task.complexity}`, 'status::todo'];
491
- addIssueLabels(projectDir, ciPlatformTriage, issueNum, newLabels);
492
- logger.info(`[${task.id}] Issue updated with spec + labels`);
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 alreadyMerged = isBranchMerged(projectDir, branch);
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
- logger.info(`[recovery] ${branch} has no changescleaning up`);
898
- cleanupWorktree(projectDir, worktreePath, branch);
899
- deleteBranchRemote(projectDir, branch);
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 step = diagnoseStep(projectDir, branch, ciPlatform);
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] = {
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "2.3.28",
3
+ "version": "2.3.30",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",