@lumenflow/cli 2.17.0 → 2.18.0

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.
Files changed (72) hide show
  1. package/README.md +24 -23
  2. package/dist/gates.js +56 -6
  3. package/dist/gates.js.map +1 -1
  4. package/dist/hooks/enforcement-generator.js +3 -3
  5. package/dist/init.js +1 -1
  6. package/dist/orchestrate-initiative.js +4 -3
  7. package/dist/orchestrate-initiative.js.map +1 -1
  8. package/dist/orchestrate-monitor.js +2 -1
  9. package/dist/orchestrate-monitor.js.map +1 -1
  10. package/dist/public-manifest.js +11 -4
  11. package/dist/public-manifest.js.map +1 -1
  12. package/dist/spawn-list.js +1 -0
  13. package/dist/spawn-list.js.map +1 -1
  14. package/dist/wu-block.js +89 -45
  15. package/dist/wu-block.js.map +1 -1
  16. package/dist/wu-brief.js +40 -0
  17. package/dist/wu-brief.js.map +1 -0
  18. package/dist/wu-claim-cloud.js +61 -0
  19. package/dist/wu-claim-cloud.js.map +1 -0
  20. package/dist/wu-claim.js +166 -57
  21. package/dist/wu-claim.js.map +1 -1
  22. package/dist/wu-cleanup-cloud.js +76 -0
  23. package/dist/wu-cleanup-cloud.js.map +1 -0
  24. package/dist/wu-cleanup.js +42 -19
  25. package/dist/wu-cleanup.js.map +1 -1
  26. package/dist/wu-create-cloud.js +40 -0
  27. package/dist/wu-create-cloud.js.map +1 -0
  28. package/dist/wu-create.js +137 -53
  29. package/dist/wu-create.js.map +1 -1
  30. package/dist/wu-delegate.js +21 -0
  31. package/dist/wu-delegate.js.map +1 -0
  32. package/dist/wu-delete.js +102 -61
  33. package/dist/wu-delete.js.map +1 -1
  34. package/dist/wu-done-auto-cleanup.js +5 -16
  35. package/dist/wu-done-auto-cleanup.js.map +1 -1
  36. package/dist/wu-done-cloud.js +46 -0
  37. package/dist/wu-done-cloud.js.map +1 -0
  38. package/dist/wu-done.js +153 -44
  39. package/dist/wu-done.js.map +1 -1
  40. package/dist/wu-edit.js +217 -55
  41. package/dist/wu-edit.js.map +1 -1
  42. package/dist/wu-prep.js +1 -1
  43. package/dist/wu-prep.js.map +1 -1
  44. package/dist/wu-recover.js +128 -55
  45. package/dist/wu-recover.js.map +1 -1
  46. package/dist/wu-release.js +99 -45
  47. package/dist/wu-release.js.map +1 -1
  48. package/dist/wu-spawn.js +108 -42
  49. package/dist/wu-spawn.js.map +1 -1
  50. package/dist/wu-state-cloud.js +39 -0
  51. package/dist/wu-state-cloud.js.map +1 -0
  52. package/dist/wu-unblock.js +99 -49
  53. package/dist/wu-unblock.js.map +1 -1
  54. package/package.json +8 -7
  55. package/templates/core/.lumenflow/constraints.md.template +31 -4
  56. package/templates/core/LUMENFLOW.md.template +31 -9
  57. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +21 -13
  58. package/templates/core/ai/onboarding/agent-safety-card.md.template +2 -2
  59. package/templates/core/ai/onboarding/docs-generation.md.template +1 -1
  60. package/templates/core/ai/onboarding/lumenflow-force-usage.md.template +1 -1
  61. package/templates/core/ai/onboarding/quick-ref-commands.md.template +235 -66
  62. package/templates/core/ai/onboarding/rapid-prototyping.md +2 -2
  63. package/templates/core/ai/onboarding/starting-prompt.md.template +124 -24
  64. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +11 -0
  65. package/templates/core/ai/onboarding/vendor-support.md.template +58 -69
  66. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +3 -3
  67. package/templates/vendors/claude/.claude/skills/design-first/SKILL.md.template +151 -0
  68. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +8 -8
  69. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +1 -0
  70. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +5 -5
  71. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +19 -16
  72. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +2 -0
