@lumenflow/cli 2.17.0 → 2.18.1
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/README.md +24 -23
- package/dist/gates.js +56 -6
- package/dist/gates.js.map +1 -1
- package/dist/hooks/enforcement-generator.js +3 -3
- package/dist/init.js +1 -1
- package/dist/orchestrate-initiative.js +4 -3
- package/dist/orchestrate-initiative.js.map +1 -1
- package/dist/orchestrate-monitor.js +2 -1
- package/dist/orchestrate-monitor.js.map +1 -1
- package/dist/public-manifest.js +11 -4
- package/dist/public-manifest.js.map +1 -1
- package/dist/spawn-list.js +1 -0
- package/dist/spawn-list.js.map +1 -1
- package/dist/wu-block.js +89 -45
- package/dist/wu-block.js.map +1 -1
- package/dist/wu-brief.js +40 -0
- package/dist/wu-brief.js.map +1 -0
- package/dist/wu-claim-cloud.js +61 -0
- package/dist/wu-claim-cloud.js.map +1 -0
- package/dist/wu-claim.js +234 -57
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-cleanup-cloud.js +76 -0
- package/dist/wu-cleanup-cloud.js.map +1 -0
- package/dist/wu-cleanup.js +42 -19
- package/dist/wu-cleanup.js.map +1 -1
- package/dist/wu-create-cloud.js +40 -0
- package/dist/wu-create-cloud.js.map +1 -0
- package/dist/wu-create.js +137 -53
- package/dist/wu-create.js.map +1 -1
- package/dist/wu-delegate.js +21 -0
- package/dist/wu-delegate.js.map +1 -0
- package/dist/wu-delete.js +102 -61
- package/dist/wu-delete.js.map +1 -1
- package/dist/wu-done-auto-cleanup.js +5 -16
- package/dist/wu-done-auto-cleanup.js.map +1 -1
- package/dist/wu-done-cloud.js +46 -0
- package/dist/wu-done-cloud.js.map +1 -0
- package/dist/wu-done.js +186 -44
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-edit.js +217 -55
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-prep.js +1 -1
- package/dist/wu-prep.js.map +1 -1
- package/dist/wu-recover.js +128 -55
- package/dist/wu-recover.js.map +1 -1
- package/dist/wu-release.js +99 -45
- package/dist/wu-release.js.map +1 -1
- package/dist/wu-spawn.js +108 -42
- package/dist/wu-spawn.js.map +1 -1
- package/dist/wu-state-cloud.js +39 -0
- package/dist/wu-state-cloud.js.map +1 -0
- package/dist/wu-unblock.js +99 -49
- package/dist/wu-unblock.js.map +1 -1
- package/package.json +8 -7
- package/templates/core/.lumenflow/constraints.md.template +31 -4
- package/templates/core/LUMENFLOW.md.template +31 -9
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +21 -13
- package/templates/core/ai/onboarding/agent-safety-card.md.template +2 -2
- package/templates/core/ai/onboarding/docs-generation.md.template +1 -1
- package/templates/core/ai/onboarding/lumenflow-force-usage.md.template +1 -1
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +235 -66
- package/templates/core/ai/onboarding/rapid-prototyping.md +2 -2
- package/templates/core/ai/onboarding/starting-prompt.md.template +124 -24
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +11 -0
- package/templates/core/ai/onboarding/vendor-support.md.template +58 -69
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +3 -3
- package/templates/vendors/claude/.claude/skills/design-first/SKILL.md.template +151 -0
- package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +8 -8
- package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +1 -0
- package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +5 -5
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +19 -16
- 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';
|
|
@@ -58,6 +60,7 @@ import { getAssignedEmail } from '@lumenflow/core/wu-claim-helpers';
|
|
|
58
60
|
import { symlinkNodeModules, symlinkNestedNodeModules } from '@lumenflow/core/worktree-symlink';
|
|
59
61
|
// WU-1572: Import WUStateStore for event-sourced state tracking
|
|
60
62
|
import { WUStateStore } from '@lumenflow/core/wu-state-store';
|
|
63
|
+
import { SpawnRegistryStore } from '@lumenflow/core/spawn-registry-store';
|
|
61
64
|
// WU-1574: Import backlog generator to replace BacklogManager
|
|
62
65
|
import { generateBacklog, generateStatus } from '@lumenflow/core/backlog-generator';
|
|
63
66
|
// WU-2411: Import resume helpers for agent handoff
|
|
@@ -90,7 +93,7 @@ async function surfaceUnreadSignalsForDisplay(baseDir) {
|
|
|
90
93
|
}
|
|
91
94
|
catch (err) {
|
|
92
95
|
// WU-1473 AC4: Fail-open - never block claim on memory errors
|
|
93
|
-
console.warn(`${PREFIX} Warning: Could not surface unread signals: ${err
|
|
96
|
+
console.warn(`${PREFIX} Warning: Could not surface unread signals: ${getErrorMessage(err)}`);
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
async function ensureCleanOrClaimOnlyWhenNoAuto() {
|
|
@@ -116,6 +119,23 @@ async function ensureCleanOrClaimOnlyWhenNoAuto() {
|
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
const PREFIX = LOG_PREFIX.CLAIM;
|
|
122
|
+
/**
|
|
123
|
+
* Resolve branch-aware cloud activation for wu:claim.
|
|
124
|
+
*
|
|
125
|
+
* This preserves source attribution from detectCloudMode while enforcing
|
|
126
|
+
* protected-branch behavior for explicit vs env-signal activation.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveCloudActivationForClaim(input) {
|
|
129
|
+
const detection = detectCloudMode({
|
|
130
|
+
cloudFlag: input.cloudFlag,
|
|
131
|
+
env: input.env,
|
|
132
|
+
config: input.config,
|
|
133
|
+
});
|
|
134
|
+
return resolveEffectiveCloudActivation({
|
|
135
|
+
detection,
|
|
136
|
+
currentBranch: input.currentBranch,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
119
139
|
/**
|
|
120
140
|
* WU-1508: Enforce tests.manual at claim time for non-doc/process WUs.
|
|
121
141
|
* This is non-bypassable (independent of --allow-incomplete) to fail early.
|
|
@@ -133,6 +153,27 @@ export function validateManualTestsForClaim(doc, id) {
|
|
|
133
153
|
`Add at least one manual verification step under tests.manual before claiming.`,
|
|
134
154
|
};
|
|
135
155
|
}
|
|
156
|
+
export function resolveClaimStatus(status) {
|
|
157
|
+
return resolveWUStatus(status, WU_STATUS.READY);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Decide whether wu:claim should update canonical state on origin/main.
|
|
161
|
+
*
|
|
162
|
+
* Cloud branch-pr claims run on platform-managed branches and should not mutate
|
|
163
|
+
* canonical state on main during claim; they commit claim metadata on their own branch.
|
|
164
|
+
*/
|
|
165
|
+
export function shouldApplyCanonicalClaimUpdate(input) {
|
|
166
|
+
if (input.noPush) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
return !(input.isCloud && input.claimedMode === CLAIMED_MODES.BRANCH_PR);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Decide whether wu:claim should write claim metadata directly to the active branch.
|
|
173
|
+
*/
|
|
174
|
+
export function shouldPersistClaimMetadataOnBranch(input) {
|
|
175
|
+
return input.noPush === true || input.claimedMode === CLAIMED_MODES.BRANCH_PR;
|
|
176
|
+
}
|
|
136
177
|
/**
|
|
137
178
|
* WU-1521: Build a rolled-back version of a WU YAML doc by stripping claim metadata.
|
|
138
179
|
*
|
|
@@ -152,6 +193,7 @@ export function buildRollbackYamlDoc(doc) {
|
|
|
152
193
|
rolled.status = WU_STATUS.READY;
|
|
153
194
|
// Remove claim-specific metadata fields
|
|
154
195
|
delete rolled.claimed_mode;
|
|
196
|
+
delete rolled.claimed_branch; // WU-1589: Clear claimed_branch on rollback
|
|
155
197
|
delete rolled.claimed_at;
|
|
156
198
|
delete rolled.worktree_path;
|
|
157
199
|
delete rolled.baseline_main_sha;
|
|
@@ -159,6 +201,49 @@ export function buildRollbackYamlDoc(doc) {
|
|
|
159
201
|
delete rolled.assigned_to;
|
|
160
202
|
return rolled;
|
|
161
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Returns true when a spawn record includes claim-time pickup evidence.
|
|
206
|
+
*/
|
|
207
|
+
export function hasClaimPickupEvidence(entry) {
|
|
208
|
+
const pickedUpAt = typeof entry?.pickedUpAt === 'string' && entry.pickedUpAt.trim().length > 0
|
|
209
|
+
? entry.pickedUpAt
|
|
210
|
+
: '';
|
|
211
|
+
const pickedUpBy = typeof entry?.pickedUpBy === 'string' && entry.pickedUpBy.trim().length > 0
|
|
212
|
+
? entry.pickedUpBy
|
|
213
|
+
: '';
|
|
214
|
+
return pickedUpAt.length > 0 && pickedUpBy.length > 0;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* WU-1605: Record delegated pickup evidence at wu:claim time when a spawn/delegate
|
|
218
|
+
* provenance record already exists for this target WU.
|
|
219
|
+
*/
|
|
220
|
+
export async function recordClaimPickupEvidence(id, options = {}) {
|
|
221
|
+
const baseDir = options.baseDir ?? process.cwd();
|
|
222
|
+
const claimedBy = typeof options.claimedBy === 'string' && options.claimedBy.trim().length > 0
|
|
223
|
+
? options.claimedBy.trim()
|
|
224
|
+
: 'unknown';
|
|
225
|
+
const store = new SpawnRegistryStore(path.join(baseDir, '.lumenflow', 'state'));
|
|
226
|
+
await store.load();
|
|
227
|
+
const spawnEntry = store.getByTarget(id);
|
|
228
|
+
if (!spawnEntry) {
|
|
229
|
+
return { matchedSpawn: false, recorded: false, alreadyRecorded: false };
|
|
230
|
+
}
|
|
231
|
+
if (hasClaimPickupEvidence(spawnEntry)) {
|
|
232
|
+
return {
|
|
233
|
+
matchedSpawn: true,
|
|
234
|
+
recorded: false,
|
|
235
|
+
alreadyRecorded: true,
|
|
236
|
+
spawnId: spawnEntry.id,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
await store.recordPickup(spawnEntry.id, claimedBy);
|
|
240
|
+
return {
|
|
241
|
+
matchedSpawn: true,
|
|
242
|
+
recorded: true,
|
|
243
|
+
alreadyRecorded: false,
|
|
244
|
+
spawnId: spawnEntry.id,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
162
247
|
/**
|
|
163
248
|
* Pre-flight validation: Check WU file exists and is valid BEFORE any git operations
|
|
164
249
|
* Prevents zombie worktrees when WU YAML is missing or malformed
|
|
@@ -181,7 +266,7 @@ function preflightValidateWU(WU_PATH, id) {
|
|
|
181
266
|
}
|
|
182
267
|
catch (e) {
|
|
183
268
|
die(`Failed to parse WU YAML ${WU_PATH}\n\n` +
|
|
184
|
-
`YAML parsing error: ${e
|
|
269
|
+
`YAML parsing error: ${getErrorMessage(e)}\n\n` +
|
|
185
270
|
`Fix the YAML syntax errors before claiming.`);
|
|
186
271
|
}
|
|
187
272
|
// Validate ID matches
|
|
@@ -192,7 +277,7 @@ function preflightValidateWU(WU_PATH, id) {
|
|
|
192
277
|
`Fix the id field in the WU YAML before claiming.`);
|
|
193
278
|
}
|
|
194
279
|
// Validate state transition is allowed
|
|
195
|
-
const currentStatus = doc.status
|
|
280
|
+
const currentStatus = resolveClaimStatus(doc.status);
|
|
196
281
|
try {
|
|
197
282
|
assertTransition(currentStatus, WU_STATUS.IN_PROGRESS, id);
|
|
198
283
|
}
|
|
@@ -200,7 +285,7 @@ function preflightValidateWU(WU_PATH, id) {
|
|
|
200
285
|
die(`Cannot claim ${id} - invalid state transition\n\n` +
|
|
201
286
|
`Current status: ${currentStatus}\n` +
|
|
202
287
|
`Attempted transition: ${currentStatus} → in_progress\n\n` +
|
|
203
|
-
`Reason: ${error
|
|
288
|
+
`Reason: ${getErrorMessage(error)}`);
|
|
204
289
|
}
|
|
205
290
|
return doc;
|
|
206
291
|
}
|
|
@@ -252,7 +337,7 @@ function validateYAMLSchema(WU_PATH, doc, args) {
|
|
|
252
337
|
}
|
|
253
338
|
// WU-1576: validateBacklogConsistency removed - repair now happens inside micro-worktree
|
|
254
339
|
// See claimWorktreeMode() execute function for the new location
|
|
255
|
-
async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null, gitAdapter = null) {
|
|
340
|
+
async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null, gitAdapter = null, claimedBranch = null) {
|
|
256
341
|
// Check file exists
|
|
257
342
|
try {
|
|
258
343
|
await access(WU_PATH);
|
|
@@ -270,7 +355,7 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
270
355
|
}
|
|
271
356
|
catch (e) {
|
|
272
357
|
die(`Failed to read WU file: ${WU_PATH}\n\n` +
|
|
273
|
-
`Error: ${e
|
|
358
|
+
`Error: ${getErrorMessage(e)}\n\n` +
|
|
274
359
|
`Options:\n` +
|
|
275
360
|
` 1. Check file permissions: ls -la ${WU_PATH}\n` +
|
|
276
361
|
` 2. Ensure you have read access to the repository`);
|
|
@@ -281,7 +366,7 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
281
366
|
}
|
|
282
367
|
catch (e) {
|
|
283
368
|
die(`Failed to parse YAML ${WU_PATH}\n\n` +
|
|
284
|
-
`Error: ${e
|
|
369
|
+
`Error: ${getErrorMessage(e)}\n\n` +
|
|
285
370
|
`Options:\n` +
|
|
286
371
|
` 1. Validate YAML syntax: pnpm wu:validate --id ${id}\n` +
|
|
287
372
|
` 2. Fix YAML errors manually and retry`);
|
|
@@ -293,12 +378,12 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
293
378
|
` 2. Verify you're claiming the right WU`);
|
|
294
379
|
}
|
|
295
380
|
// Validate state transition before updating
|
|
296
|
-
const currentStatus = doc.status
|
|
381
|
+
const currentStatus = resolveClaimStatus(doc.status);
|
|
297
382
|
try {
|
|
298
383
|
assertTransition(currentStatus, WU_STATUS.IN_PROGRESS, id);
|
|
299
384
|
}
|
|
300
385
|
catch (error) {
|
|
301
|
-
die(`State transition validation failed: ${error
|
|
386
|
+
die(`State transition validation failed: ${getErrorMessage(error)}`);
|
|
302
387
|
}
|
|
303
388
|
// Update status and lane (lane only if provided and different)
|
|
304
389
|
doc.status = WU_STATUS.IN_PROGRESS;
|
|
@@ -306,6 +391,11 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
306
391
|
doc.lane = lane;
|
|
307
392
|
// Record claimed mode (worktree or branch-only)
|
|
308
393
|
doc.claimed_mode = claimedMode;
|
|
394
|
+
// WU-1590: Persist claimed_branch for branch-pr cloud agents so downstream commands
|
|
395
|
+
// (wu:prep, wu:done, wu:cleanup) can resolve the actual branch via defaultBranchFrom()
|
|
396
|
+
if (claimedBranch) {
|
|
397
|
+
doc.claimed_branch = claimedBranch;
|
|
398
|
+
}
|
|
309
399
|
// WU-1226: Record worktree path to prevent resolution failures if lane field changes
|
|
310
400
|
if (worktreePath) {
|
|
311
401
|
doc.worktree_path = worktreePath;
|
|
@@ -386,7 +476,7 @@ async function maybeProgressInitiativeStatus(worktreePath, initiativeRef, wuId)
|
|
|
386
476
|
}
|
|
387
477
|
catch (error) {
|
|
388
478
|
// Non-fatal: log warning and continue
|
|
389
|
-
console.warn(`${PREFIX} ⚠️ Could not check initiative status progression: ${error
|
|
479
|
+
console.warn(`${PREFIX} ⚠️ Could not check initiative status progression: ${getErrorMessage(error)}`);
|
|
390
480
|
return { updated: false, initPath: null };
|
|
391
481
|
}
|
|
392
482
|
}
|
|
@@ -526,7 +616,8 @@ async function applyStagedChangesToMicroWorktree(worktreePath, stagedChanges) {
|
|
|
526
616
|
* Ensures canonical state stays global while local main remains unchanged.
|
|
527
617
|
*/
|
|
528
618
|
async function applyCanonicalClaimUpdate(ctx, sessionId) {
|
|
529
|
-
const { args, id, laneK, worktree, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, fixableIssues, stagedChanges,
|
|
619
|
+
const { args, id, laneK, worktree, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, fixableIssues, stagedChanges, currentBranchForCloud, // WU-1590: For persisting claimed_branch
|
|
620
|
+
} = ctx;
|
|
530
621
|
const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
|
|
531
622
|
const worktreePathForYaml = claimedMode === CLAIMED_MODES.BRANCH_ONLY ? null : path.resolve(worktree);
|
|
532
623
|
let updatedTitle = '';
|
|
@@ -554,7 +645,7 @@ async function applyCanonicalClaimUpdate(ctx, sessionId) {
|
|
|
554
645
|
}
|
|
555
646
|
const microGit = createGitForPath(worktreePath);
|
|
556
647
|
// WU-1211: updateWUYaml now returns {title, initiative}
|
|
557
|
-
const updateResult = await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit);
|
|
648
|
+
const updateResult = await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit, currentBranchForCloud || null);
|
|
558
649
|
updatedTitle = updateResult.title || updatedTitle;
|
|
559
650
|
await addOrReplaceInProgressStatus(microStatusPath, id, updatedTitle);
|
|
560
651
|
await removeFromReadyAndAddToInProgressBacklog(microBacklogPath, id, updatedTitle, args.lane);
|
|
@@ -790,7 +881,7 @@ function validateLaneFormatWithError(lane) {
|
|
|
790
881
|
validateLaneFormat(lane);
|
|
791
882
|
}
|
|
792
883
|
catch (error) {
|
|
793
|
-
die(`Invalid lane format: ${error
|
|
884
|
+
die(`Invalid lane format: ${getErrorMessage(error)}\n\n` +
|
|
794
885
|
`Valid formats:\n` +
|
|
795
886
|
` - Parent-only: "Operations", "Intelligence", "Experience", etc.\n` +
|
|
796
887
|
` - Sub-lane: "Operations: Tooling", "Intelligence: Prompts", etc.\n\n` +
|
|
@@ -917,28 +1008,42 @@ async function validateBranchOnlyMode(STATUS_PATH, id) {
|
|
|
917
1008
|
* Execute branch-only mode claim workflow
|
|
918
1009
|
*/
|
|
919
1010
|
async function claimBranchOnlyMode(ctx) {
|
|
920
|
-
const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, sessionId, updatedTitle,
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1011
|
+
const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, shouldCreateBranch, currentBranch, sessionId, updatedTitle, currentBranchForCloud, // WU-1590: For persisting claimed_branch
|
|
1012
|
+
} = ctx;
|
|
1013
|
+
if (shouldCreateBranch) {
|
|
1014
|
+
// Create branch and switch to it from origin/main (avoids local main mutation)
|
|
1015
|
+
try {
|
|
1016
|
+
await getGitForCwd().createBranch(branch, `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
die(`Canonical claim state may be updated, but branch creation failed.\n\n` +
|
|
1020
|
+
`Error: ${getErrorMessage(error)}\n\n` +
|
|
1021
|
+
`Recovery:\n` +
|
|
1022
|
+
` 1. Run: git fetch ${REMOTES.ORIGIN} ${BRANCHES.MAIN}\n` +
|
|
1023
|
+
` 2. Retry: pnpm wu:claim --id ${id} --lane "${args.lane}"\n` +
|
|
1024
|
+
` 3. If needed, delete local branch: git branch -D ${branch}`);
|
|
1025
|
+
}
|
|
924
1026
|
}
|
|
925
|
-
|
|
926
|
-
die(`
|
|
927
|
-
`
|
|
928
|
-
`
|
|
929
|
-
`
|
|
930
|
-
` 2. Retry: pnpm wu:claim --id ${id} --lane "${args.lane}"\n` +
|
|
931
|
-
` 3. If needed, delete local branch: git branch -D ${branch}`);
|
|
1027
|
+
else if (currentBranch !== branch) {
|
|
1028
|
+
die(`Cloud branch-pr claim must run on the active branch.\n\n` +
|
|
1029
|
+
`Current branch: ${currentBranch}\n` +
|
|
1030
|
+
`Resolved branch: ${branch}\n\n` +
|
|
1031
|
+
`Switch to ${branch} and retry, or omit conflicting --branch flags.`);
|
|
932
1032
|
}
|
|
933
1033
|
let finalTitle = updatedTitle || title;
|
|
934
1034
|
const msg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
|
|
935
|
-
|
|
1035
|
+
const shouldPersistClaimMetadata = shouldPersistClaimMetadataOnBranch({
|
|
1036
|
+
claimedMode,
|
|
1037
|
+
noPush: Boolean(args.noPush),
|
|
1038
|
+
});
|
|
1039
|
+
if (shouldPersistClaimMetadata) {
|
|
936
1040
|
if (args.noAuto) {
|
|
937
1041
|
await ensureCleanOrClaimOnlyWhenNoAuto();
|
|
938
1042
|
}
|
|
939
1043
|
else {
|
|
940
1044
|
// WU-1211: updateWUYaml now returns {title, initiative}
|
|
941
|
-
|
|
1045
|
+
// WU-1590: Pass claimed_branch for branch-pr persistence
|
|
1046
|
+
const updateResult = await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId, null, currentBranchForCloud || null);
|
|
942
1047
|
finalTitle = updateResult.title || finalTitle;
|
|
943
1048
|
await addOrReplaceInProgressStatus(STATUS_PATH, id, finalTitle);
|
|
944
1049
|
await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, finalTitle, args.lane);
|
|
@@ -950,9 +1055,11 @@ async function claimBranchOnlyMode(ctx) {
|
|
|
950
1055
|
filesToAdd.push(initProgress.initPath);
|
|
951
1056
|
}
|
|
952
1057
|
}
|
|
953
|
-
await getGitForCwd().add(filesToAdd
|
|
1058
|
+
await getGitForCwd().add(filesToAdd);
|
|
954
1059
|
}
|
|
955
1060
|
await getGitForCwd().commit(msg);
|
|
1061
|
+
}
|
|
1062
|
+
if (args.noPush) {
|
|
956
1063
|
console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
|
|
957
1064
|
}
|
|
958
1065
|
else {
|
|
@@ -1323,6 +1430,33 @@ async function main() {
|
|
|
1323
1430
|
if (!PATTERNS.WU_ID.test(id))
|
|
1324
1431
|
die(`Invalid WU id '${args.id}'. Expected format WU-123`);
|
|
1325
1432
|
await ensureOnMain(getGitForCwd());
|
|
1433
|
+
// WU-1609: Resolve branch-aware cloud activation at preflight so explicit
|
|
1434
|
+
// protected-branch cloud requests fail before lane locking/state mutation.
|
|
1435
|
+
const preflightBranch = await getGitForCwd().getCurrentBranch();
|
|
1436
|
+
const preflightCloudEffective = resolveCloudActivationForClaim({
|
|
1437
|
+
cloudFlag: Boolean(args.cloud),
|
|
1438
|
+
env: process.env,
|
|
1439
|
+
config: getConfig().cloud,
|
|
1440
|
+
currentBranch: preflightBranch,
|
|
1441
|
+
});
|
|
1442
|
+
if (preflightCloudEffective.blocked) {
|
|
1443
|
+
const sourceHint = preflightCloudEffective.source === CLOUD_ACTIVATION_SOURCE.FLAG
|
|
1444
|
+
? '--cloud'
|
|
1445
|
+
: 'LUMENFLOW_CLOUD=1';
|
|
1446
|
+
die(`Cloud mode blocked on protected branch "${preflightBranch}".\n\n` +
|
|
1447
|
+
`Explicit cloud activation (${sourceHint}) is not allowed on main/master.\n` +
|
|
1448
|
+
`Switch to a non-main branch for cloud mode, or run wu:claim without cloud activation on main/master.`);
|
|
1449
|
+
}
|
|
1450
|
+
if (preflightCloudEffective.suppressed) {
|
|
1451
|
+
const signalSuffix = preflightCloudEffective.matchedSignal
|
|
1452
|
+
? ` (signal: ${preflightCloudEffective.matchedSignal})`
|
|
1453
|
+
: '';
|
|
1454
|
+
console.log(`${PREFIX} Cloud auto-detection suppressed on protected branch "${preflightBranch}"${signalSuffix}; continuing with standard claim flow.`);
|
|
1455
|
+
}
|
|
1456
|
+
else if (preflightCloudEffective.isCloud &&
|
|
1457
|
+
preflightCloudEffective.source === CLOUD_ACTIVATION_SOURCE.ENV_SIGNAL) {
|
|
1458
|
+
console.log(`${PREFIX} Cloud mode auto-detected (source: ${preflightCloudEffective.source}${preflightCloudEffective.matchedSignal ? `, signal: ${preflightCloudEffective.matchedSignal}` : ''})`);
|
|
1459
|
+
}
|
|
1326
1460
|
// WU-2411: Handle --resume flag for agent handoff
|
|
1327
1461
|
if (args.resume) {
|
|
1328
1462
|
await handleResumeMode(args, id);
|
|
@@ -1449,18 +1583,11 @@ async function main() {
|
|
|
1449
1583
|
const title = (await readWUTitle(id)) || '';
|
|
1450
1584
|
const branch = args.branch || `lane/${laneK}/${idK}`;
|
|
1451
1585
|
const worktree = args.worktree || `worktrees/${laneK}-${idK}`;
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
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
|
-
}
|
|
1586
|
+
const currentBranch = preflightBranch;
|
|
1587
|
+
const cloudEffective = preflightCloudEffective;
|
|
1588
|
+
const effectiveCloud = cloudEffective.isCloud;
|
|
1589
|
+
// WU-1590: Capture current branch for cloud claim metadata (before any branch switching)
|
|
1590
|
+
const currentBranchForCloud = effectiveCloud ? currentBranch : undefined;
|
|
1464
1591
|
// WU-1491: Resolve claimed mode from flag combination
|
|
1465
1592
|
const modeResult = resolveClaimMode({
|
|
1466
1593
|
branchOnly: args.branchOnly,
|
|
@@ -1476,11 +1603,24 @@ async function main() {
|
|
|
1476
1603
|
if (!modeResult.skipBranchOnlySingletonGuard) {
|
|
1477
1604
|
await validateBranchOnlyMode(STATUS_PATH, id);
|
|
1478
1605
|
}
|
|
1606
|
+
// WU-1590: Skip branch-exists checks in cloud mode (branch already exists by definition)
|
|
1607
|
+
const branchExecution = resolveBranchClaimExecution({
|
|
1608
|
+
claimedMode,
|
|
1609
|
+
isCloud: effectiveCloud,
|
|
1610
|
+
currentBranch,
|
|
1611
|
+
requestedBranch: branch,
|
|
1612
|
+
});
|
|
1613
|
+
const effectiveBranch = branchExecution.executionBranch;
|
|
1614
|
+
const skipBranchChecks = shouldSkipBranchExistsCheck({
|
|
1615
|
+
isCloud: effectiveCloud,
|
|
1616
|
+
currentBranch,
|
|
1617
|
+
laneBranch: effectiveBranch,
|
|
1618
|
+
});
|
|
1479
1619
|
// Check if remote branch already exists (prevents duplicate global claims)
|
|
1480
|
-
if (!args.noPush) {
|
|
1481
|
-
const remoteExists = await getGitForCwd().remoteBranchExists(REMOTES.ORIGIN,
|
|
1620
|
+
if (!args.noPush && !skipBranchChecks) {
|
|
1621
|
+
const remoteExists = await getGitForCwd().remoteBranchExists(REMOTES.ORIGIN, effectiveBranch);
|
|
1482
1622
|
if (remoteExists) {
|
|
1483
|
-
die(`Remote branch ${REMOTES.ORIGIN}/${
|
|
1623
|
+
die(`Remote branch ${REMOTES.ORIGIN}/${effectiveBranch} already exists. WU may already be claimed.\n\n` +
|
|
1484
1624
|
`Options:\n` +
|
|
1485
1625
|
` 1. Coordinate with the owning agent or wait for completion\n` +
|
|
1486
1626
|
` 2. Choose a different WU\n` +
|
|
@@ -1488,14 +1628,16 @@ async function main() {
|
|
|
1488
1628
|
}
|
|
1489
1629
|
}
|
|
1490
1630
|
// Check if branch already exists locally (prevents duplicate claims)
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
`
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1631
|
+
if (!skipBranchChecks) {
|
|
1632
|
+
const branchAlreadyExists = await getGitForCwd().branchExists(effectiveBranch);
|
|
1633
|
+
if (branchAlreadyExists) {
|
|
1634
|
+
die(`Branch ${effectiveBranch} already exists. WU may already be claimed.\n\n` +
|
|
1635
|
+
`Git branch existence = WU claimed (natural locking).\n\n` +
|
|
1636
|
+
`Options:\n` +
|
|
1637
|
+
` 1. Check git worktree list to see if worktree exists\n` +
|
|
1638
|
+
` 2. Coordinate with the owning agent or wait for them to complete\n` +
|
|
1639
|
+
` 3. Choose a different WU`);
|
|
1640
|
+
}
|
|
1499
1641
|
}
|
|
1500
1642
|
// Layer 3 defense (WU-1476): Pre-flight orphan check
|
|
1501
1643
|
// Clean up orphan directory if it exists at target worktree path
|
|
@@ -1508,7 +1650,7 @@ async function main() {
|
|
|
1508
1650
|
}
|
|
1509
1651
|
catch (err) {
|
|
1510
1652
|
die(`Failed to clean up orphan directory at ${worktree}\n\n` +
|
|
1511
|
-
`Error: ${err
|
|
1653
|
+
`Error: ${getErrorMessage(err)}\n\n` +
|
|
1512
1654
|
`Manual cleanup: rm -rf ${absoluteWorktreePath}`);
|
|
1513
1655
|
}
|
|
1514
1656
|
}
|
|
@@ -1529,7 +1671,7 @@ async function main() {
|
|
|
1529
1671
|
}
|
|
1530
1672
|
catch (err) {
|
|
1531
1673
|
// Non-blocking: session start failure should not block claim
|
|
1532
|
-
console.warn(`${PREFIX} Warning: Could not start agent session: ${err
|
|
1674
|
+
console.warn(`${PREFIX} Warning: Could not start agent session: ${getErrorMessage(err)}`);
|
|
1533
1675
|
}
|
|
1534
1676
|
// Execute claim workflow
|
|
1535
1677
|
const baseCtx = {
|
|
@@ -1537,18 +1679,26 @@ async function main() {
|
|
|
1537
1679
|
id,
|
|
1538
1680
|
laneK,
|
|
1539
1681
|
title,
|
|
1540
|
-
branch,
|
|
1682
|
+
branch: effectiveBranch,
|
|
1541
1683
|
worktree,
|
|
1542
1684
|
WU_PATH,
|
|
1543
1685
|
STATUS_PATH,
|
|
1544
1686
|
BACKLOG_PATH,
|
|
1545
1687
|
claimedMode,
|
|
1688
|
+
shouldCreateBranch: branchExecution.shouldCreateBranch,
|
|
1689
|
+
currentBranch,
|
|
1546
1690
|
fixableIssues, // WU-1361: Pass fixable issues for worktree application
|
|
1547
1691
|
stagedChanges,
|
|
1692
|
+
currentBranchForCloud, // WU-1590: For persisting claimed_branch in branch-pr mode
|
|
1548
1693
|
};
|
|
1549
1694
|
let updatedTitle = title;
|
|
1550
1695
|
claimTitle = title;
|
|
1551
|
-
|
|
1696
|
+
const shouldApplyCanonicalUpdate = shouldApplyCanonicalClaimUpdate({
|
|
1697
|
+
isCloud: effectiveCloud,
|
|
1698
|
+
claimedMode,
|
|
1699
|
+
noPush: Boolean(args.noPush),
|
|
1700
|
+
});
|
|
1701
|
+
if (shouldApplyCanonicalUpdate) {
|
|
1552
1702
|
updatedTitle = (await applyCanonicalClaimUpdate(baseCtx, sessionId)) || updatedTitle;
|
|
1553
1703
|
// WU-1521: Mark that canonical claim was pushed to origin/main
|
|
1554
1704
|
// If claim fails after this point, the finally block will rollback
|
|
@@ -1557,6 +1707,9 @@ async function main() {
|
|
|
1557
1707
|
// Refresh origin/main after push-only update so worktrees start from canonical state
|
|
1558
1708
|
await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
1559
1709
|
}
|
|
1710
|
+
else if (!args.noPush && claimedMode === CLAIMED_MODES.BRANCH_PR) {
|
|
1711
|
+
console.log(`${PREFIX} Skipping canonical claim update on origin/main for cloud branch-pr claim.`);
|
|
1712
|
+
}
|
|
1560
1713
|
const ctx = {
|
|
1561
1714
|
...baseCtx,
|
|
1562
1715
|
sessionId,
|
|
@@ -1570,6 +1723,30 @@ async function main() {
|
|
|
1570
1723
|
else {
|
|
1571
1724
|
await claimWorktreeMode(ctx);
|
|
1572
1725
|
}
|
|
1726
|
+
// WU-1605: Record claim-time pickup evidence for delegation provenance.
|
|
1727
|
+
// Non-blocking: this metadata should not block claim completion.
|
|
1728
|
+
try {
|
|
1729
|
+
let claimedBy = process.env.GIT_AUTHOR_EMAIL?.trim();
|
|
1730
|
+
try {
|
|
1731
|
+
claimedBy = await getAssignedEmail(getGitForCwd());
|
|
1732
|
+
}
|
|
1733
|
+
catch {
|
|
1734
|
+
// Fall back to env/default when git email lookup fails in this context.
|
|
1735
|
+
}
|
|
1736
|
+
const pickupResult = await recordClaimPickupEvidence(id, {
|
|
1737
|
+
baseDir: process.cwd(),
|
|
1738
|
+
claimedBy,
|
|
1739
|
+
});
|
|
1740
|
+
if (pickupResult.recorded) {
|
|
1741
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} Recorded delegation pickup evidence (${pickupResult.spawnId})`);
|
|
1742
|
+
}
|
|
1743
|
+
else if (pickupResult.alreadyRecorded) {
|
|
1744
|
+
console.log(`${PREFIX} ${EMOJI.INFO} Delegation pickup evidence already recorded (${pickupResult.spawnId})`);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
catch (err) {
|
|
1748
|
+
console.warn(`${PREFIX} Warning: Could not record delegation pickup evidence: ${getErrorMessage(err)}`);
|
|
1749
|
+
}
|
|
1573
1750
|
// Mark claim as successful - lock should remain for wu:done to release
|
|
1574
1751
|
claimSucceeded = true;
|
|
1575
1752
|
}
|