@lumenflow/cli 1.1.0 → 1.3.2

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 (118) hide show
  1. package/dist/__tests__/cli-entry-point.test.js +50 -0
  2. package/dist/__tests__/cli-subprocess.test.js +64 -0
  3. package/dist/cli-entry-point.js +46 -0
  4. package/dist/gates.js +102 -39
  5. package/dist/init.js +241 -195
  6. package/dist/initiative-add-wu.js +2 -1
  7. package/dist/initiative-create.js +5 -8
  8. package/dist/initiative-edit.js +3 -3
  9. package/dist/initiative-list.js +2 -1
  10. package/dist/initiative-status.js +2 -1
  11. package/dist/wu-claim.js +297 -110
  12. package/dist/wu-cleanup.js +129 -57
  13. package/dist/wu-create.js +197 -122
  14. package/dist/wu-deps.js +2 -1
  15. package/dist/wu-done.js +46 -14
  16. package/dist/wu-edit.js +152 -61
  17. package/dist/wu-infer-lane.js +5 -4
  18. package/dist/wu-preflight.js +2 -1
  19. package/dist/wu-prune.js +12 -3
  20. package/dist/wu-repair.js +2 -1
  21. package/dist/wu-spawn.js +79 -159
  22. package/dist/wu-unlock-lane.js +6 -1
  23. package/dist/wu-validate.js +2 -1
  24. package/package.json +14 -14
  25. package/dist/gates.d.ts +0 -41
  26. package/dist/gates.d.ts.map +0 -1
  27. package/dist/gates.js.map +0 -1
  28. package/dist/initiative-add-wu.d.ts +0 -22
  29. package/dist/initiative-add-wu.d.ts.map +0 -1
  30. package/dist/initiative-add-wu.js.map +0 -1
  31. package/dist/initiative-create.d.ts +0 -28
  32. package/dist/initiative-create.d.ts.map +0 -1
  33. package/dist/initiative-create.js.map +0 -1
  34. package/dist/initiative-edit.d.ts +0 -34
  35. package/dist/initiative-edit.d.ts.map +0 -1
  36. package/dist/initiative-edit.js.map +0 -1
  37. package/dist/initiative-list.d.ts +0 -12
  38. package/dist/initiative-list.d.ts.map +0 -1
  39. package/dist/initiative-list.js.map +0 -1
  40. package/dist/initiative-status.d.ts +0 -11
  41. package/dist/initiative-status.d.ts.map +0 -1
  42. package/dist/initiative-status.js.map +0 -1
  43. package/dist/mem-checkpoint.d.ts +0 -16
  44. package/dist/mem-checkpoint.d.ts.map +0 -1
  45. package/dist/mem-checkpoint.js.map +0 -1
  46. package/dist/mem-cleanup.d.ts +0 -29
  47. package/dist/mem-cleanup.d.ts.map +0 -1
  48. package/dist/mem-cleanup.js.map +0 -1
  49. package/dist/mem-create.d.ts +0 -17
  50. package/dist/mem-create.d.ts.map +0 -1
  51. package/dist/mem-create.js.map +0 -1
  52. package/dist/mem-inbox.d.ts +0 -35
  53. package/dist/mem-inbox.d.ts.map +0 -1
  54. package/dist/mem-inbox.js.map +0 -1
  55. package/dist/mem-init.d.ts +0 -15
  56. package/dist/mem-init.d.ts.map +0 -1
  57. package/dist/mem-init.js.map +0 -1
  58. package/dist/mem-ready.d.ts +0 -16
  59. package/dist/mem-ready.d.ts.map +0 -1
  60. package/dist/mem-ready.js.map +0 -1
  61. package/dist/mem-signal.d.ts +0 -16
  62. package/dist/mem-signal.d.ts.map +0 -1
  63. package/dist/mem-signal.js.map +0 -1
  64. package/dist/mem-start.d.ts +0 -16
  65. package/dist/mem-start.d.ts.map +0 -1
  66. package/dist/mem-start.js.map +0 -1
  67. package/dist/mem-summarize.d.ts +0 -22
  68. package/dist/mem-summarize.d.ts.map +0 -1
  69. package/dist/mem-summarize.js.map +0 -1
  70. package/dist/mem-triage.d.ts +0 -22
  71. package/dist/mem-triage.d.ts.map +0 -1
  72. package/dist/mem-triage.js.map +0 -1
  73. package/dist/spawn-list.d.ts +0 -16
  74. package/dist/spawn-list.d.ts.map +0 -1
  75. package/dist/spawn-list.js.map +0 -1
  76. package/dist/wu-block.d.ts +0 -16
  77. package/dist/wu-block.d.ts.map +0 -1
  78. package/dist/wu-block.js.map +0 -1
  79. package/dist/wu-claim.d.ts +0 -32
  80. package/dist/wu-claim.d.ts.map +0 -1
  81. package/dist/wu-claim.js.map +0 -1
  82. package/dist/wu-cleanup.d.ts +0 -17
  83. package/dist/wu-cleanup.d.ts.map +0 -1
  84. package/dist/wu-cleanup.js.map +0 -1
  85. package/dist/wu-create.d.ts +0 -38
  86. package/dist/wu-create.d.ts.map +0 -1
  87. package/dist/wu-create.js.map +0 -1
  88. package/dist/wu-deps.d.ts +0 -13
  89. package/dist/wu-deps.d.ts.map +0 -1
  90. package/dist/wu-deps.js.map +0 -1
  91. package/dist/wu-done.d.ts +0 -153
  92. package/dist/wu-done.d.ts.map +0 -1
  93. package/dist/wu-done.js.map +0 -1
  94. package/dist/wu-edit.d.ts +0 -29
  95. package/dist/wu-edit.d.ts.map +0 -1
  96. package/dist/wu-edit.js.map +0 -1
  97. package/dist/wu-infer-lane.d.ts +0 -17
  98. package/dist/wu-infer-lane.d.ts.map +0 -1
  99. package/dist/wu-infer-lane.js.map +0 -1
  100. package/dist/wu-preflight.d.ts +0 -47
  101. package/dist/wu-preflight.d.ts.map +0 -1
  102. package/dist/wu-preflight.js.map +0 -1
  103. package/dist/wu-prune.d.ts +0 -16
  104. package/dist/wu-prune.d.ts.map +0 -1
  105. package/dist/wu-prune.js.map +0 -1
  106. package/dist/wu-repair.d.ts +0 -60
  107. package/dist/wu-repair.d.ts.map +0 -1
  108. package/dist/wu-repair.js.map +0 -1
  109. package/dist/wu-spawn-completion.d.ts +0 -10
  110. package/dist/wu-spawn.d.ts +0 -168
  111. package/dist/wu-spawn.d.ts.map +0 -1
  112. package/dist/wu-spawn.js.map +0 -1
  113. package/dist/wu-unblock.d.ts +0 -16
  114. package/dist/wu-unblock.d.ts.map +0 -1
  115. package/dist/wu-unblock.js.map +0 -1
  116. package/dist/wu-validate.d.ts +0 -16
  117. package/dist/wu-validate.d.ts.map +0 -1
  118. package/dist/wu-validate.js.map +0 -1