package/dist/wu-claim.js CHANGED
@@ -33,14 +33,16 @@ import { validateLaneCodePaths, logLaneValidationWarnings, } from '@lumenflow/co
33
33
  // WU-1574: parseBacklogFrontmatter/getSectionHeadings removed - state store replaces backlog parsing
34
34
  import { detectConflicts } from '@lumenflow/core/code-paths-overlap';
35
35
  import { getGitForCwd, createGitForPath } from '@lumenflow/core/git-adapter';
36
- import { die } from '@lumenflow/core/error-handler';
36
+ import { die, getErrorMessage } from '@lumenflow/core/error-handler';
37
37
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/arg-parser';
38
38
  // WU-1491: Mode resolution for --cloud and flag combinations
39
39
  import { resolveClaimMode } from './wu-claim-mode.js';
40
+ // WU-1590: Cloud claim helpers for branch-pr/cloud execution behavior
41
+ import { shouldSkipBranchExistsCheck, resolveBranchClaimExecution } from './wu-claim-cloud.js';
40
42
  // WU-1495: Cloud auto-detection from config-driven env signals
41
- import { detectCloudMode } from '@lumenflow/core/cloud-detect';
43
+ import { detectCloudMode, resolveEffectiveCloudActivation, CLOUD_ACTIVATION_SOURCE, } from '@lumenflow/core/cloud-detect';
42
44
  import { WU_PATHS, getStateStoreDirFromBacklog } from '@lumenflow/core/wu-paths';
43
- import { BRANCHES, REMOTES, WU_STATUS, CLAIMED_MODES, STATUS_SECTIONS, PATTERNS, toKebab, LOG_PREFIX, GIT_REFS, MICRO_WORKTREE_OPERATIONS, COMMIT_FORMATS, EMOJI, FILE_SYSTEM, STRING_LITERALS, LUMENFLOW_PATHS, } from '@lumenflow/core/wu-constants';
45
+ import { BRANCHES, REMOTES, WU_STATUS, CLAIMED_MODES, STATUS_SECTIONS, PATTERNS, toKebab, LOG_PREFIX, GIT_REFS, MICRO_WORKTREE_OPERATIONS, COMMIT_FORMATS, EMOJI, FILE_SYSTEM, STRING_LITERALS, LUMENFLOW_PATHS, resolveWUStatus, } from '@lumenflow/core/wu-constants';
44
46
  import { withMicroWorktree } from '@lumenflow/core/micro-worktree';
45
47
  import { ensureOnMain, ensureMainUpToDate } from '@lumenflow/core/wu-helpers';
46
48
  import { emitWUFlowEvent } from '@lumenflow/core/telemetry';
@@ -90,7 +92,7 @@ async function surfaceUnreadSignalsForDisplay(baseDir) {
90
92
  }
91
93
  catch (err) {
92
94
  // WU-1473 AC4: Fail-open - never block claim on memory errors
93
- console.warn(`${PREFIX} Warning: Could not surface unread signals: ${err.message}`);
95
+ console.warn(`${PREFIX} Warning: Could not surface unread signals: ${getErrorMessage(err)}`);
94
96
  }
95
97
  }
96
98
  async function ensureCleanOrClaimOnlyWhenNoAuto() {
@@ -116,6 +118,23 @@ async function ensureCleanOrClaimOnlyWhenNoAuto() {
116
118
  }
117
119
  }
118
120
  const PREFIX = LOG_PREFIX.CLAIM;
