@lumenflow/cli 2.2.1 → 2.3.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.
Files changed (119) 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 +28 -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 +468 -116
  23. package/dist/guard-locked.js +4 -3
  24. package/dist/guard-worktree-commit.js +4 -3
  25. package/dist/init.js +508 -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/signal-cleanup.js +296 -0
  56. package/dist/spawn-list.js +6 -5
  57. package/dist/state-bootstrap.js +5 -4
  58. package/dist/state-cleanup.js +360 -0
  59. package/dist/state-doctor-fix.js +196 -0
  60. package/dist/state-doctor.js +501 -0
  61. package/dist/validate-agent-skills.js +4 -3
  62. package/dist/validate-agent-sync.js +4 -3
  63. package/dist/validate-backlog-sync.js +7 -84
  64. package/dist/validate-skills-spec.js +4 -3
  65. package/dist/validate.js +7 -107
  66. package/dist/wu-block.js +3 -3
  67. package/dist/wu-claim.js +208 -98
  68. package/dist/wu-cleanup.js +5 -4
  69. package/dist/wu-create.js +71 -46
  70. package/dist/wu-delete.js +88 -60
  71. package/dist/wu-deps.js +6 -5
  72. package/dist/wu-done-check.js +34 -0
  73. package/dist/wu-done.js +60 -24
  74. package/dist/wu-edit.js +63 -28
  75. package/dist/wu-infer-lane.js +7 -6
  76. package/dist/wu-preflight.js +23 -81
  77. package/dist/wu-prep.js +125 -0
  78. package/dist/wu-prune.js +4 -3
  79. package/dist/wu-recover.js +88 -22
  80. package/dist/wu-repair.js +7 -6
  81. package/dist/wu-spawn.js +226 -270
  82. package/dist/wu-status.js +4 -3
  83. package/dist/wu-unblock.js +5 -5
  84. package/dist/wu-unlock-lane.js +4 -3
  85. package/dist/wu-validate.js +5 -4
  86. package/package.json +16 -7
  87. package/templates/core/.lumenflow/constraints.md.template +192 -0
  88. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  89. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  90. package/templates/core/AGENTS.md.template +60 -0
  91. package/templates/core/LUMENFLOW.md.template +255 -0
  92. package/templates/core/UPGRADING.md.template +121 -0
  93. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  94. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  95. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  96. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  97. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  98. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  99. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  100. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  101. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  102. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  103. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  104. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  105. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  106. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  107. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  108. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  109. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  110. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  111. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  112. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  113. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  114. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  115. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  116. package/templates/vendors/cline/.clinerules.template +53 -0
  117. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  118. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  119. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
package/dist/wu-create.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 Create Helper (WU-1262, WU-1439)
4
5
  *
@@ -30,7 +31,7 @@ import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
30
31
  import { die } from '@lumenflow/core/dist/error-handler.js';
31
32
  import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
32
33
  import { join } from 'node:path';