package/dist/wu-claim.js CHANGED
@@ -15,7 +15,7 @@
15
15
  * Full migration to thin shim pending @lumenflow/core CLI export implementation.
16
16
  */
17
17
  import { existsSync, readFileSync, rmSync } from 'node:fs';
18
- import { access, readFile, writeFile } from 'node:fs/promises';
18
+ import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
19
19
  import path from 'node:path';
20
20
  import { isOrphanWorktree } from '@lumenflow/core/dist/orphan-detector.js';
21
21
  // WU-1352: Use centralized YAML functions from wu-yaml.mjs
@@ -32,14 +32,15 @@ import { getGitForCwd, createGitForPath } from '@lumenflow/core/dist/git-adapter
32
32
  import { die } from '@lumenflow/core/dist/error-handler.js';
33
33
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
34
34
  import { WU_PATHS, getStateStoreDirFromBacklog } from '@lumenflow/core/dist/wu-paths.js';
35
- import { BRANCHES, REMOTES, WU_STATUS, CLAIMED_MODES, STATUS_SECTIONS, PATTERNS, toKebab, LOG_PREFIX, MICRO_WORKTREE_OPERATIONS, COMMIT_FORMATS, EMOJI, FILE_SYSTEM, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
35
+ 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, } from '@lumenflow/core/dist/wu-constants.js';
36
36
  import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
37
- import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
37
+ import { ensureOnMain, ensureMainUpToDate } from '@lumenflow/core/dist/wu-helpers.js';
38
38
  import { emitWUFlowEvent } from '@lumenflow/core/dist/telemetry.js';
39
39
  import { checkLaneForOrphanDoneWU, repairWUInconsistency, } from '@lumenflow/core/dist/wu-consistency-checker.js';
40
40
  import { emitMandatoryAgentAdvisory } from '@lumenflow/core/dist/orchestration-advisory-loader.js';
41
41
  import { validateWU, generateAutoApproval } from '@lumenflow/core/dist/wu-schema.js';
42
42
  import { startSessionForWU } from '@lumenflow/agent/dist/auto-session-integration.js';
43
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
43
44
  import { detectFixableIssues, applyFixes, autoFixWUYaml, formatIssues, } from '@lumenflow/core/dist/wu-yaml-fixer.js';
44
45
  import { validateSpecCompleteness } from '@lumenflow/core/dist/wu-done-validators.js';
45
46
  import { getAssignedEmail } from '@lumenflow/core/dist/wu-claim-helpers.js';