121
+ /**
122
+ * Resolve branch-aware cloud activation for wu:claim.
123
+ *
124
+ * This preserves source attribution from detectCloudMode while enforcing
125
+ * protected-branch behavior for explicit vs env-signal activation.
126
+ */
127
+ export function resolveCloudActivationForClaim(input) {
128
+ const detection = detectCloudMode({
129
+ cloudFlag: input.cloudFlag,
130
+ env: input.env,
131
+ config: input.config,
132
+ });
133
+ return resolveEffectiveCloudActivation({
134
+ detection,
135
+ currentBranch: input.currentBranch,
136
+ });
137
+ }
119
138
  /**
120
139
  * WU-1508: Enforce tests.manual at claim time for non-doc/process WUs.
121
140
  * This is non-bypassable (independent of --allow-incomplete) to fail early.
@@ -133,6 +152,27 @@ export function validateManualTestsForClaim(doc, id) {
133
152
  `Add at least one manual verification step under tests.manual before claiming.`,
134
153
  };
135
154
  }
155
+ export function resolveClaimStatus(status) {
156
+ return resolveWUStatus(status, WU_STATUS.READY);
157
+ }
158
+ /**
159
+ * Decide whether wu:claim should update canonical state on origin/main.
160
+ *
161
+ * Cloud branch-pr claims run on platform-managed branches and should not mutate
162
+ * canonical state on main during claim; they commit claim metadata on their own branch.
163
+ */
164
+ export function shouldApplyCanonicalClaimUpdate(input) {
165
+ if (input.noPush) {
166
+ return false;
167
+ }
168
+ return !(input.isCloud && input.claimedMode === CLAIMED_MODES.BRANCH_PR);
169
+ }
170
+ /**
171
+ * Decide whether wu:claim should write claim metadata directly to the active branch.
172
+ */
173
+ export function shouldPersistClaimMetadataOnBranch(input) {
174
+ return input.noPush === true || input.claimedMode === CLAIMED_MODES.BRANCH_PR;
175
+ }
136
176
  /**
137
177
  * WU-1521: Build a rolled-back version of a WU YAML doc by stripping claim metadata.
138
178
  *
@@ -152,6 +192,7 @@ export function buildRollbackYamlDoc(doc) {
152
192
  rolled.status = WU_STATUS.READY;
153
193
  // Remove claim-specific metadata fields
154
194
  delete rolled.claimed_mode;
195
+ delete rolled.claimed_branch; // WU-1589: Clear claimed_branch on rollback
155
196
  delete rolled.claimed_at;
156
197
  delete rolled.worktree_path;
157
198
  delete rolled.baseline_main_sha;
@@ -181,7 +222,7 @@ function preflightValidateWU(WU_PATH, id) {
181
222
  }
182
223
  catch (e) {
183
224
  die(`Failed to parse WU YAML ${WU_PATH}\n\n` +
184
- `YAML parsing error: ${e.message}\n\n` +
225
+ `YAML parsing error: ${getErrorMessage(e)}\n\n` +
185
226
  `Fix the YAML syntax errors before claiming.`);
186
227
  }
187
228
  // Validate ID matches
@@ -192,7 +233,7 @@ function preflightValidateWU(WU_PATH, id) {
192
233
  `Fix the id field in the WU YAML before claiming.`);
193
234
  }
194
235
  // Validate state transition is allowed
195
- const currentStatus = doc.status || WU_STATUS.READY;
236
+ const currentStatus = resolveClaimStatus(doc.status);
196
237
  try {
197
238
  assertTransition(currentStatus, WU_STATUS.IN_PROGRESS, id);
198
239
  }
@@ -200,7 +241,7 @@ function preflightValidateWU(WU_PATH, id) {
200
241
  die(`Cannot claim ${id} - invalid state transition\n\n` +
201
242
  `Current status: ${currentStatus}\n` +
202
243
  `Attempted transition: ${currentStatus} → in_progress\n\n` +
203
- `Reason: ${error.message}`);
244
+ `Reason: ${getErrorMessage(error)}`);
204
245
  }
205
246
  return doc;
206
247
  }
@@ -252,7 +293,7 @@ function validateYAMLSchema(WU_PATH, doc, args) {
252
293
  }
253
294
  // WU-1576: validateBacklogConsistency removed - repair now happens inside micro-worktree
254
295
  // See claimWorktreeMode() execute function for the new location
255
- async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null, gitAdapter = null) {
296
+ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null, gitAdapter = null, claimedBranch = null) {
256
297
  // Check file exists
257
298
  try {
258
299
  await access(WU_PATH);
@@ -270,7 +311,7 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
270
311
  }
271
312
  catch (e) {
272
313
  die(`Failed to read WU file: ${WU_PATH}\n\n` +
273
- `Error: ${e.message}\n\n` +
314
+ `Error: ${getErrorMessage(e)}\n\n` +
274
315
  `Options:\n` +
275
316
  ` 1. Check file permissions: ls -la ${WU_PATH}\n` +
276
317
  ` 2. Ensure you have read access to the repository`);
@@ -281,7 +322,7 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
281
322
  }
282
323
  catch (e) {
283
324
  die(`Failed to parse YAML ${WU_PATH}\n\n` +
284
- `Error: ${e.message}\n\n` +
325
+ `Error: ${getErrorMessage(e)}\n\n` +
285
326
  `Options:\n` +
286
327
  ` 1. Validate YAML syntax: pnpm wu:validate --id ${id}\n` +
287
328
  ` 2. Fix YAML errors manually and retry`);
@@ -293,12 +334,12 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
293
334
  ` 2. Verify you're claiming the right WU`);
294
335
  }
295
336
  // Validate state transition before updating
296
- const currentStatus = doc.status || WU_STATUS.READY;
337
+ const currentStatus = resolveClaimStatus(doc.status);
297
338
  try {
298
339
  assertTransition(currentStatus, WU_STATUS.IN_PROGRESS, id);
299
340
  }
300
341
  catch (error) {
301
- die(`State transition validation failed: ${error.message}`);
342
+ die(`State transition validation failed: ${getErrorMessage(error)}`);
302
343
  }
303
344
  // Update status and lane (lane only if provided and different)
304
345
  doc.status = WU_STATUS.IN_PROGRESS;
@@ -306,6 +347,11 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
306
347
  doc.lane = lane;
307
348
  // Record claimed mode (worktree or branch-only)
308
349
  doc.claimed_mode = claimedMode;
350
+ // WU-1590: Persist claimed_branch for branch-pr cloud agents so downstream commands
351
+ // (wu:prep, wu:done, wu:cleanup) can resolve the actual branch via defaultBranchFrom()
352
+ if (claimedBranch) {
353
+ doc.claimed_branch = claimedBranch;
354
+ }
309
355
  // WU-1226: Record worktree path to prevent resolution failures if lane field changes
310
356
  if (worktreePath) {
311
357
  doc.worktree_path = worktreePath;
@@ -386,7 +432,7 @@ async function maybeProgressInitiativeStatus(worktreePath, initiativeRef, wuId)
386
432
  }
387
433
  catch (error) {
388
434
  // Non-fatal: log warning and continue
389
- console.warn(`${PREFIX} ⚠️ Could not check initiative status progression: ${error.message}`);
435
+ console.warn(`${PREFIX} ⚠️ Could not check initiative status progression: ${getErrorMessage(error)}`);
390
436
  return { updated: false, initPath: null };
391
437
  }
392
438
  }
@@ -526,7 +572,8 @@ async function applyStagedChangesToMicroWorktree(worktreePath, stagedChanges) {
526
572
  * Ensures canonical state stays global while local main remains unchanged.
527
573
  */
