@lumenflow/cli 2.2.2 → 2.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 (120) hide show
  1. package/README.md +147 -57
  2. package/dist/__tests__/agent-log-issue.test.js +56 -0
  3. package/dist/__tests__/cli-entry-point.test.js +66 -17
  4. package/dist/__tests__/cli-subprocess.test.js +25 -0
  5. package/dist/__tests__/init.test.js +298 -0
  6. package/dist/__tests__/initiative-plan.test.js +340 -0
  7. package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
  8. package/dist/__tests__/merge-block.test.js +220 -0
  9. package/dist/__tests__/release.test.js +61 -0
  10. package/dist/__tests__/safe-git.test.js +191 -0
  11. package/dist/__tests__/state-doctor.test.js +274 -0
  12. package/dist/__tests__/wu-done.test.js +36 -0
  13. package/dist/__tests__/wu-edit.test.js +119 -0
  14. package/dist/__tests__/wu-prep.test.js +108 -0
  15. package/dist/agent-issues-query.js +4 -3
  16. package/dist/agent-log-issue.js +25 -4
  17. package/dist/backlog-prune.js +5 -4
  18. package/dist/cli-entry-point.js +11 -1
  19. package/dist/doctor.js +368 -0
  20. package/dist/flow-bottlenecks.js +6 -5
  21. package/dist/flow-report.js +4 -3
  22. package/dist/gates.js +356 -101
  23. package/dist/guard-locked.js +4 -3
  24. package/dist/guard-worktree-commit.js +4 -3
  25. package/dist/init.js +517 -86
  26. package/dist/initiative-add-wu.js +4 -3
  27. package/dist/initiative-bulk-assign-wus.js +8 -5
  28. package/dist/initiative-create.js +73 -37
  29. package/dist/initiative-edit.js +37 -21
  30. package/dist/initiative-list.js +4 -3
  31. package/dist/initiative-plan.js +337 -0
  32. package/dist/initiative-status.js +4 -3
  33. package/dist/lane-health.js +377 -0
  34. package/dist/lane-suggest.js +382 -0
  35. package/dist/mem-checkpoint.js +2 -2
  36. package/dist/mem-cleanup.js +2 -2
  37. package/dist/mem-context.js +306 -0
  38. package/dist/mem-create.js +2 -2
  39. package/dist/mem-delete.js +293 -0
  40. package/dist/mem-inbox.js +2 -2
  41. package/dist/mem-index.js +211 -0
  42. package/dist/mem-init.js +1 -1
  43. package/dist/mem-profile.js +207 -0
  44. package/dist/mem-promote.js +254 -0
  45. package/dist/mem-ready.js +2 -2
  46. package/dist/mem-signal.js +2 -2
  47. package/dist/mem-start.js +2 -2
  48. package/dist/mem-summarize.js +2 -2
  49. package/dist/mem-triage.js +2 -2
  50. package/dist/merge-block.js +222 -0
  51. package/dist/metrics-cli.js +7 -4
  52. package/dist/metrics-snapshot.js +4 -3
  53. package/dist/orchestrate-initiative.js +10 -4
  54. package/dist/orchestrate-monitor.js +379 -31
  55. package/dist/release.js +69 -29
  56. package/dist/signal-cleanup.js +296 -0
  57. package/dist/spawn-list.js +6 -5
  58. package/dist/state-bootstrap.js +5 -4
  59. package/dist/state-cleanup.js +360 -0
  60. package/dist/state-doctor-fix.js +196 -0
  61. package/dist/state-doctor.js +501 -0
  62. package/dist/validate-agent-skills.js +4 -3
  63. package/dist/validate-agent-sync.js +4 -3
  64. package/dist/validate-backlog-sync.js +4 -3
  65. package/dist/validate-skills-spec.js +4 -3
  66. package/dist/validate.js +4 -3
  67. package/dist/wu-block.js +3 -3
  68. package/dist/wu-claim.js +208 -98
  69. package/dist/wu-cleanup.js +5 -4
  70. package/dist/wu-create.js +71 -46
  71. package/dist/wu-delete.js +88 -60
  72. package/dist/wu-deps.js +6 -5
  73. package/dist/wu-done-check.js +34 -0
  74. package/dist/wu-done.js +39 -12
  75. package/dist/wu-edit.js +63 -28
  76. package/dist/wu-infer-lane.js +7 -6
  77. package/dist/wu-preflight.js +23 -81
  78. package/dist/wu-prep.js +125 -0
  79. package/dist/wu-prune.js +4 -3
  80. package/dist/wu-recover.js +88 -22
  81. package/dist/wu-repair.js +7 -6
  82. package/dist/wu-spawn.js +226 -270
  83. package/dist/wu-status.js +4 -3
  84. package/dist/wu-unblock.js +5 -5
  85. package/dist/wu-unlock-lane.js +4 -3
  86. package/dist/wu-validate.js +5 -4
  87. package/package.json +16 -7
  88. package/templates/core/.lumenflow/constraints.md.template +192 -0
  89. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  90. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  91. package/templates/core/AGENTS.md.template +60 -0
  92. package/templates/core/LUMENFLOW.md.template +255 -0
  93. package/templates/core/UPGRADING.md.template +121 -0
  94. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  95. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  96. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  97. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  98. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  99. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  100. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  101. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  102. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  103. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  104. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  105. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  106. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  107. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  108. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  109. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  110. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  111. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  112. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  113. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  114. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  115. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  116. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  117. package/templates/vendors/cline/.clinerules.template +53 -0
  118. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  119. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  120. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