@@ -164,7 +165,7 @@ function validateYAMLSchema(WU_PATH, doc, args) {
164
165
  }
165
166
  // WU-1576: validateBacklogConsistency removed - repair now happens inside micro-worktree
166
167
  // See claimWorktreeMode() execute function for the new location
167
- async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null) {
168
+ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null, gitAdapter = null) {
168
169
  // Check file exists
169
170
  try {
170
171
  await access(WU_PATH);
@@ -223,16 +224,16 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
223
224
  if (worktreePath) {
224
225
  doc.worktree_path = worktreePath;
225
226
  }
227
+ const git = gitAdapter || getGitForCwd();
226
228
  // WU-1423: Record owner using validated email (no silent username fallback)
227
229
  // Fallback chain: git config user.email > GIT_AUTHOR_EMAIL > error
228
230
  // WU-1427: getAssignedEmail is now async to properly await gitAdapter.getConfigValue
229
- doc.assigned_to = await getAssignedEmail(getGitForCwd());
231
+ doc.assigned_to = await getAssignedEmail(git);
230
232
  // Record claim timestamp for duration tracking (WU-637)
231
233
  doc.claimed_at = new Date().toISOString();
232
234
  // WU-1382: Store baseline main SHA for parallel agent detection
233
235
  // wu:done will compare against this to detect if other WUs were merged during work
234
- const git = getGitForCwd();
235
- doc.baseline_main_sha = await git.getCommitHash('origin/main');
236
+ doc.baseline_main_sha = await git.getCommitHash(GIT_REFS.ORIGIN_MAIN);
236
237
  // WU-1438: Store agent session ID for tracking
237
238
  if (sessionId) {
238
239
  doc.session_id = sessionId;
@@ -357,6 +358,90 @@ export function getWorktreeCommitFiles(wuId) {
357
358
  // These generated files cause merge conflicts when main advances
358
359
  ];
359
360
  }
361
+ function parseStagedChangeLine(line) {
362
+ const parts = line.trim().split(/\s+/);
363
+ const status = parts[0];
364
+ if (!status)
365
+ return null;
366
+ if (status.startsWith('R') || status.startsWith('C')) {
367
+ return { status, from: parts[1], filePath: parts[2] };
368
+ }
369
+ return { status, filePath: parts.slice(1).join(' ') };
370
+ }
371
+ async function getStagedChanges() {
372
+ const diff = await getGitForCwd().raw(['diff', '--cached', '--name-status']);
373
+ if (!diff.trim())
374
+ return [];
375
+ return diff
376
+ .split(STRING_LITERALS.NEWLINE)
377
+ .filter(Boolean)
378
+ .map(parseStagedChangeLine)
379
+ .filter(Boolean);
380
+ }
381
+ async function applyStagedChangesToMicroWorktree(worktreePath, stagedChanges) {
382
+ for (const change of stagedChanges) {
383
+ const filePath = change.filePath;
384
+ if (!filePath)
385
+ continue;
386
+ const targetPath = path.join(worktreePath, filePath);
387
+ if (change.status.startsWith('D')) {
388
+ rmSync(targetPath, { recursive: true, force: true });
389
+ continue;
390
+ }
391
+ const sourcePath = path.join(process.cwd(), filePath);
392
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool applies staged files
393
+ const contents = await readFile(sourcePath, { encoding: FILE_SYSTEM.UTF8 });
394
+ await mkdir(path.dirname(targetPath), { recursive: true });
395
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool applies staged files
396
+ await writeFile(targetPath, contents, { encoding: FILE_SYSTEM.UTF8 });
397
+ }
398
+ }
399
+ /**
400
+ * Update canonical claim state on origin/main using push-only micro-worktree.
401
+ * Ensures canonical state stays global while local main remains unchanged.
402
+ */
403
+ async function applyCanonicalClaimUpdate(ctx, sessionId) {
404
+ const { args, id, laneK, worktree, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, fixableIssues, stagedChanges, } = ctx;
405
+ const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
406
+ const worktreePathForYaml = claimedMode === CLAIMED_MODES.BRANCH_ONLY ? null : path.resolve(worktree);
407
+ let updatedTitle = '';
408
+ const filesToCommit = args.noAuto && stagedChanges.length > 0
409
+ ? stagedChanges.map((change) => change.filePath).filter(Boolean)
410
+ : [WU_PATHS.WU(id), WU_PATHS.STATUS(), WU_PATHS.BACKLOG(), '.beacon/state/wu-events.jsonl'];
411
+ console.log(`${PREFIX} Updating canonical claim state (push-only)...`);
412
+ await withMicroWorktree({
413
+ operation: MICRO_WORKTREE_OPERATIONS.WU_CLAIM,
414
+ id,
415
+ logPrefix: PREFIX,
416
+ pushOnly: true,
417
+ execute: async ({ worktreePath }) => {
418
+ const microWUPath = path.join(worktreePath, WU_PATH);
419
+ const microStatusPath = path.join(worktreePath, STATUS_PATH);
420
+ const microBacklogPath = path.join(worktreePath, BACKLOG_PATH);
421
+ if (args.noAuto) {
422
+ await applyStagedChangesToMicroWorktree(worktreePath, stagedChanges);
423
+ }
424
+ else {
425
+ if (fixableIssues && fixableIssues.length > 0) {
426
+ console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
427
+ autoFixWUYaml(microWUPath);
428
+ console.log(`${PREFIX} YAML fixes applied successfully`);
429
+ }
430
+ const microGit = createGitForPath(worktreePath);
431
+ updatedTitle =
432
+ (await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit)) || updatedTitle;
433
+ await addOrReplaceInProgressStatus(microStatusPath, id, updatedTitle);
434
+ await removeFromReadyAndAddToInProgressBacklog(microBacklogPath, id, updatedTitle, args.lane);
435
+ }
436
+ return {
437
+ commitMessage: commitMsg,
438
+ files: filesToCommit,
439
+ };
440
+ },
441
+ });
442
+ console.log(`${PREFIX} Canonical claim state updated on origin/main`);
443
+ return updatedTitle;
444
+ }
360
445
  async function readWUTitle(id) {
361
446
  const p = WU_PATHS.WU(id);
362
447
  // Check file exists
@@ -617,51 +702,44 @@ async function validateBranchOnlyMode(STATUS_PATH, id) {
617
702
  * Execute branch-only mode claim workflow
618
703
  */
619
704
  async function claimBranchOnlyMode(ctx) {
620
- const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode } = ctx;
621
- // WU-1438: Start agent session BEFORE metadata update to include session_id in YAML
622
- let sessionId = null;
705
+ const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, sessionId, updatedTitle, } = ctx;
706
+ // Create branch and switch to it from origin/main (avoids local main mutation)
623
707
  try {
624
- const sessionResult = await startSessionForWU({
625
- wuId: id,
626
- tier: 2,
627
- });
628
- sessionId = sessionResult.sessionId;
629
- if (sessionResult.alreadyActive) {
630
- console.log(`${PREFIX} Agent session already active (${sessionId.slice(0, 8)}...)`);
708
+ await getGitForCwd().createBranch(branch, `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
709
+ }
710
+ catch (error) {
711
+ die(`Canonical claim state may be updated, but branch creation failed.\n\n` +
712
+ `Error: ${error.message}\n\n` +
713
+ `Recovery:\n` +
714
+ ` 1. Run: git fetch ${REMOTES.ORIGIN} ${BRANCHES.MAIN}\n` +
715
+ ` 2. Retry: pnpm wu:claim --id ${id} --lane "${args.lane}"\n` +
716
+ ` 3. If needed, delete local branch: git branch -D ${branch}`);
717
+ }
718
+ let finalTitle = updatedTitle || title;
719
+ const msg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
720
+ if (args.noPush) {
721
+ if (args.noAuto) {
722
+ await ensureCleanOrClaimOnlyWhenNoAuto();
631
723
  }
632
724
  else {
633
- console.log(`${PREFIX} ${EMOJI.SUCCESS} Agent session started (${sessionId.slice(0, 8)}...)`);
725
+ finalTitle =
726
+ (await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId)) || finalTitle;
727
+ await addOrReplaceInProgressStatus(STATUS_PATH, id, finalTitle);
728
+ await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, finalTitle, args.lane);
729
+ await getGitForCwd().add(`${JSON.stringify(WU_PATH)} ${JSON.stringify(STATUS_PATH)} ${JSON.stringify(BACKLOG_PATH)}`);
634
730
  }
635
- }
636
- catch (err) {
637
- // Non-blocking: session start failure should not block claim
638
- console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
639
- }
640
- // Create branch and switch to it (LEGACY - for constrained environments only)
641
- await getGitForCwd().createBranch(branch, BRANCHES.MAIN);
642
- // Update metadata in branch-only mode (on main checkout)
643
- let updatedTitle = title;
644
- if (args.noAuto) {
645
- await ensureCleanOrClaimOnlyWhenNoAuto();
731
+ await getGitForCwd().commit(msg);
732
+ console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
646
733
  }
647
734
  else {
648
- updatedTitle =
649
- (await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId)) || title;
650
- await addOrReplaceInProgressStatus(STATUS_PATH, id, updatedTitle);
651
- await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, updatedTitle, args.lane);
652
- await getGitForCwd().add(`${JSON.stringify(WU_PATH)} ${JSON.stringify(STATUS_PATH)} ${JSON.stringify(BACKLOG_PATH)}`);
735
+ await getGitForCwd().push(REMOTES.ORIGIN, branch, { setUpstream: true });
653
736
  }
654
- // Commit and push
655
- const msg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
656
- await getGitForCwd().commit(msg);
657
- await getGitForCwd().push(REMOTES.ORIGIN, branch);
658
737
  // Summary
659
738
  console.log(`\n${PREFIX} Claim recorded in Branch-Only mode.`);
660
- console.log(`- WU: ${id}${updatedTitle ? ` — ${updatedTitle}` : ''}`);
739
+ console.log(`- WU: ${id}${finalTitle ? ` — ${finalTitle}` : ''}`);
661
740
  console.log(`- Lane: ${args.lane}`);
662
741
  console.log(`- Mode: Branch-Only (no worktree)`);
663
- console.log(`- Commit: ${msg}`);
664
- console.log(`- Branch: ${branch}`);
742
+ console.log(`${args.noPush ? `- Commit: ${msg}` : `- Branch: ${branch}`}`);
665
743
  console.log('\n⚠️ LIMITATION: Branch-Only mode does not support parallel WUs (WIP=1 across ALL lanes)');
666
744
  console.log('Next: work on this branch in the main checkout.');
667
745
  // WU-1360: Print next-steps checklist to prevent common mistakes
@@ -706,97 +784,95 @@ async function claimBranchOnlyMode(ctx) {
706
784
  */
707
785
  async function claimWorktreeMode(ctx) {
708
786
  const { args, id, laneK, title, branch, worktree, WU_PATH, BACKLOG_PATH, claimedMode, fixableIssues, // Fixable issues from pre-flight validation
709
- } = ctx;
787
+ sessionId, updatedTitle, stagedChanges, } = ctx;
710
788
  const originalCwd = process.cwd();
711
789
  const worktreePath = path.resolve(worktree);
712
- let updatedTitle = title;
790
+ let finalTitle = updatedTitle || title;
713
791
  const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
714
- // WU-1438: Start agent session BEFORE metadata update to include session_id in YAML
715
- let sessionId = null;
716
- try {
717
- const sessionResult = await startSessionForWU({
718
- wuId: id,
719
- tier: 2,
720
- });
721
- sessionId = sessionResult.sessionId;
722
- if (sessionResult.alreadyActive) {
723
- console.log(`${PREFIX} Agent session already active (${sessionId.slice(0, 8)}...)`);
724
- }
725
- else {
726
- console.log(`${PREFIX} ${EMOJI.SUCCESS} Agent session started (${sessionId.slice(0, 8)}...)`);
727
- }
728
- }
729
- catch (err) {
730
- // Non-blocking: session start failure should not block claim
731
- console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
732
- }
733
792
  // WU-1741: Step 1 - Create work worktree+branch from main
734
793
  // Branch creation IS the coordination lock (git prevents duplicate branch names)
735
794
  console.log(`${PREFIX} Creating worktree (branch = coordination lock)...`);
736
- await getGitForCwd().worktreeAdd(worktree, branch, BRANCHES.MAIN);
795
+ const startPoint = args.noPush ? BRANCHES.MAIN : `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`;
796
+ await getGitForCwd().worktreeAdd(worktree, branch, startPoint);
737
797
  console.log(`${PREFIX} ${EMOJI.SUCCESS} Worktree created at ${worktree}`);
738
- // WU-1741: Step 2 - Update metadata IN the work worktree (not main)
739
- if (!args.noAuto) {
740
- // Build paths relative to work worktree
741
- const wtWUPath = path.join(worktreePath, WU_PATH);
742
- const wtBacklogPath = path.join(worktreePath, BACKLOG_PATH);
743
- // Apply YAML fixes in worktree (not on main)
744
- if (fixableIssues && fixableIssues.length > 0) {
745
- console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
746
- autoFixWUYaml(wtWUPath);
747
- console.log(`${PREFIX} YAML fixes applied successfully`);
798
+ if (!args.noPush) {
799
+ const wtGit = createGitForPath(worktreePath);
800
+ await wtGit.push(REMOTES.ORIGIN, branch, { setUpstream: true });
801
+ }
802
+ if (args.noPush) {
803
+ // Local-only claim (air-gapped) - update metadata inside worktree branch
804
+ if (args.noAuto) {
805
+ await applyStagedChangesToMicroWorktree(worktreePath, stagedChanges);
806
+ }
807
+ else {
808
+ const wtWUPath = path.join(worktreePath, WU_PATH);
809
+ const wtBacklogPath = path.join(worktreePath, BACKLOG_PATH);
810
+ if (fixableIssues && fixableIssues.length > 0) {
811
+ console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
812
+ autoFixWUYaml(wtWUPath);
813
+ console.log(`${PREFIX} YAML fixes applied successfully`);
814
+ }
815
+ finalTitle =
816
+ (await updateWUYaml(wtWUPath, id, args.lane, claimedMode, worktree, sessionId)) ||
817
+ finalTitle;
818
+ // WU-1746: Only append claim event to state store - don't regenerate backlog.md/status.md
819
+ const wtStateDir = getStateStoreDirFromBacklog(wtBacklogPath);
820
+ await appendClaimEventOnly(wtStateDir, id, finalTitle, args.lane);
748
821
  }
749
- // Update metadata files in worktree (WU-1438: include session_id)
750
- updatedTitle =
751
- (await updateWUYaml(wtWUPath, id, args.lane, claimedMode, worktree, sessionId)) || title;
752
- // WU-1746: Only append claim event to state store - don't regenerate backlog.md/status.md
753
- // These generated files cause merge conflicts when committed to worktrees
754
- const wtStateDir = getStateStoreDirFromBacklog(wtBacklogPath);
755
- await appendClaimEventOnly(wtStateDir, id, updatedTitle, args.lane);
756
- // WU-1741: Step 3 - Commit metadata in worktree (NOT on main)
757
- // This commit stays on the lane branch until wu:done merges to main
758
822
  console.log(`${PREFIX} Committing claim metadata in worktree...`);
759
823
  const wtGit = createGitForPath(worktreePath);
760
- // WU-1746: Use getWorktreeCommitFiles which excludes backlog.md and status.md
761
824
  const filesToCommit = getWorktreeCommitFiles(id);
762
825
  await wtGit.add(filesToCommit);
763
826
  await wtGit.commit(commitMsg);
764
827
  console.log(`${PREFIX} ${EMOJI.SUCCESS} Claim committed: ${commitMsg}`);
828
+ console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
765
829
  }
766
- // WU-1443: Auto-symlink node_modules for immediate pnpm usability
767
- // WU-2238: Pass mainRepoPath to detect broken worktree-path symlinks
768
- const symlinkResult = symlinkNodeModules(worktreePath, console, originalCwd);
769
- if (symlinkResult.created) {
770
- console.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked for immediate use`);
830
+ // WU-1023: Auto-setup worktree dependencies
831
+ // By default, run pnpm install to ensure all dependencies are built (including CLI dist)
832
+ // Use --skip-setup to use symlink-only approach for faster claims when deps are already built
833
+ if (args.skipSetup) {
834
+ // WU-1443: Symlink-only mode for fast claims
835
+ // WU-2238: Pass mainRepoPath to detect broken worktree-path symlinks
836
+ const symlinkResult = symlinkNodeModules(worktreePath, console, originalCwd);
837
+ if (symlinkResult.created) {
838
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked (--skip-setup mode)`);
839
+ }
840
+ else if (symlinkResult.refused) {
841
+ console.warn(`${PREFIX} Warning: symlink refused: ${symlinkResult.reason}`);
842
+ console.warn(`${PREFIX} Run 'pnpm install' manually in the worktree`);
843
+ }
844
+ // WU-1579: Auto-symlink nested package node_modules for turbo typecheck
845
+ if (!symlinkResult.refused) {
846
+ const nestedResult = symlinkNestedNodeModules(worktreePath, originalCwd);
847
+ if (nestedResult.created > 0) {
848
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
849
+ }
850
+ }
771
851
  }
772
- else if (symlinkResult.refused) {
773
- // WU-2238: Symlinking was refused due to worktree-path symlinks
774
- // Fall back to running pnpm install in the worktree
775
- console.log(`${PREFIX} Running pnpm install in worktree (symlink refused: ${symlinkResult.reason})`);
852
+ else {
853
+ // WU-1023: Full setup mode (default) - run pnpm install with progress indicator
854
+ // This ensures CLI dist is built and all dependencies are properly resolved
855
+ console.log(`${PREFIX} Installing worktree dependencies (this may take a moment)...`);
776
856
  try {
777
857
  const { execSync } = await import('node:child_process');
778
858
  execSync('pnpm install --frozen-lockfile', {
779
859
  cwd: worktreePath,
780
- stdio: 'inherit',
781
- timeout: 120000, // 2 minute timeout
860
+ stdio: 'inherit', // Shows progress output from pnpm
861
+ timeout: 300000, // 5 minute timeout for full install
782
862
  });
783
- console.log(`${PREFIX} ${EMOJI.SUCCESS} pnpm install completed in worktree`);
863
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} Worktree dependencies installed`);
784
864
  }
785
865
  catch (installError) {
866
+ // Non-fatal: warn but don't block claim
786
867
  console.warn(`${PREFIX} Warning: pnpm install failed: ${installError.message}`);
787
868
  console.warn(`${PREFIX} You may need to run 'pnpm install' manually in the worktree`);
788
- }
789
- }
790
- // WU-1579: Auto-symlink nested package node_modules for turbo typecheck
791
- // WU-2238: Skip nested symlinks if root symlink was refused (pnpm install handles them)
792
- if (!symlinkResult.refused) {
793
- const nestedResult = symlinkNestedNodeModules(worktreePath, originalCwd);
794
- if (nestedResult.created > 0) {
795
- console.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
869
+ // Fall back to symlink approach so worktree is at least somewhat usable
870
+ console.log(`${PREFIX} Falling back to symlink approach...`);
871
+ applyFallbackSymlinks(worktreePath, originalCwd, console);
796
872
  }
797
873
  }
798
874
  console.log(`${PREFIX} Claim recorded in worktree`);
799
- console.log(`- WU: ${id}${updatedTitle ? ` — ${updatedTitle}` : ''}`);
875
+ console.log(`- WU: ${id}${finalTitle ? ` — ${finalTitle}` : ''}`);
800
876
  console.log(`- Lane: ${args.lane}`);
801
877
  console.log(`- Worktree: ${worktreePath}`);
802
878
  console.log(`- Branch: ${branch}`);
@@ -827,9 +903,44 @@ async function claimWorktreeMode(ctx) {
827
903
  const wuDoc = parseYAML(wuContent);
828
904
  const codePaths = wuDoc.code_paths || [];
829
905
  emitMandatoryAgentAdvisory(codePaths, id);
906
+ // WU-1047: Emit agent-only project defaults from config
907
+ const config = getConfig();
908
+ printProjectDefaults(config?.agents?.methodology);
830
909
  // WU-1763: Print lifecycle nudge with tips for tool adoption
831
910
  printLifecycleNudge(id);
832
911
  }
912
+ /**
913
+ * WU-1047: Format Project Defaults section (agent-only).
914
+ *
915
+ * @param {object} methodology - Methodology defaults config
916
+ * @returns {string} Formatted output or empty string if disabled
917
+ */
918
+ export function formatProjectDefaults(methodology) {
919
+ if (!methodology || methodology.enabled === false)
920
+ return '';
921
+ const enforcement = methodology.enforcement || 'required';
922
+ const principles = Array.isArray(methodology.principles) ? methodology.principles : [];
923
+ const lines = [
924
+ `${PREFIX} 🧭 Project Defaults (agent-only)`,
925
+ ` Enforcement: ${enforcement}`,
926
+ ` Principles: ${principles.length > 0 ? principles.join(', ') : 'None'}`,
927
+ ];
928
+ if (methodology.notes) {
929
+ lines.push(` Notes: ${methodology.notes}`);
930
+ }
931
+ return `\n${lines.join('\n')}`;
932
+ }
933
+ /**
934
+ * WU-1047: Print Project Defaults section (agent-only).
935
+ *
936
+ * @param {object} methodology - Methodology defaults config
937
+ */
938
+ export function printProjectDefaults(methodology) {
939
+ const output = formatProjectDefaults(methodology);
940
+ if (output) {
941
+ console.log(output);
942
+ }
943
+ }
833
944
  /**
834
945
  * WU-1763: Print a single concise tips line to improve tool adoption.
835
946
  * Non-blocking, single-line output to avoid flooding the console.
@@ -840,6 +951,27 @@ export function printLifecycleNudge(_id) {
840
951
  // Single line, concise, actionable
841
952
  console.log(`\n${PREFIX} 💡 Tip: pnpm session:recommend for context tier, mem:ready for pending work, pnpm file:*/git:* for audited wrappers`);
842
953
  }
954
+ /**
955
+ * WU-1029: Apply symlink fallback (root + nested node_modules) after install failure.
956
+ *
957
+ * @param {string} worktreePath - Worktree path
958
+ * @param {string} mainRepoPath - Main repo path
959
+ * @param {Console} logger - Logger (console-compatible)
960
+ */
961
+ export function applyFallbackSymlinks(worktreePath, mainRepoPath, logger = console) {
962
+ const symlinkResult = symlinkNodeModules(worktreePath, logger, mainRepoPath);
963
+ if (symlinkResult.created) {
964
+ logger.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked as fallback`);
965
+ }
966
+ let nestedResult = null;
967
+ if (!symlinkResult.refused) {
968
+ nestedResult = symlinkNestedNodeModules(worktreePath, mainRepoPath);
969
+ if (nestedResult.created > 0) {
970
+ logger.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
971
+ }
972
+ }
973
+ return { symlinkResult, nestedResult };
974
+ }
843
975
  /**
844
976
  * WU-2411: Handle --resume flag for agent handoff
845
977
  *
@@ -929,6 +1061,8 @@ async function main() {
929
1061
  WU_OPTIONS.reason,
930
1062
  WU_OPTIONS.allowIncomplete,
931
1063
  WU_OPTIONS.resume, // WU-2411: Agent handoff flag
1064
+ WU_OPTIONS.skipSetup, // WU-1023: Skip auto-setup for fast claims
1065
+ WU_OPTIONS.noPush, // Skip pushing claim state/branch (air-gapped)
932
1066
  ],
933
1067
  required: ['id', 'lane'],
934
1068
  allowPositionalId: true,
@@ -954,9 +1088,19 @@ async function main() {
954
1088
  ` 3. Use --no-auto if you already staged claim edits manually`);
955
1089
  }
956
1090
  }
957
- // WU-1361: Fetch and pull FIRST - validate on fresh data, not stale pre-pull data
958
- await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
959
- await getGitForCwd().pull(REMOTES.ORIGIN, BRANCHES.MAIN);
1091
+ let stagedChanges = [];
1092
+ if (args.noAuto) {
1093
+ await ensureCleanOrClaimOnlyWhenNoAuto();
1094
+ stagedChanges = await getStagedChanges();
1095
+ }
1096
+ // WU-1361: Fetch latest remote before validation (no local main mutation)
1097
+ if (!args.noPush) {
1098
+ await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
1099
+ await ensureMainUpToDate(getGitForCwd(), 'wu:claim');
1100
+ }
1101
+ else {
1102
+ console.warn(`${PREFIX} Warning: --no-push enabled. Skipping origin/main sync; local state may be stale.`);
1103
+ }
960
1104
  const WU_PATH = WU_PATHS.WU(id);
961
1105
  const STATUS_PATH = WU_PATHS.STATUS();
962
1106
  const BACKLOG_PATH = WU_PATHS.BACKLOG();
@@ -1050,7 +1194,18 @@ async function main() {
1050
1194
  if (args.branchOnly) {
1051
1195
  await validateBranchOnlyMode(STATUS_PATH, id);
1052
1196
  }
1053
- // Check if branch already exists (prevents duplicate claims)
1197
+ // Check if remote branch already exists (prevents duplicate global claims)
1198
+ if (!args.noPush) {
1199
+ const remoteExists = await getGitForCwd().remoteBranchExists(REMOTES.ORIGIN, branch);
1200
+ if (remoteExists) {
1201
+ die(`Remote branch ${REMOTES.ORIGIN}/${branch} already exists. WU may already be claimed.\n\n` +
1202
+ `Options:\n` +
1203
+ ` 1. Coordinate with the owning agent or wait for completion\n` +
1204
+ ` 2. Choose a different WU\n` +
1205
+ ` 3. Use --no-push for local-only claims (offline)`);
1206
+ }
1207
+ }
1208
+ // Check if branch already exists locally (prevents duplicate claims)
1054
1209
  const branchAlreadyExists = await getGitForCwd().branchExists(branch);
1055
1210
  if (branchAlreadyExists) {
1056
1211
  die(`Branch ${branch} already exists. WU may already be claimed.\n\n` +
@@ -1075,8 +1230,27 @@ async function main() {
1075
1230
  `Manual cleanup: rm -rf ${absoluteWorktreePath}`);
1076
1231
  }
1077
1232
  }
1233
+ // WU-1438: Start agent session BEFORE metadata update to include session_id in YAML
1234
+ let sessionId = null;
1235
+ try {
1236
+ const sessionResult = await startSessionForWU({
1237
+ wuId: id,
1238
+ tier: 2,
1239
+ });
1240
+ sessionId = sessionResult.sessionId;
1241
+ if (sessionResult.alreadyActive) {
1242
+ console.log(`${PREFIX} Agent session already active (${sessionId.slice(0, 8)}...)`);
1243
+ }
1244
+ else {
1245
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} Agent session started (${sessionId.slice(0, 8)}...)`);
1246
+ }
1247
+ }
1248
+ catch (err) {
1249
+ // Non-blocking: session start failure should not block claim
1250
+ console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
1251
+ }
1078
1252
  // Execute claim workflow
1079
- const ctx = {
1253
+ const baseCtx = {
1080
1254
  args,
1081
1255
  id,
1082
1256
  laneK,
@@ -1088,6 +1262,18 @@ async function main() {
1088
1262
  BACKLOG_PATH,
1089
1263
  claimedMode,
1090
1264
  fixableIssues, // WU-1361: Pass fixable issues for worktree application
1265
+ stagedChanges,
1266
+ };
1267
+ let updatedTitle = title;
1268
+ if (!args.noPush) {
1269
+ updatedTitle = (await applyCanonicalClaimUpdate(baseCtx, sessionId)) || updatedTitle;
1270
+ // Refresh origin/main after push-only update so worktrees start from canonical state
1271
+ await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
1272
+ }
1273
+ const ctx = {
1274
+ ...baseCtx,
1275
+ sessionId,
1276
+ updatedTitle,
1091
1277
  };
1092
1278
  if (args.branchOnly) {
1093
1279
  await claimBranchOnlyMode(ctx);
@@ -1112,6 +1298,7 @@ async function main() {
1112
1298
  }
1113
1299
  // Guard main() for testability (WU-1366)
1114
1300
  import { fileURLToPath } from 'node:url';
1301
+ import { runCLI } from './cli-entry-point.js';
1115
1302
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
1116
- main();
1303
+ runCLI(main);
1117
1304
  }