528
574
  async function applyCanonicalClaimUpdate(ctx, sessionId) {
529
- const { args, id, laneK, worktree, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, fixableIssues, stagedChanges, } = ctx;
575
+ const { args, id, laneK, worktree, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, fixableIssues, stagedChanges, currentBranchForCloud, // WU-1590: For persisting claimed_branch
576
+ } = ctx;
530
577
  const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
531
578
  const worktreePathForYaml = claimedMode === CLAIMED_MODES.BRANCH_ONLY ? null : path.resolve(worktree);
532
579
  let updatedTitle = '';
@@ -554,7 +601,7 @@ async function applyCanonicalClaimUpdate(ctx, sessionId) {
554
601
  }
555
602
  const microGit = createGitForPath(worktreePath);
556
603
  // WU-1211: updateWUYaml now returns {title, initiative}
557
- const updateResult = await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit);
604
+ const updateResult = await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit, currentBranchForCloud || null);
558
605
  updatedTitle = updateResult.title || updatedTitle;
559
606
  await addOrReplaceInProgressStatus(microStatusPath, id, updatedTitle);
560
607
  await removeFromReadyAndAddToInProgressBacklog(microBacklogPath, id, updatedTitle, args.lane);
@@ -790,7 +837,7 @@ function validateLaneFormatWithError(lane) {
790
837
  validateLaneFormat(lane);
791
838
  }