package/dist/wu-claim.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * WU Claim Helper
4
5
  *
@@ -8,7 +9,7 @@
8
9
  * 3) Create a dedicated worktree+branch for the WU
9
10
  *
10
11
  * Usage:
11
- * node tools/wu-claim.mjs --id WU-334 --lane Intelligence \
12
+ * node tools/wu-claim.ts --id WU-334 --lane Intelligence \
12
13
  * [--worktree worktrees/intelligence-wu-334] [--branch lane/intelligence/wu-334]
13
14
  *
14
15
  * WU-2542: This script imports utilities from @lumenflow/core package.
@@ -18,21 +19,24 @@ import { existsSync, readFileSync, rmSync } from 'node:fs';
18
19
  import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
19
20
  import path from 'node:path';
20
21
  import { isOrphanWorktree } from '@lumenflow/core/dist/orphan-detector.js';
21
- // WU-1352: Use centralized YAML functions from wu-yaml.mjs
22
+ // WU-1352: Use centralized YAML functions from wu-yaml.ts
22
23
  import { parseYAML, stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
23
24
  import { assertTransition } from '@lumenflow/core/dist/state-machine.js';
24
- import { checkLaneFree, validateLaneFormat } from '@lumenflow/core/dist/lane-checker.js';
25
+ import { checkLaneFree, validateLaneFormat, checkWipJustification, } from '@lumenflow/core/dist/lane-checker.js';
25
26
  // WU-1603: Atomic lane locking to prevent TOCTOU race conditions
26
27
  import { acquireLaneLock, releaseLaneLock, checkLaneLock, forceRemoveStaleLock, } from '@lumenflow/core/dist/lane-lock.js';
27
28
  // WU-1825: Import from unified code-path-validator (consolidates 3 validators)
28
- import { validateLaneCodePaths, logLaneValidationWarnings, } from '@lumenflow/core/dist/code-path-validator.js';
29
+ // WU-1213: Using deprecated sync API - async validate() requires larger refactor (separate WU)
30
+ import {
31
+ // eslint-disable-next-line sonarjs/deprecation -- sync API required for current architecture
32
+ validateLaneCodePaths, logLaneValidationWarnings, } from '@lumenflow/core/dist/code-path-validator.js';
29
33
  // WU-1574: parseBacklogFrontmatter/getSectionHeadings removed - state store replaces backlog parsing
30
34
  import { detectConflicts } from '@lumenflow/core/dist/code-paths-overlap.js';
31
35
  import { getGitForCwd, createGitForPath } from '@lumenflow/core/dist/git-adapter.js';
32
36
  import { die } from '@lumenflow/core/dist/error-handler.js';
33
37
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
34
38
  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, GIT_REFS, MICRO_WORKTREE_OPERATIONS, COMMIT_FORMATS, EMOJI, FILE_SYSTEM, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
39
+ 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/dist/wu-constants.js';
36
40
  import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
37
41
  import { ensureOnMain, ensureMainUpToDate } from '@lumenflow/core/dist/wu-helpers.js';
38
42
  import { emitWUFlowEvent } from '@lumenflow/core/dist/telemetry.js';
@@ -51,7 +55,9 @@ import { WUStateStore } from '@lumenflow/core/dist/wu-state-store.js';
51
55
  import { generateBacklog, generateStatus } from '@lumenflow/core/dist/backlog-generator.js';
52
56
  // WU-2411: Import resume helpers for agent handoff
53
57
  import { resumeClaimForHandoff, getWorktreeUncommittedChanges, formatUncommittedChanges, createHandoffCheckpoint, } from '@lumenflow/core/dist/wu-claim-resume.js';
54
- // ensureOnMain() moved to wu-helpers.mjs (WU-1256)
58
+ // WU-1211: Import initiative validation for status auto-progression
59
+ import { shouldProgressInitiativeStatus, findInitiative, writeInitiative, getInitiativeWUs, } from '@lumenflow/initiatives/dist/index.js';
60
+ // ensureOnMain() moved to wu-helpers.ts (WU-1256)
55
61
  async function ensureCleanOrClaimOnlyWhenNoAuto() {
56
62
  // Require staged claim edits only if running with --no-auto
57
63
  const status = await getGitForCwd().getStatus();
@@ -258,7 +264,52 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
258
264
  // Write file
259
265
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes WU files
260
266
  await writeFile(WU_PATH, out, { encoding: FILE_SYSTEM.UTF8 });
261
- return doc.title || '';
267
+ // WU-1211: Return both title and initiative for status progression check
268
+ return { title: doc.title || '', initiative: doc.initiative || null };
269
+ }
270
+ /**
271
+ * WU-1211: Check and progress initiative status from draft/open to in_progress.
272
+ *
273
+ * Called when a WU with an initiative field is claimed. If this is the first
274
+ * WU being claimed for the initiative, progress the initiative status.
275
+ *
276
+ * @param {string} worktreePath - Path to micro-worktree (or main)
277
+ * @param {string} initiativeRef - Initiative ID or slug
278
+ * @param {string} wuId - WU ID being claimed
279
+ * @returns {Promise<{updated: boolean, initPath: string|null}>} Result
280
+ */
281
+ async function maybeProgressInitiativeStatus(worktreePath, initiativeRef, wuId) {
282
+ try {
283
+ // Find the initiative
284
+ const initiative = findInitiative(initiativeRef);
285
+ if (!initiative) {
286
+ console.log(`${PREFIX} Initiative ${initiativeRef} not found (may be created later)`);
287
+ return { updated: false, initPath: null };
288
+ }
289
+ // Get all WUs for this initiative to check if any are in_progress
290
+ const wus = getInitiativeWUs(initiativeRef);
291
+ // Include the WU we're currently claiming as in_progress
292
+ const wusWithCurrent = wus.map((wu) => wu.id === wuId ? { ...wu, doc: { ...wu.doc, status: 'in_progress' } } : wu);
293
+ const wuDocs = wusWithCurrent.map((wu) => wu.doc);
294
+ // Check if initiative status should progress
295
+ const progressCheck = shouldProgressInitiativeStatus(initiative.doc, wuDocs);
296
+ if (!progressCheck.shouldProgress || !progressCheck.newStatus) {
297
+ return { updated: false, initPath: null };
298
+ }
299
+ // Update initiative status in worktree
300
+ const initRelativePath = initiative.path.replace(process.cwd() + '/', '');
301
+ const initAbsPath = path.join(worktreePath, initRelativePath);
302
+ // Read, update, write
303
+ const initDoc = { ...initiative.doc, status: progressCheck.newStatus };
304
+ writeInitiative(initAbsPath, initDoc);
305
+ console.log(`${PREFIX} ✅ Initiative ${initiativeRef} status progressed: ${initiative.doc.status} → ${progressCheck.newStatus}`);
306
+ return { updated: true, initPath: initRelativePath };
307
+ }
308
+ catch (error) {
309
+ // Non-fatal: log warning and continue
310
+ console.warn(`${PREFIX} ⚠️ Could not check initiative status progression: ${error.message}`);
311
+ return { updated: false, initPath: null };
312
+ }
262
313
  }
263
314
  async function addOrReplaceInProgressStatus(statusPath, id, title) {
264
315
  // Check file exists
@@ -351,7 +402,7 @@ async function appendClaimEventOnly(stateDir, id, title, lane) {
351
402
  export function getWorktreeCommitFiles(wuId) {
352
403
  return [
353
404
  `docs/04-operations/tasks/wu/${wuId}.yaml`,
354
- '.lumenflow/state/wu-events.jsonl', // WU-1740: Event store is source of truth
405
+ LUMENFLOW_PATHS.WU_EVENTS, // WU-1740: Event store is source of truth
355
406
  // WU-1746: Explicitly NOT including:
356
407
  // - docs/04-operations/tasks/backlog.md
357
408
  // - docs/04-operations/tasks/status.md
@@ -407,12 +458,7 @@ async function applyCanonicalClaimUpdate(ctx, sessionId) {
407
458
  let updatedTitle = '';
408
459
  const filesToCommit = args.noAuto && stagedChanges.length > 0
409
460
  ? stagedChanges.map((change) => change.filePath).filter(Boolean)
410
- : [
411
- WU_PATHS.WU(id),
412
- WU_PATHS.STATUS(),
413
- WU_PATHS.BACKLOG(),
414
- '.lumenflow/state/wu-events.jsonl',
415
- ];
461
+ : [WU_PATHS.WU(id), WU_PATHS.STATUS(), WU_PATHS.BACKLOG(), LUMENFLOW_PATHS.WU_EVENTS];
416
462
  console.log(`${PREFIX} Updating canonical claim state (push-only)...`);
417
463
  await withMicroWorktree({
418
464
  operation: MICRO_WORKTREE_OPERATIONS.WU_CLAIM,
@@ -433,10 +479,23 @@ async function applyCanonicalClaimUpdate(ctx, sessionId) {
433
479
  console.log(`${PREFIX} YAML fixes applied successfully`);
434
480
  }
435
481
  const microGit = createGitForPath(worktreePath);
436
- updatedTitle =
437
- (await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit)) || updatedTitle;
482
+ // WU-1211: updateWUYaml now returns {title, initiative}
483
+ const updateResult = await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit);
484
+ updatedTitle = updateResult.title || updatedTitle;
438
485
  await addOrReplaceInProgressStatus(microStatusPath, id, updatedTitle);
439
486
  await removeFromReadyAndAddToInProgressBacklog(microBacklogPath, id, updatedTitle, args.lane);
487
+ // WU-1211: Check and progress initiative status
488
+ let initPath = null;
489
+ if (updateResult.initiative) {
490
+ const initProgress = await maybeProgressInitiativeStatus(worktreePath, updateResult.initiative, id);
491
+ initPath = initProgress.initPath;
492
+ }
493
+ // Include initiative path in files to commit if updated
494
+ const allFilesToCommit = initPath ? [...filesToCommit, initPath] : filesToCommit;
495
+ return {
496
+ commitMessage: commitMsg,
497
+ files: allFilesToCommit,
498
+ };
440
499
  }
441
500
  return {
442
501
  commitMessage: commitMsg,
@@ -459,10 +518,14 @@ async function readWUTitle(id) {
459
518
  // Read file
460
519
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates WU files
461
520
  const text = await readFile(p, { encoding: FILE_SYSTEM.UTF8 });
462
- const m = text.match(/^title:\s*"?(.+?)"?$/m);
521
+ // Match title field - use RegExp.exec for sonarjs/prefer-regexp-exec compliance
522
+ // Regex is safe: runs on trusted WU YAML files with bounded input
523
+ // eslint-disable-next-line sonarjs/slow-regex -- Bounded input from WU YAML files
524
+ const titlePattern = /^title:\s*"?([^"\n]+)"?$/m;
525
+ const m = titlePattern.exec(text);
463
526
  return m ? m[1] : null;
464
527
  }
465
- // emitWUFlowEvent() moved to telemetry.mjs as emitWUFlowEvent() (WU-1256)
528
+ // emitWUFlowEvent() moved to telemetry.ts as emitWUFlowEvent() (WU-1256)
466
529
  /**
467
530
  * Check if there's already a Branch-Only WU in progress
468
531
  * Branch-Only mode doesn't support parallel WUs (only one WU at a time in main checkout)
@@ -492,11 +555,12 @@ async function checkExistingBranchOnlyWU(statusPath, currentWU) {
492
555
  else
493
556
  endIdx = startIdx + 1 + endIdx;
494
557
  // Extract WU IDs from In Progress section
558
+ // Use RegExp.exec for sonarjs/prefer-regexp-exec compliance
495
559
  const wuPattern = /\[?(WU-\d+)/i;
496
560
  const inProgressWUs = lines
497
561
  .slice(startIdx + 1, endIdx)
498
562
  .map((line) => {
499
- const match = line.match(wuPattern);
563
+ const match = wuPattern.exec(line);
500
564
  return match ? match[1].toUpperCase() : null;
501
565
  })
502
566
  .filter(Boolean)
@@ -566,7 +630,7 @@ async function handleOrphanCheck(lane, id) {
566
630
  WU_PATHS.BACKLOG(),
567
631
  WU_PATHS.STATUS(),
568
632
  `.lumenflow/stamps/${orphanId}.done`,
569
- '.lumenflow/state/wu-events.jsonl',
633
+ LUMENFLOW_PATHS.WU_EVENTS,
570
634
  ],
571
635
  };
572
636
  },
@@ -655,7 +719,12 @@ function handleCodePathOverlap(WU_PATH, STATUS_PATH, id, args) {
655
719
  });
656
720
  if (overlapCheck.hasBlocker && !args.forceOverlap) {
657
721
  const conflictList = overlapCheck.conflicts
658
- .map((c) => ` - ${c.wuid}: ${c.overlaps.slice(0, 3).join(', ')}${c.overlaps.length > 3 ? ` (+${c.overlaps.length - 3} more)` : ''}`)
722
+ .map((c) => {
723
+ const displayedOverlaps = c.overlaps.slice(0, 3).join(', ');
724
+ const remainingCount = c.overlaps.length - 3;
725
+ const suffix = remainingCount > 0 ? ` (+${remainingCount} more)` : '';
726
+ return ` - ${c.wuid}: ${displayedOverlaps}${suffix}`;
727
+ })
659
728
  .join(STRING_LITERALS.NEWLINE);
660
729
  die(`Code path overlap detected with in-progress WUs:\n\n${conflictList}\n\n` +
661
730
  `Merge conflicts are guaranteed if both WUs proceed.\n\n` +
@@ -727,11 +796,20 @@ async function claimBranchOnlyMode(ctx) {
727
796
  await ensureCleanOrClaimOnlyWhenNoAuto();
728
797
  }
729
798
  else {
730
- finalTitle =
731
- (await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId)) || finalTitle;
799
+ // WU-1211: updateWUYaml now returns {title, initiative}
800
+ const updateResult = await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId);
801
+ finalTitle = updateResult.title || finalTitle;
732
802
  await addOrReplaceInProgressStatus(STATUS_PATH, id, finalTitle);
733
803
  await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, finalTitle, args.lane);
734
- await getGitForCwd().add(`${JSON.stringify(WU_PATH)} ${JSON.stringify(STATUS_PATH)} ${JSON.stringify(BACKLOG_PATH)}`);
804
+ const filesToAdd = [WU_PATH, STATUS_PATH, BACKLOG_PATH];
805
+ // WU-1211: Progress initiative status if needed
806
+ if (updateResult.initiative) {
807
+ const initProgress = await maybeProgressInitiativeStatus(process.cwd(), updateResult.initiative, id);
808
+ if (initProgress.initPath) {
809
+ filesToAdd.push(initProgress.initPath);
810
+ }
811
+ }
812
+ await getGitForCwd().add(filesToAdd.map((f) => JSON.stringify(f)).join(' '));
735
813
  }
736
814
  await getGitForCwd().commit(msg);
737
815
  console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
@@ -741,10 +819,12 @@ async function claimBranchOnlyMode(ctx) {
741
819
  }
742
820
  // Summary
743
821
  console.log(`\n${PREFIX} Claim recorded in Branch-Only mode.`);
744
- console.log(`- WU: ${id}${finalTitle ? ` — ${finalTitle}` : ''}`);
822
+ const wuDisplay = finalTitle ? `- WU: ${id} — ${finalTitle}` : `- WU: ${id}`;
823
+ console.log(wuDisplay);
745
824
  console.log(`- Lane: ${args.lane}`);
746
825
  console.log(`- Mode: Branch-Only (no worktree)`);
747
- console.log(`${args.noPush ? `- Commit: ${msg}` : `- Branch: ${branch}`}`);
826
+ const refDisplay = args.noPush ? `- Commit: ${msg}` : `- Branch: ${branch}`;
827
+ console.log(refDisplay);
748
828
  console.log('\n⚠️ LIMITATION: Branch-Only mode does not support parallel WUs (WIP=1 across ALL lanes)');
749
829
  console.log('Next: work on this branch in the main checkout.');
750
830
  // WU-1360: Print next-steps checklist to prevent common mistakes
@@ -768,6 +848,82 @@ async function claimBranchOnlyMode(ctx) {
768
848
  // WU-1763: Print lifecycle nudge with tips for tool adoption
769
849
  printLifecycleNudge(id);
770
850
  }
851
+ /**
852
+ * WU-1213: Handle local-only claim metadata update (noPush mode).
853
+ * Extracted to reduce cognitive complexity of claimWorktreeMode.
854
+ *
855
+ * @returns {Promise<{finalTitle: string, initPathToCommit: string | null}>}
856
+ */
857
+ async function handleNoPushMetadataUpdate(ctx) {
858
+ const { args, id, worktree, worktreePath, WU_PATH, BACKLOG_PATH, claimedMode, fixableIssues, sessionId, title, updatedTitle, stagedChanges, } = ctx;
859
+ let finalTitle = updatedTitle || title;
860
+ let initPathToCommit = null;
861
+ if (args.noAuto) {
862
+ await applyStagedChangesToMicroWorktree(worktreePath, stagedChanges);
863
+ }
864
+ else {
865
+ const wtWUPath = path.join(worktreePath, WU_PATH);
866
+ const wtBacklogPath = path.join(worktreePath, BACKLOG_PATH);
867
+ if (fixableIssues && fixableIssues.length > 0) {
868
+ console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
869
+ autoFixWUYaml(wtWUPath);
870
+ console.log(`${PREFIX} YAML fixes applied successfully`);
871
+ }
872
+ const updateResult = await updateWUYaml(wtWUPath, id, args.lane, claimedMode, worktree, sessionId);
873
+ finalTitle = updateResult.title || finalTitle;
874
+ const wtStateDir = getStateStoreDirFromBacklog(wtBacklogPath);
875
+ await appendClaimEventOnly(wtStateDir, id, finalTitle, args.lane);
876
+ if (updateResult.initiative) {
877
+ const initProgress = await maybeProgressInitiativeStatus(worktreePath, updateResult.initiative, id);
878
+ initPathToCommit = initProgress.initPath;
879
+ }
880
+ }
881
+ return { finalTitle, initPathToCommit };
882
+ }
883
+ /**
884
+ * WU-1213: Setup worktree dependencies (symlink or full install).
885
+ * Extracted to reduce cognitive complexity of claimWorktreeMode.
886
+ */
887
+ async function setupWorktreeDependencies(worktreePath, originalCwd, skipSetup) {
888
+ // eslint-disable-next-line sonarjs/no-selector-parameter -- skipSetup mirrors CLI flag semantics
889
+ if (skipSetup) {
890
+ // WU-1443: Symlink-only mode for fast claims
891
+ const symlinkResult = symlinkNodeModules(worktreePath, console, originalCwd);
892
+ if (symlinkResult.created) {
893
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked (--skip-setup mode)`);
894
+ }
895
+ else if (symlinkResult.refused) {
896
+ console.warn(`${PREFIX} Warning: symlink refused: ${symlinkResult.reason}`);
897
+ console.warn(`${PREFIX} Run 'pnpm install' manually in the worktree`);
898
+ }
899
+ // WU-1579: Auto-symlink nested package node_modules for turbo typecheck
900
+ if (!symlinkResult.refused) {
901
+ const nestedResult = symlinkNestedNodeModules(worktreePath, originalCwd);
902
+ if (nestedResult.created > 0) {
903
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
904
+ }
905
+ }
906
+ }
907
+ else {
908
+ // WU-1023: Full setup mode (default) - run pnpm install with progress indicator
909
+ console.log(`${PREFIX} Installing worktree dependencies (this may take a moment)...`);
910
+ try {
911
+ const { execSync } = await import('node:child_process');
912
+ execSync('pnpm install --frozen-lockfile', {
913
+ cwd: worktreePath,
914
+ stdio: 'inherit',
915
+ timeout: 300000, // 5 minute timeout
916
+ });
917
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} Worktree dependencies installed`);
918
+ }
919
+ catch (installError) {
920
+ console.warn(`${PREFIX} Warning: pnpm install failed: ${installError.message}`);
921
+ console.warn(`${PREFIX} You may need to run 'pnpm install' manually in the worktree`);
922
+ console.log(`${PREFIX} Falling back to symlink approach...`);
923
+ applyFallbackSymlinks(worktreePath, originalCwd, console);
924
+ }
925
+ }
926
+ }
771
927
  /**
772
928
  * Execute worktree mode claim workflow
773
929
  *
@@ -788,14 +944,12 @@ async function claimBranchOnlyMode(ctx) {
788
944
  * - Cleaner rollback: delete worktree+branch = claim undone
789
945
  */
790
946
  async function claimWorktreeMode(ctx) {
791
- const { args, id, laneK, title, branch, worktree, WU_PATH, BACKLOG_PATH, claimedMode, fixableIssues, // Fixable issues from pre-flight validation
792
- sessionId, updatedTitle, stagedChanges, } = ctx;
947
+ const { args, id, laneK, title, branch, worktree, WU_PATH, updatedTitle } = ctx;
793
948
  const originalCwd = process.cwd();
794
949
  const worktreePath = path.resolve(worktree);
795
950
  let finalTitle = updatedTitle || title;
796
951
  const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
797
952
  // WU-1741: Step 1 - Create work worktree+branch from main
798
- // Branch creation IS the coordination lock (git prevents duplicate branch names)
799
953
  console.log(`${PREFIX} Creating worktree (branch = coordination lock)...`);
800
954
  const startPoint = args.noPush ? BRANCHES.MAIN : `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`;
801
955
  await getGitForCwd().worktreeAdd(worktree, branch, startPoint);
@@ -804,80 +958,27 @@ async function claimWorktreeMode(ctx) {
804
958
  const wtGit = createGitForPath(worktreePath);
805
959
  await wtGit.push(REMOTES.ORIGIN, branch, { setUpstream: true });
806
960
  }
961
+ // Handle local-only claim metadata update (noPush mode)
807
962
  if (args.noPush) {
808
- // Local-only claim (air-gapped) - update metadata inside worktree branch
809
- if (args.noAuto) {
810
- await applyStagedChangesToMicroWorktree(worktreePath, stagedChanges);
811
- }
812
- else {
813
- const wtWUPath = path.join(worktreePath, WU_PATH);
814
- const wtBacklogPath = path.join(worktreePath, BACKLOG_PATH);
815
- if (fixableIssues && fixableIssues.length > 0) {
816
- console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
817
- autoFixWUYaml(wtWUPath);
818
- console.log(`${PREFIX} YAML fixes applied successfully`);
819
- }
820
- finalTitle =
821
- (await updateWUYaml(wtWUPath, id, args.lane, claimedMode, worktree, sessionId)) ||
822
- finalTitle;
823
- // WU-1746: Only append claim event to state store - don't regenerate backlog.md/status.md
824
- const wtStateDir = getStateStoreDirFromBacklog(wtBacklogPath);
825
- await appendClaimEventOnly(wtStateDir, id, finalTitle, args.lane);
826
- }
963
+ const metadataResult = await handleNoPushMetadataUpdate({ ...ctx, worktreePath });
964
+ finalTitle = metadataResult.finalTitle;
965
+ // Commit metadata in worktree
827
966
  console.log(`${PREFIX} Committing claim metadata in worktree...`);
828
967
  const wtGit = createGitForPath(worktreePath);
829
968
  const filesToCommit = getWorktreeCommitFiles(id);
969
+ if (metadataResult.initPathToCommit) {
970
+ filesToCommit.push(metadataResult.initPathToCommit);
971
+ }
830
972
  await wtGit.add(filesToCommit);
831
973
  await wtGit.commit(commitMsg);
832
974
  console.log(`${PREFIX} ${EMOJI.SUCCESS} Claim committed: ${commitMsg}`);
833
975
  console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
834
976
  }
835
977
  // WU-1023: Auto-setup worktree dependencies
836
- // By default, run pnpm install to ensure all dependencies are built (including CLI dist)
837
- // Use --skip-setup to use symlink-only approach for faster claims when deps are already built
838
- if (args.skipSetup) {
839
- // WU-1443: Symlink-only mode for fast claims
840
- // WU-2238: Pass mainRepoPath to detect broken worktree-path symlinks
841
- const symlinkResult = symlinkNodeModules(worktreePath, console, originalCwd);
842
- if (symlinkResult.created) {
843
- console.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked (--skip-setup mode)`);
844
- }
845
- else if (symlinkResult.refused) {
846
- console.warn(`${PREFIX} Warning: symlink refused: ${symlinkResult.reason}`);
847
- console.warn(`${PREFIX} Run 'pnpm install' manually in the worktree`);
848
- }
849
- // WU-1579: Auto-symlink nested package node_modules for turbo typecheck
850
- if (!symlinkResult.refused) {
851
- const nestedResult = symlinkNestedNodeModules(worktreePath, originalCwd);
852
- if (nestedResult.created > 0) {
853
- console.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
854
- }
855
- }
856
- }
857
- else {
858
- // WU-1023: Full setup mode (default) - run pnpm install with progress indicator
859
- // This ensures CLI dist is built and all dependencies are properly resolved
860
- console.log(`${PREFIX} Installing worktree dependencies (this may take a moment)...`);
861
- try {
862
- const { execSync } = await import('node:child_process');
863
- execSync('pnpm install --frozen-lockfile', {
864
- cwd: worktreePath,
865
- stdio: 'inherit', // Shows progress output from pnpm
866
- timeout: 300000, // 5 minute timeout for full install
867
- });
868
- console.log(`${PREFIX} ${EMOJI.SUCCESS} Worktree dependencies installed`);
869
- }
870
- catch (installError) {
871
- // Non-fatal: warn but don't block claim
872
- console.warn(`${PREFIX} Warning: pnpm install failed: ${installError.message}`);
873
- console.warn(`${PREFIX} You may need to run 'pnpm install' manually in the worktree`);
874
- // Fall back to symlink approach so worktree is at least somewhat usable
875
- console.log(`${PREFIX} Falling back to symlink approach...`);
876
- applyFallbackSymlinks(worktreePath, originalCwd, console);
877
- }
878
- }
978
+ await setupWorktreeDependencies(worktreePath, originalCwd, args.skipSetup);
879
979
  console.log(`${PREFIX} Claim recorded in worktree`);
880
- console.log(`- WU: ${id}${finalTitle ? ` — ${finalTitle}` : ''}`);
980
+ const worktreeWuDisplay = finalTitle ? `- WU: ${id} — ${finalTitle}` : `- WU: ${id}`;
981
+ console.log(worktreeWuDisplay);
881
982
  console.log(`- Lane: ${args.lane}`);
882
983
  console.log(`- Worktree: ${worktreePath}`);
883
984
  console.log(`- Branch: ${branch}`);
@@ -1113,7 +1214,13 @@ async function main() {
1113
1214
  const doc = preflightValidateWU(WU_PATH, id);
1114
1215
  await handleOrphanCheck(args.lane, id);
1115
1216
  validateLaneFormatWithError(args.lane);
1217
+ // WU-1187: Check for WIP justification when WIP > 1 (soft enforcement - warning only)
1218
+ const wipJustificationCheck = checkWipJustification(args.lane);
1219
+ if (wipJustificationCheck.warning) {
1220
+ console.warn(`${PREFIX} ${wipJustificationCheck.warning}`);
1221
+ }
1116
1222
  // WU-1372: Lane-to-code_paths consistency check (advisory only, never blocks)
1223
+ // eslint-disable-next-line sonarjs/deprecation -- sync API required for current architecture
1117
1224
  const laneValidation = validateLaneCodePaths(doc, args.lane);
1118
1225
  logLaneValidationWarnings(laneValidation, PREFIX);
1119
1226
  // WU-1361: YAML schema validation at claim time
@@ -1190,11 +1297,14 @@ async function main() {
1190
1297
  const title = (await readWUTitle(id)) || '';
1191
1298
  const branch = args.branch || `lane/${laneK}/${idK}`;
1192
1299
  const worktree = args.worktree || `worktrees/${laneK}-${idK}`;
1193
- const claimedMode = args.branchOnly
1194
- ? CLAIMED_MODES.BRANCH_ONLY
1195
- : args.prMode
1196
- ? CLAIMED_MODES.WORKTREE_PR
1197
- : CLAIMED_MODES.WORKTREE;
1300
+ // Determine claimed mode based on flags (no nested ternaries for sonarjs compliance)
1301
+ let claimedMode = CLAIMED_MODES.WORKTREE;
1302
+ if (args.branchOnly) {
1303
+ claimedMode = CLAIMED_MODES.BRANCH_ONLY;
1304
+ }
1305
+ else if (args.prMode) {
1306
+ claimedMode = CLAIMED_MODES.WORKTREE_PR;
1307
+ }
1198
1308
  // Branch-Only mode validation
1199
1309
  if (args.branchOnly) {
1200
1310
  await validateBranchOnlyMode(STATUS_PATH, id);
@@ -1307,5 +1417,5 @@ async function main() {
1307
1417
  // path but import.meta.url resolves to the real path - they never match
1308
1418
  import { runCLI } from './cli-entry-point.js';
1309
1419
  if (import.meta.main) {
1310
- runCLI(main);
1420
+ void runCLI(main);
1311
1421
  }
@@ -191,7 +191,7 @@ async function main() {
191
191
  }
192
192
  const id = args.id.toUpperCase();
193
193
  const wu = readWU(WU_PATHS.WU(id), id);
194
- // Use kebab-case lane naming (match wu-claim.mjs logic)
194
+ // Use kebab-case lane naming (match wu-claim.ts logic)
195
195
  const laneK = wu.lane
196
196
  ?.toLowerCase()
197
197
  .trim()
@@ -267,9 +267,10 @@ async function main() {
267
267
  console.log('║ Branch deleted (local + remote)');
268
268
  console.log(BOX.BOT);
269
269
  }
270
- // Guard main() for testability (WU-1366)
271
- import { fileURLToPath } from 'node:url';
270
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
271
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
272
+ // path but import.meta.url resolves to the real path - they never match
272
273
  import { runCLI } from './cli-entry-point.js';
273
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
274
+ if (import.meta.main) {
274
275
  runCLI(main);
275
276
  }