33
- // WU-1352: Use centralized YAML functions from wu-yaml.mjs
34
+ // WU-1352: Use centralized YAML functions from wu-yaml.ts
34
35
  import { stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
35
36
  // WU-1428: Use date-utils for consistent YYYY-MM-DD format (library-first)
36
37
  import { todayISO } from '@lumenflow/core/dist/date-utils.js';
@@ -48,6 +49,8 @@ import { COMMIT_FORMATS, FILE_SYSTEM, READINESS_UI, STRING_LITERALS, } from '@lu
48
49
  import { ensureOnMain, validateWUIDFormat } from '@lumenflow/core/dist/wu-helpers.js';
49
50
  // WU-1439: Use shared micro-worktree helper
50
51
  import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
52
+ // WU-1246: Auto-generate WU IDs when --id not provided
53
+ import { generateWuIdWithRetry } from '@lumenflow/core/dist/wu-id-generator.js';
51
54
  // WU-1620: Import spec completeness validator for readiness summary
52
55
  import { validateSpecCompleteness } from '@lumenflow/core/dist/wu-done-validators.js';
53
56
  // WU-1620: Import readWU to read back created YAML for validation
@@ -56,6 +59,8 @@ import { readWU } from '@lumenflow/core/dist/wu-yaml.js';
56
59
  import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
57
60
  // WU-1025: Import placeholder validator for inline content validation
58
61
  import { validateNoPlaceholders, buildPlaceholderErrorMessage, } from '@lumenflow/core/dist/wu-validator.js';
62
+ // WU-1211: Import initiative validation for phase check
63
+ import { checkInitiativePhases, findInitiative } from '@lumenflow/initiatives/dist/index.js';
59
64
  /** Log prefix for console output */
60
65
  const LOG_PREFIX = '[wu:create]';
61
66
  /** Micro-worktree operation name */
@@ -77,21 +82,16 @@ const MIN_CONFIDENCE_FOR_WARNING = 30;
77
82
  * Non-blocking - just logs a warning if a better lane is suggested.
78
83
  *
79
84
  * @param {string} providedLane - Lane provided by the user
80
- * @param {string|undefined} codePathsRaw - Comma-separated code paths
85
+ * @param {string[]|undefined} codePathsArray - Code paths array from Commander
81
86
  * @param {string} title - WU title (used as fallback description)
82
87
  * @param {string|undefined} description - WU description
83
88
  */
84
- export function warnIfBetterLaneExists(providedLane, codePathsRaw, title, description) {
85
- if (!codePathsRaw && !description) {
89
+ export function warnIfBetterLaneExists(providedLane, codePathsArray, title, description) {
90
+ if (!codePathsArray?.length && !description) {
86
91
  return;
87
92
  }
88
93
  try {
89
- const codePaths = codePathsRaw
90
- ? codePathsRaw
91
- .split(',')
92
- .map((s) => s.trim())
93
- .filter(Boolean)
94
- : [];
94
+ const codePaths = codePathsArray ?? [];
95
95
  const descForInference = description || title;
96
96
  const suggestion = inferSubLane(codePaths, descForInference);
97
97
  // Only warn if suggestion differs and confidence is meaningful
@@ -105,7 +105,7 @@ export function warnIfBetterLaneExists(providedLane, codePathsRaw, title, descri
105
105
  const isDifferentLane = providedParent !== suggestedParent;
106
106
  if (isMoreSpecific || isDifferentLane) {
107
107
  console.warn(`${LOG_PREFIX} Consider using "${suggestion.lane}" (${suggestion.confidence}% match) instead of "${providedLane}"`);
108
- console.warn(`${LOG_PREFIX} Run: pnpm wu:infer-lane --paths "${codePathsRaw || ''}" --desc "${title}"`);
108
+ console.warn(`${LOG_PREFIX} Run: pnpm wu:infer-lane --paths "${codePaths.join(' ')}" --desc "${title}"`);
109
109
  }
110
110
  }
111
111
  catch {
@@ -179,19 +179,12 @@ function displayReadinessSummary(id) {
179
179
  console.warn(`${LOG_PREFIX} ⚠️ Could not validate readiness: ${err.message}`);
180
180
  }
181
181
  }
182
- // Helper to parse comma-separated strings into arrays (DRY)
183
- const parseCommaSeparated = (value) => value
184
- ? value
185
- .split(',')
186
- .map((s) => s.trim())
187
- .filter(Boolean)
188
- : [];
189
182
  function mergeSpecRefs(specRefs, extraRef) {
190
- const refs = parseCommaSeparated(specRefs);
183
+ const refs = specRefs ? [...specRefs] : [];
191
184
  if (extraRef && !refs.includes(extraRef)) {
192
185
  refs.push(extraRef);
193
186
  }
194
- return refs.length > 0 ? refs.join(',') : undefined;
187
+ return refs;
195
188
  }
196
189
  function createPlanTemplate(wuId, title) {
197
190
  const plansDir = getPlansDir();
@@ -218,11 +211,12 @@ function createPlanTemplate(wuId, title) {
218
211
  }
219
212
  function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
220
213
  const { description, acceptance, codePaths, testPathsManual, testPathsUnit, testPathsE2e, initiative, phase, blockedBy, blocks, labels, assignedTo, exposure, userJourney, uiPairingWus, specRefs, } = opts;
221
- const code_paths = parseCommaSeparated(codePaths);
214
+ // Arrays come directly from Commander.js repeatable options - no parsing needed
215
+ const code_paths = codePaths ?? [];
222
216
  const tests = {
223
- manual: parseCommaSeparated(testPathsManual),
224
- unit: parseCommaSeparated(testPathsUnit),
225
- e2e: parseCommaSeparated(testPathsE2e),
217
+ manual: testPathsManual ?? [],
218
+ unit: testPathsUnit ?? [],
219
+ e2e: testPathsE2e ?? [],
226
220
  };
227
221
  return {
228
222
  id,
@@ -243,14 +237,14 @@ function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
243
237
  requires_review: false,
244
238
  ...(initiative && { initiative }),
245
239
  ...(phase && { phase: parseInt(phase, 10) }),
246
- ...(blockedBy && { blocked_by: blockedBy.split(',').map((s) => s.trim()) }),
247
- ...(blocks && { blocks: blocks.split(',').map((s) => s.trim()) }),
248
- ...(labels && { labels: labels.split(',').map((s) => s.trim()) }),
240
+ ...(blockedBy?.length && { blocked_by: blockedBy }),
241
+ ...(blocks?.length && { blocks }),
242
+ ...(labels?.length && { labels }),
249
243
  ...(assignedTo && { assigned_to: assignedTo }),
250
244
  ...(exposure && { exposure }),
251
245
  ...(userJourney && { user_journey: userJourney }),
252
- ...(uiPairingWus && { ui_pairing_wus: parseCommaSeparated(uiPairingWus) }),
253
- ...(specRefs && { spec_refs: parseCommaSeparated(specRefs) }),
246
+ ...(uiPairingWus?.length && { ui_pairing_wus: uiPairingWus }),
247
+ ...(specRefs?.length && { spec_refs: specRefs }),
254
248
  };
255
249
  }
256
250
  export function validateCreateSpec({ id, lane, title, priority, type, opts, }) {
@@ -453,6 +447,7 @@ async function getDefaultAssignedTo() {
453
447
  return '';
454
448
  }
455
449
  }
450
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- main() orchestrates multi-step WU creation workflow
456
451
  async function main() {
457
452
  const args = createWUParser({
458
453
  name: 'wu-create',
@@ -488,12 +483,32 @@ async function main() {
488
483
  // WU-1062: External plan options for wu:create
489
484
  WU_CREATE_OPTIONS.plan,
490
485
  ],
491
- required: ['id', 'lane', 'title'],
486
+ required: ['lane', 'title'], // WU-1246: --id is now optional (auto-generated if not provided)
492
487
  allowPositionalId: false,
493
488
  });
494
- console.log(`${LOG_PREFIX} Creating WU ${args.id} in ${args.lane} lane...`);
495
- // Pre-flight checks (validation only - no main modification)
496
- validateWUIDFormat(args.id);
489
+ // WU-1246: Auto-generate WU ID if not provided
490
+ let wuId;
491
+ if (args.id) {
492
+ wuId = args.id;
493
+ // Validate explicitly provided ID
494
+ validateWUIDFormat(wuId);
495
+ }
496
+ else {
497
+ // Auto-generate next sequential ID
498
+ console.log(`${LOG_PREFIX} Auto-generating WU ID...`);
499
+ try {
500
+ wuId = await generateWuIdWithRetry();
501
+ console.log(`${LOG_PREFIX} Generated WU ID: ${wuId}`);
502
+ }
503
+ catch (error) {
504
+ die(`Failed to auto-generate WU ID: ${error.message}\n\n` +
505
+ `Options:\n` +
506
+ ` 1. Retry the command (transient file system issue)\n` +
507
+ ` 2. Provide an explicit ID: --id WU-XXXX\n` +
508
+ ` 3. Check for race conditions if running parallel wu:create`);
509
+ }
510
+ }
511
+ console.log(`${LOG_PREFIX} Creating WU ${wuId} in ${args.lane} lane...`);
497
512
  // Validate lane format (sub-lane or parent-only)
498
513
  try {
499
514
  validateLaneFormat(args.lane);
@@ -512,16 +527,16 @@ async function main() {
512
527
  // WU-2330: Warn if a more specific sub-lane matches code_paths or description
513
528
  warnIfBetterLaneExists(args.lane, args.codePaths, args.title, args.description);
514
529
  await ensureOnMain(getGitForCwd());
515
- checkWUExists(args.id);
530
+ checkWUExists(wuId);
516
531
  // WU-1368: Get assigned_to from flag or git config user.email
517
532
  const assignedTo = args.assignedTo || (await getDefaultAssignedTo());
518
533
  if (!assignedTo) {
519
534
  console.warn(`${LOG_PREFIX} ⚠️ No assigned_to set - WU will need manual assignment`);
520
535
  }
521
- const planSpecRef = args.plan ? getPlanProtocolRef(args.id) : undefined;
536
+ const planSpecRef = args.plan ? getPlanProtocolRef(wuId) : undefined;
522
537
  const mergedSpecRefs = mergeSpecRefs(args.specRefs, planSpecRef);
523
538
  const createSpecValidation = validateCreateSpec({
524
- id: args.id,
539
+ id: wuId,
525
540
  lane: args.lane,
526
541
  title: args.title,
527
542
  priority: args.priority || DEFAULT_PRIORITY,
@@ -552,7 +567,17 @@ async function main() {
552
567
  die(`${LOG_PREFIX} ❌ Spec validation failed:\n\n${errorList}`);
553
568
  }
554
569
  console.log(`${LOG_PREFIX} ✅ Spec validation passed`);
555
- const specRefsList = parseCommaSeparated(mergedSpecRefs);
570
+ // WU-1211: Warn if linking to initiative with no phases defined
571
+ if (args.initiative) {
572
+ const initiative = findInitiative(args.initiative);
573
+ if (initiative) {
574
+ const phaseCheck = checkInitiativePhases(initiative.doc);
575
+ if (!phaseCheck.hasPhases && phaseCheck.warning) {
576
+ console.warn(`${LOG_PREFIX} ⚠️ ${phaseCheck.warning}`);
577
+ }
578
+ }
579
+ }
580
+ const specRefsList = mergedSpecRefs;
556
581
  const specRefsValidation = validateSpecRefs(specRefsList);
557
582
  if (!specRefsValidation.valid) {
558
583
  const errorList = specRefsValidation.errors
@@ -566,7 +591,7 @@ async function main() {
566
591
  }
567
592
  }
568
593
  if (args.plan) {
569
- createPlanTemplate(args.id, args.title);
594
+ createPlanTemplate(wuId, args.title);
570
595
  }
571
596
  // Transaction: micro-worktree isolation (WU-1439)
572
597
  try {
@@ -577,11 +602,11 @@ async function main() {
577
602
  try {
578
603
  await withMicroWorktree({
579
604
  operation: OPERATION_NAME,
580
- id: args.id,
605
+ id: wuId,
581
606
  logPrefix: LOG_PREFIX,
582
607
  execute: async ({ worktreePath }) => {
583
608
  // Create WU YAML in micro-worktree
584
- const wuPath = createWUYamlInWorktree(worktreePath, args.id, args.lane, args.title, priority, type, {
609
+ const wuPath = createWUYamlInWorktree(worktreePath, wuId, args.lane, args.title, priority, type, {
585
610
  // Initiative system fields (WU-1247)
586
611
  initiative: args.initiative,
587
612
  phase: args.phase,
@@ -605,10 +630,10 @@ async function main() {
605
630
  specRefs: mergedSpecRefs,
606
631
  });
607
632
  // Update backlog.md in micro-worktree
608
- const backlogPath = updateBacklogInWorktree(worktreePath, args.id, args.lane, args.title);
633
+ const backlogPath = updateBacklogInWorktree(worktreePath, wuId, args.lane, args.title);
609
634
  // Build commit message
610
635
  const shortTitle = truncateTitle(args.title);
611
- const commitMessage = COMMIT_FORMATS.CREATE(args.id, shortTitle);
636
+ const commitMessage = COMMIT_FORMATS.CREATE(wuId, shortTitle);
612
637
  // Return commit message and files to commit
613
638
  return {
614
639
  commitMessage,
@@ -626,12 +651,12 @@ async function main() {
626
651
  }
627
652
  }
628
653
  console.log(`\n${LOG_PREFIX} ✅ Transaction complete!`);
629
- console.log(`\nWU ${args.id} created successfully:`);
630
- console.log(` File: ${WU_PATHS.WU(args.id)}`);
654
+ console.log(`\nWU ${wuId} created successfully:`);
655
+ console.log(` File: ${WU_PATHS.WU(wuId)}`);
631
656
  console.log(` Lane: ${args.lane}`);
632
657
  console.log(` Status: ready`);
633
658
  // WU-1620: Display readiness summary
634
- displayReadinessSummary(args.id);
659
+ displayReadinessSummary(wuId);
635
660
  }
636
661
  catch (error) {
637
662
  die(`Transaction failed: ${error.message}\n\n` +
@@ -645,5 +670,5 @@ async function main() {
645
670
  // path but import.meta.url resolves to the real path - they never match
646
671
  import { runCLI } from './cli-entry-point.js';
647
672
  if (import.meta.main) {
648
- runCLI(main);
673
+ void runCLI(main);
649
674
  }
package/dist/wu-delete.js CHANGED
@@ -122,35 +122,49 @@ async function deleteSingleWU(id, dryRun) {
122
122
  await ensureCleanWorkingTree();
123
123
  await ensureMainUpToDate(getGitForCwd(), 'wu:delete');
124
124
  console.log(`${PREFIX} Deleting via micro-worktree...`);
125
- await withMicroWorktree({
126
- operation: MICRO_WORKTREE_OPERATIONS.WU_DELETE,
127
- id: id,
128
- logPrefix: PREFIX,
129
- execute: async ({ worktreePath, gitWorktree }) => {
130
- const wuFilePath = join(worktreePath, wuPath);
131
- const backlogFilePath = join(worktreePath, WU_PATHS.BACKLOG());
132
- unlinkSync(wuFilePath);
133
- console.log(`${PREFIX} Deleted ${id}.yaml`);
134
- const stampPath = join(worktreePath, getStampPath(id));
135
- if (existsSync(stampPath)) {
136
- unlinkSync(stampPath);
137
- console.log(`${PREFIX} ✅ Deleted stamp ${id}.done`);
138
- }
139
- const removedFromBacklog = removeFromBacklog(backlogFilePath, id);
140
- if (removedFromBacklog) {
141
- console.log(`${PREFIX} ✅ Removed ${id} from backlog.md`);
142
- }
143
- else {
144
- console.log(`${PREFIX} ℹ️ ${id} was not found in backlog.md`);
145
- }
146
- await gitWorktree.add('.');
147
- const commitMessage = `docs: delete ${id.toLowerCase()}`;
148
- return {
149
- commitMessage,
150
- files: [],
151
- };
152
- },
153
- });
125
+ // WU-1245: Set LUMENFLOW_WU_TOOL for pre-push hook allowlist
126
+ const previousWuTool = process.env.LUMENFLOW_WU_TOOL;
127
+ process.env.LUMENFLOW_WU_TOOL = MICRO_WORKTREE_OPERATIONS.WU_DELETE;
128
+ try {
129
+ await withMicroWorktree({
130
+ operation: MICRO_WORKTREE_OPERATIONS.WU_DELETE,
131
+ id: id,
132
+ logPrefix: PREFIX,
133
+ execute: async ({ worktreePath, gitWorktree }) => {
134
+ const wuFilePath = join(worktreePath, wuPath);
135
+ const backlogFilePath = join(worktreePath, WU_PATHS.BACKLOG());
136
+ unlinkSync(wuFilePath);
137
+ console.log(`${PREFIX} ✅ Deleted ${id}.yaml`);
138
+ const stampPath = join(worktreePath, getStampPath(id));
139
+ if (existsSync(stampPath)) {
140
+ unlinkSync(stampPath);
141
+ console.log(`${PREFIX} ✅ Deleted stamp ${id}.done`);
142
+ }
143
+ const removedFromBacklog = removeFromBacklog(backlogFilePath, id);
144
+ if (removedFromBacklog) {
145
+ console.log(`${PREFIX} ✅ Removed ${id} from backlog.md`);
146
+ }
147
+ else {
148
+ console.log(`${PREFIX} ℹ️ ${id} was not found in backlog.md`);
149
+ }
150
+ await gitWorktree.add('.');
151
+ const commitMessage = `docs: delete ${id.toLowerCase()}`;
152
+ return {
153
+ commitMessage,
154
+ files: [],
155
+ };
156
+ },
157
+ });
158
+ }
159
+ finally {
160
+ // Restore previous LUMENFLOW_WU_TOOL value
161
+ if (previousWuTool === undefined) {
162
+ delete process.env.LUMENFLOW_WU_TOOL;
163
+ }
164
+ else {
165
+ process.env.LUMENFLOW_WU_TOOL = previousWuTool;
166
+ }
167
+ }
154
168
  console.log(`${PREFIX} ✅ Successfully deleted ${id}`);
155
169
  console.log(`${PREFIX} Changes pushed to origin/main`);
156
170
  }
@@ -179,39 +193,53 @@ async function deleteBatchWUs(ids, dryRun) {
179
193
  await ensureCleanWorkingTree();
180
194
  await ensureMainUpToDate(getGitForCwd(), 'wu:delete --batch');
181
195
  console.log(`${PREFIX} Deleting ${ids.length} WU(s) via micro-worktree...`);
182
- await withMicroWorktree({
183
- operation: MICRO_WORKTREE_OPERATIONS.WU_DELETE,
184
- id: `batch-${ids.length}`,
185
- logPrefix: PREFIX,
186
- execute: async ({ worktreePath, gitWorktree }) => {
187
- const backlogFilePath = join(worktreePath, WU_PATHS.BACKLOG());
188
- for (const { id, wuPath } of wusToDelete) {
189
- const wuFilePath = join(worktreePath, wuPath);
190
- unlinkSync(wuFilePath);
191
- console.log(`${PREFIX} Deleted ${id}.yaml`);
192
- }
193
- for (const id of stampsToDelete) {
194
- const stampPath = join(worktreePath, getStampPath(id));
195
- if (existsSync(stampPath)) {
196
- unlinkSync(stampPath);
197
- console.log(`${PREFIX} ✅ Deleted stamp ${id}.done`);
196
+ // WU-1245: Set LUMENFLOW_WU_TOOL for pre-push hook allowlist
197
+ const previousWuTool = process.env.LUMENFLOW_WU_TOOL;
198
+ process.env.LUMENFLOW_WU_TOOL = MICRO_WORKTREE_OPERATIONS.WU_DELETE;
199
+ try {
200
+ await withMicroWorktree({
201
+ operation: MICRO_WORKTREE_OPERATIONS.WU_DELETE,
202
+ id: `batch-${ids.length}`,
203
+ logPrefix: PREFIX,
204
+ execute: async ({ worktreePath, gitWorktree }) => {
205
+ const backlogFilePath = join(worktreePath, WU_PATHS.BACKLOG());
206
+ for (const { id, wuPath } of wusToDelete) {
207
+ const wuFilePath = join(worktreePath, wuPath);
208
+ unlinkSync(wuFilePath);
209
+ console.log(`${PREFIX} ✅ Deleted ${id}.yaml`);
198
210
  }
199
- }
200
- for (const { id } of wusToDelete) {
201
- const removed = removeFromBacklog(backlogFilePath, id);
202
- if (removed) {
203
- console.log(`${PREFIX} ✅ Removed ${id} from backlog.md`);
211
+ for (const id of stampsToDelete) {
212
+ const stampPath = join(worktreePath, getStampPath(id));
213
+ if (existsSync(stampPath)) {
214
+ unlinkSync(stampPath);
215
+ console.log(`${PREFIX} ✅ Deleted stamp ${id}.done`);
216
+ }
204
217
  }
205
- }
206
- await gitWorktree.add('.');
207
- const idList = ids.map((id) => id.toLowerCase()).join(', ');
208
- const commitMessage = `chore(repair): delete ${ids.length} orphaned wus (${idList})`;
209
- return {
210
- commitMessage,
211
- files: [],
212
- };
213
- },
214
- });
218
+ for (const { id } of wusToDelete) {
219
+ const removed = removeFromBacklog(backlogFilePath, id);
220
+ if (removed) {
221
+ console.log(`${PREFIX} Removed ${id} from backlog.md`);
222
+ }
223
+ }
224
+ await gitWorktree.add('.');
225
+ const idList = ids.map((id) => id.toLowerCase()).join(', ');
226
+ const commitMessage = `chore(repair): delete ${ids.length} orphaned wus (${idList})`;
227
+ return {
228
+ commitMessage,
229
+ files: [],
230
+ };
231
+ },
232
+ });
233
+ }
234
+ finally {
235
+ // Restore previous LUMENFLOW_WU_TOOL value
236
+ if (previousWuTool === undefined) {
237
+ delete process.env.LUMENFLOW_WU_TOOL;
238
+ }
239
+ else {
240
+ process.env.LUMENFLOW_WU_TOOL = previousWuTool;
241
+ }
242
+ }
215
243
  console.log(`${PREFIX} ✅ Successfully deleted ${ids.length} WU(s)`);
216
244
  console.log(`${PREFIX} Changes pushed to origin/main`);
217
245
  }
package/dist/wu-deps.js CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
14
14
  import { die } from '@lumenflow/core/dist/error-handler.js';
15
- import { buildDependencyGraph, renderASCII, renderMermaid, validateGraph, } from '@lumenflow/core/dist/dependency-graph.js';
15
+ import { buildDependencyGraphAsync, renderASCII, renderMermaid, validateGraph, } from '@lumenflow/core/dist/dependency-graph.js';
16
16
  import { OUTPUT_FORMATS } from '@lumenflow/initiatives/dist/initiative-constants.js';
17
17
  import { PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
18
18
  async function main() {
@@ -31,7 +31,7 @@ async function main() {
31
31
  die(`Invalid WU ID format: "${wuId}"\n\nExpected format: WU-<number> (e.g., WU-1247)`);
32
32
  }
33
33
  console.log(`[wu:deps] Building dependency graph...`);
34
- const graph = buildDependencyGraph();
34
+ const graph = await buildDependencyGraphAsync();
35
35
  if (!graph.has(wuId)) {
36
36
  die(`WU not found in graph: ${wuId}\n\nEnsure the WU exists in docs/04-operations/tasks/wu/`);
37
37
  }
@@ -112,9 +112,10 @@ function renderGraphJSON(graph, rootId, depth, direction) {
112
112
  }
113
113
  return JSON.stringify(output, null, 2);
114
114
  }
115
- // Guard main() for testability (WU-1366)
116
- import { fileURLToPath } from 'node:url';
115
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
116
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
117
+ // path but import.meta.url resolves to the real path - they never match
117
118
  import { runCLI } from './cli-entry-point.js';
118
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
119
+ if (import.meta.main) {
119
120
  runCLI(main);
120
121
  }
@@ -0,0 +1,34 @@
1
+ import { createGitForPath } from '@lumenflow/core/git-adapter';
2
+ import { die } from '@lumenflow/core/error-handler';
3
+ import { LOG_PREFIX } from '@lumenflow/core/dist/wu-constants.js';
4
+ /**
5
+ * WU-1169: Ensure worktree is clean before wu:done operations.
6
+ *
7
+ * Prevents WU-1943 rollback loops where uncommitted changes in the worktree
8
+ * cause auto-rebase to fail, triggering an expensive restoration that wipes
9
+ * the uncommitted changes.
10
+ *
11
+ * This check HALTS wu:done immediately if the worktree is dirty.
12
+ *
13
+ * @param {string} worktreePath - Absolute path to the worktree
14
+ */
15
+ export async function ensureCleanWorktree(worktreePath) {
16
+ try {
17
+ const git = createGitForPath(worktreePath);
18
+ const status = await git.getStatus();
19
+ if (status.trim()) {
20
+ die(`Worktree has uncommitted changes. Cannot proceed with wu:done.\n\n` +
21
+ `Path: ${worktreePath}\n\n` +
22
+ `Uncommitted changes:\n${status}\n\n` +
23
+ `❌ BLOCKING: Uncommitted changes would be lost during auto-rebase.\n\n` +
24
+ `Fix:\n` +
25
+ ` 1. cd worktrees/<lane>-wu-xxx\n` +
26
+ ` 2. git add . && git commit -m "wip: ..."\n` +
27
+ ` 3. Retry pnpm wu:done --id WU-XXXX`);
28
+ }
29
+ }
30
+ catch (err) {
31
+ // If worktree is missing or git fails, let the flow continue (handled by other checks)
32
+ console.warn(`${LOG_PREFIX.DONE} Warning: Could not check worktree status: ${err.message}`);
33
+ }
34
+ }