792
839
  catch (error) {
793
- die(`Invalid lane format: ${error.message}\n\n` +
840
+ die(`Invalid lane format: ${getErrorMessage(error)}\n\n` +
794
841
  `Valid formats:\n` +
795
842
  ` - Parent-only: "Operations", "Intelligence", "Experience", etc.\n` +
796
843
  ` - Sub-lane: "Operations: Tooling", "Intelligence: Prompts", etc.\n\n` +
@@ -917,28 +964,42 @@ async function validateBranchOnlyMode(STATUS_PATH, id) {
917
964
  * Execute branch-only mode claim workflow
918
965
  */
919
966
  async function claimBranchOnlyMode(ctx) {
920
- const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, sessionId, updatedTitle, } = ctx;
921
- // Create branch and switch to it from origin/main (avoids local main mutation)
922
- try {
923
- await getGitForCwd().createBranch(branch, `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
967
+ const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, shouldCreateBranch, currentBranch, sessionId, updatedTitle, currentBranchForCloud, // WU-1590: For persisting claimed_branch
968
+ } = ctx;
969
+ if (shouldCreateBranch) {
970
+ // Create branch and switch to it from origin/main (avoids local main mutation)
971
+ try {
972
+ await getGitForCwd().createBranch(branch, `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
973
+ }
974
+ catch (error) {
975
+ die(`Canonical claim state may be updated, but branch creation failed.\n\n` +
976
+ `Error: ${getErrorMessage(error)}\n\n` +
977
+ `Recovery:\n` +
978
+ ` 1. Run: git fetch ${REMOTES.ORIGIN} ${BRANCHES.MAIN}\n` +
979
+ ` 2. Retry: pnpm wu:claim --id ${id} --lane "${args.lane}"\n` +
980
+ ` 3. If needed, delete local branch: git branch -D ${branch}`);
981
+ }
924
982
  }
925
- catch (error) {
926
- die(`Canonical claim state may be updated, but branch creation failed.\n\n` +
927
- `Error: ${error.message}\n\n` +
928
- `Recovery:\n` +
929
- ` 1. Run: git fetch ${REMOTES.ORIGIN} ${BRANCHES.MAIN}\n` +
930
- ` 2. Retry: pnpm wu:claim --id ${id} --lane "${args.lane}"\n` +
931
- ` 3. If needed, delete local branch: git branch -D ${branch}`);
983
+ else if (currentBranch !== branch) {
984
+ die(`Cloud branch-pr claim must run on the active branch.\n\n` +
985
+ `Current branch: ${currentBranch}\n` +
986
+ `Resolved branch: ${branch}\n\n` +
987
+ `Switch to ${branch} and retry, or omit conflicting --branch flags.`);
932
988
  }
933
989
  let finalTitle = updatedTitle || title;
934
990
  const msg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
935
- if (args.noPush) {
991
+ const shouldPersistClaimMetadata = shouldPersistClaimMetadataOnBranch({
992
+ claimedMode,
993
+ noPush: Boolean(args.noPush),
994
+ });
995
+ if (shouldPersistClaimMetadata) {
936
996
  if (args.noAuto) {
937
997
  await ensureCleanOrClaimOnlyWhenNoAuto();
938
998
  }
939
999
  else {
940
1000
  // WU-1211: updateWUYaml now returns {title, initiative}
941
- const updateResult = await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId);
1001
+ // WU-1590: Pass claimed_branch for branch-pr persistence
1002
+ const updateResult = await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId, null, currentBranchForCloud || null);
942
1003
  finalTitle = updateResult.title || finalTitle;
943
1004
  await addOrReplaceInProgressStatus(STATUS_PATH, id, finalTitle);
944
1005
  await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, finalTitle, args.lane);
@@ -950,9 +1011,11 @@ async function claimBranchOnlyMode(ctx) {
950
1011
  filesToAdd.push(initProgress.initPath);
951
1012
  }
952
1013
  }
953
- await getGitForCwd().add(filesToAdd.map((f) => JSON.stringify(f)).join(' '));
1014
+ await getGitForCwd().add(filesToAdd);
954
1015
  }
955
1016
  await getGitForCwd().commit(msg);
1017
+ }
1018
+ if (args.noPush) {
956
1019
  console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
957
1020
  }
958
1021
  else {
@@ -1323,6 +1386,33 @@ async function main() {
1323
1386
  if (!PATTERNS.WU_ID.test(id))
1324
1387
  die(`Invalid WU id '${args.id}'. Expected format WU-123`);
1325
1388
  await ensureOnMain(getGitForCwd());
1389
+ // WU-1609: Resolve branch-aware cloud activation at preflight so explicit
1390
+ // protected-branch cloud requests fail before lane locking/state mutation.
1391
+ const preflightBranch = await getGitForCwd().getCurrentBranch();
1392
+ const preflightCloudEffective = resolveCloudActivationForClaim({
1393
+ cloudFlag: Boolean(args.cloud),
1394
+ env: process.env,
1395
+ config: getConfig().cloud,
1396
+ currentBranch: preflightBranch,
1397
+ });
1398
+ if (preflightCloudEffective.blocked) {
1399
+ const sourceHint = preflightCloudEffective.source === CLOUD_ACTIVATION_SOURCE.FLAG
1400
+ ? '--cloud'
1401
+ : 'LUMENFLOW_CLOUD=1';
1402
+ die(`Cloud mode blocked on protected branch "${preflightBranch}".\n\n` +
1403
+ `Explicit cloud activation (${sourceHint}) is not allowed on main/master.\n` +
1404
+ `Switch to a non-main branch for cloud mode, or run wu:claim without cloud activation on main/master.`);
1405
+ }
1406
+ if (preflightCloudEffective.suppressed) {
1407
+ const signalSuffix = preflightCloudEffective.matchedSignal
1408
+ ? ` (signal: ${preflightCloudEffective.matchedSignal})`
1409
+ : '';
1410
+ console.log(`${PREFIX} Cloud auto-detection suppressed on protected branch "${preflightBranch}"${signalSuffix}; continuing with standard claim flow.`);
1411
+ }
1412
+ else if (preflightCloudEffective.isCloud &&
1413
+ preflightCloudEffective.source === CLOUD_ACTIVATION_SOURCE.ENV_SIGNAL) {
1414
+ console.log(`${PREFIX} Cloud mode auto-detected (source: ${preflightCloudEffective.source}${preflightCloudEffective.matchedSignal ? `, signal: ${preflightCloudEffective.matchedSignal}` : ''})`);
1415
+ }
1326
1416
  // WU-2411: Handle --resume flag for agent handoff
1327
1417
  if (args.resume) {
1328
1418
  await handleResumeMode(args, id);
@@ -1449,18 +1539,11 @@ async function main() {
1449
1539
  const title = (await readWUTitle(id)) || '';
1450
1540
  const branch = args.branch || `lane/${laneK}/${idK}`;
1451
1541
  const worktree = args.worktree || `worktrees/${laneK}-${idK}`;
1452
- // WU-1495: Cloud auto-detection from config-driven env signals
1453
- // Detection precedence: --cloud flag > LUMENFLOW_CLOUD=1 > env_signals (opt-in)
1454
- const config = getConfig();
1455
- const cloudDetection = detectCloudMode({
1456
- cloudFlag: Boolean(args.cloud),
1457
- env: process.env,
1458
- config: config.cloud,
1459
- });
1460
- const effectiveCloud = cloudDetection.isCloud;
1461
- if (cloudDetection.isCloud && !args.cloud) {
1462
- console.log(`${PREFIX} Cloud mode auto-detected (source: ${cloudDetection.source}${cloudDetection.matchedSignal ? `, signal: ${cloudDetection.matchedSignal}` : ''})`);
1463
- }
1542
+ const currentBranch = preflightBranch;
1543
+ const cloudEffective = preflightCloudEffective;
1544
+ const effectiveCloud = cloudEffective.isCloud;
1545
+ // WU-1590: Capture current branch for cloud claim metadata (before any branch switching)
1546
+ const currentBranchForCloud = effectiveCloud ? currentBranch : undefined;
1464
1547
  // WU-1491: Resolve claimed mode from flag combination
1465
1548
  const modeResult = resolveClaimMode({
1466
1549
  branchOnly: args.branchOnly,
@@ -1476,11 +1559,24 @@ async function main() {
1476
1559
  if (!modeResult.skipBranchOnlySingletonGuard) {
1477
1560
  await validateBranchOnlyMode(STATUS_PATH, id);
1478
1561
  }
1562
+ // WU-1590: Skip branch-exists checks in cloud mode (branch already exists by definition)
1563
+ const branchExecution = resolveBranchClaimExecution({
1564
+ claimedMode,
1565
+ isCloud: effectiveCloud,
1566
+ currentBranch,
1567
+ requestedBranch: branch,
1568
+ });
1569
+ const effectiveBranch = branchExecution.executionBranch;
1570
+ const skipBranchChecks = shouldSkipBranchExistsCheck({
1571
+ isCloud: effectiveCloud,
1572
+ currentBranch,
1573
+ laneBranch: effectiveBranch,
1574
+ });
1479
1575
  // Check if remote branch already exists (prevents duplicate global claims)
1480
- if (!args.noPush) {
1481
- const remoteExists = await getGitForCwd().remoteBranchExists(REMOTES.ORIGIN, branch);
1576
+ if (!args.noPush && !skipBranchChecks) {
1577
+ const remoteExists = await getGitForCwd().remoteBranchExists(REMOTES.ORIGIN, effectiveBranch);
1482
1578
  if (remoteExists) {
1483
- die(`Remote branch ${REMOTES.ORIGIN}/${branch} already exists. WU may already be claimed.\n\n` +
1579
+ die(`Remote branch ${REMOTES.ORIGIN}/${effectiveBranch} already exists. WU may already be claimed.\n\n` +
1484
1580
  `Options:\n` +
1485
1581
  ` 1. Coordinate with the owning agent or wait for completion\n` +
1486
1582
  ` 2. Choose a different WU\n` +
@@ -1488,14 +1584,16 @@ async function main() {
1488
1584
  }
1489
1585
  }
1490
1586
  // Check if branch already exists locally (prevents duplicate claims)
1491
- const branchAlreadyExists = await getGitForCwd().branchExists(branch);
1492
- if (branchAlreadyExists) {
1493
- die(`Branch ${branch} already exists. WU may already be claimed.\n\n` +
1494
- `Git branch existence = WU claimed (natural locking).\n\n` +
1495
- `Options:\n` +
1496
- ` 1. Check git worktree list to see if worktree exists\n` +
1497
- ` 2. Coordinate with the owning agent or wait for them to complete\n` +
1498
- ` 3. Choose a different WU`);
1587
+ if (!skipBranchChecks) {
1588
+ const branchAlreadyExists = await getGitForCwd().branchExists(effectiveBranch);
1589
+ if (branchAlreadyExists) {
1590
+ die(`Branch ${effectiveBranch} already exists. WU may already be claimed.\n\n` +
1591
+ `Git branch existence = WU claimed (natural locking).\n\n` +
1592
+ `Options:\n` +
1593
+ ` 1. Check git worktree list to see if worktree exists\n` +
1594
+ ` 2. Coordinate with the owning agent or wait for them to complete\n` +
1595
+ ` 3. Choose a different WU`);
1596
+ }
1499
1597
  }
1500
1598
  // Layer 3 defense (WU-1476): Pre-flight orphan check
1501
1599
  // Clean up orphan directory if it exists at target worktree path
@@ -1508,7 +1606,7 @@ async function main() {
1508
1606
  }
1509
1607
  catch (err) {
1510
1608
  die(`Failed to clean up orphan directory at ${worktree}\n\n` +
1511
- `Error: ${err.message}\n\n` +
1609
+ `Error: ${getErrorMessage(err)}\n\n` +
1512
1610
  `Manual cleanup: rm -rf ${absoluteWorktreePath}`);
1513
1611
  }
1514
1612
  }
@@ -1529,7 +1627,7 @@ async function main() {
1529
1627
  }
1530
1628
  catch (err) {
1531
1629
  // Non-blocking: session start failure should not block claim
1532
- console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
1630
+ console.warn(`${PREFIX} Warning: Could not start agent session: ${getErrorMessage(err)}`);
1533
1631
  }
1534
1632
  // Execute claim workflow
1535
1633
  const baseCtx = {
@@ -1537,18 +1635,26 @@ async function main() {
1537
1635
  id,
1538
1636
  laneK,
1539
1637
  title,
1540
- branch,
1638
+ branch: effectiveBranch,
1541
1639
  worktree,
1542
1640
  WU_PATH,
1543
1641
  STATUS_PATH,
1544
1642
  BACKLOG_PATH,
1545
1643
  claimedMode,
1644
+ shouldCreateBranch: branchExecution.shouldCreateBranch,
1645
+ currentBranch,
1546
1646
  fixableIssues, // WU-1361: Pass fixable issues for worktree application
1547
1647
  stagedChanges,
1648
+ currentBranchForCloud, // WU-1590: For persisting claimed_branch in branch-pr mode
1548
1649
  };
1549
1650
  let updatedTitle = title;
1550
1651
  claimTitle = title;
1551
- if (!args.noPush) {
1652
+ const shouldApplyCanonicalUpdate = shouldApplyCanonicalClaimUpdate({
1653
+ isCloud: effectiveCloud,
1654
+ claimedMode,
1655
+ noPush: Boolean(args.noPush),
1656
+ });
1657
+ if (shouldApplyCanonicalUpdate) {
1552
1658
  updatedTitle = (await applyCanonicalClaimUpdate(baseCtx, sessionId)) || updatedTitle;
1553
1659
  // WU-1521: Mark that canonical claim was pushed to origin/main
1554
1660
  // If claim fails after this point, the finally block will rollback
@@ -1557,6 +1663,9 @@ async function main() {
1557
1663
  // Refresh origin/main after push-only update so worktrees start from canonical state
1558
1664
  await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
1559
1665
  }
1666
+ else if (!args.noPush && claimedMode === CLAIMED_MODES.BRANCH_PR) {
1667
+ console.log(`${PREFIX} Skipping canonical claim update on origin/main for cloud branch-pr claim.`);
1668
+ }
1560
1669
  const ctx = {
1561
1670
  ...baseCtx,
1562
1671
  sessionId,