@lumenflow/cli 2.2.2 → 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 (118) 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__/safe-git.test.js +191 -0
  10. package/dist/__tests__/state-doctor.test.js +274 -0
  11. package/dist/__tests__/wu-done.test.js +36 -0
  12. package/dist/__tests__/wu-edit.test.js +119 -0
  13. package/dist/__tests__/wu-prep.test.js +108 -0
  14. package/dist/agent-issues-query.js +4 -3
  15. package/dist/agent-log-issue.js +25 -4
  16. package/dist/backlog-prune.js +5 -4
  17. package/dist/cli-entry-point.js +11 -1
  18. package/dist/doctor.js +368 -0
  19. package/dist/flow-bottlenecks.js +6 -5
  20. package/dist/flow-report.js +4 -3
  21. package/dist/gates.js +356 -101
  22. package/dist/guard-locked.js +4 -3
  23. package/dist/guard-worktree-commit.js +4 -3
  24. package/dist/init.js +508 -86
  25. package/dist/initiative-add-wu.js +4 -3
  26. package/dist/initiative-bulk-assign-wus.js +8 -5
  27. package/dist/initiative-create.js +73 -37
  28. package/dist/initiative-edit.js +37 -21
  29. package/dist/initiative-list.js +4 -3
  30. package/dist/initiative-plan.js +337 -0
  31. package/dist/initiative-status.js +4 -3
  32. package/dist/lane-health.js +377 -0
  33. package/dist/lane-suggest.js +382 -0
  34. package/dist/mem-checkpoint.js +2 -2
  35. package/dist/mem-cleanup.js +2 -2
  36. package/dist/mem-context.js +306 -0
  37. package/dist/mem-create.js +2 -2
  38. package/dist/mem-delete.js +293 -0
  39. package/dist/mem-inbox.js +2 -2
  40. package/dist/mem-index.js +211 -0
  41. package/dist/mem-init.js +1 -1
  42. package/dist/mem-profile.js +207 -0
  43. package/dist/mem-promote.js +254 -0
  44. package/dist/mem-ready.js +2 -2
  45. package/dist/mem-signal.js +2 -2
  46. package/dist/mem-start.js +2 -2
  47. package/dist/mem-summarize.js +2 -2
  48. package/dist/mem-triage.js +2 -2
  49. package/dist/merge-block.js +222 -0
  50. package/dist/metrics-cli.js +7 -4
  51. package/dist/metrics-snapshot.js +4 -3
  52. package/dist/orchestrate-initiative.js +10 -4
  53. package/dist/orchestrate-monitor.js +379 -31
  54. package/dist/signal-cleanup.js +296 -0
  55. package/dist/spawn-list.js +6 -5
  56. package/dist/state-bootstrap.js +5 -4
  57. package/dist/state-cleanup.js +360 -0
  58. package/dist/state-doctor-fix.js +196 -0
  59. package/dist/state-doctor.js +501 -0
  60. package/dist/validate-agent-skills.js +4 -3
  61. package/dist/validate-agent-sync.js +4 -3
  62. package/dist/validate-backlog-sync.js +4 -3
  63. package/dist/validate-skills-spec.js +4 -3
  64. package/dist/validate.js +4 -3
  65. package/dist/wu-block.js +3 -3
  66. package/dist/wu-claim.js +208 -98
  67. package/dist/wu-cleanup.js +5 -4
  68. package/dist/wu-create.js +71 -46
  69. package/dist/wu-delete.js +88 -60
  70. package/dist/wu-deps.js +6 -5
  71. package/dist/wu-done-check.js +34 -0
  72. package/dist/wu-done.js +39 -12
  73. package/dist/wu-edit.js +63 -28
  74. package/dist/wu-infer-lane.js +7 -6
  75. package/dist/wu-preflight.js +23 -81
  76. package/dist/wu-prep.js +125 -0
  77. package/dist/wu-prune.js +4 -3
  78. package/dist/wu-recover.js +88 -22
  79. package/dist/wu-repair.js +7 -6
  80. package/dist/wu-spawn.js +226 -270
  81. package/dist/wu-status.js +4 -3
  82. package/dist/wu-unblock.js +5 -5
  83. package/dist/wu-unlock-lane.js +4 -3
  84. package/dist/wu-validate.js +5 -4
  85. package/package.json +16 -7
  86. package/templates/core/.lumenflow/constraints.md.template +192 -0
  87. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  88. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  89. package/templates/core/AGENTS.md.template +60 -0
  90. package/templates/core/LUMENFLOW.md.template +255 -0
  91. package/templates/core/UPGRADING.md.template +121 -0
  92. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  93. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  94. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  95. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  96. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  97. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  98. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  99. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  100. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  101. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  102. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  103. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  104. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  105. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  106. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  107. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  108. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  109. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  110. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  111. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  112. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  113. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  114. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  115. package/templates/vendors/cline/.clinerules.template +53 -0
  116. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  117. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  118. 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
+ }
package/dist/wu-done.js CHANGED
@@ -31,11 +31,16 @@
31
31
  * WU-2542: This script imports utilities from @lumenflow/core package.
32
32
  * Full migration to thin shim pending @lumenflow/core CLI export implementation.
33
33
  */
34
+ // WU-1153: wu:done guard for uncommitted code_paths is implemented in core package
35
+ // The guard runs in executeWorktreeCompletion() before metadata transaction
36
+ // See: packages/@lumenflow/core/src/wu-done-validation.ts
34
37
  import { execSync } from 'node:child_process';
35
38
  import prettyMs from 'pretty-ms';
36
39
  import { runGates } from './gates.js';
37
40
  import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
38
41
  import { die } from '@lumenflow/core/dist/error-handler.js';
42
+ // WU-1223: Location detection for worktree check
43
+ import { resolveLocation } from '@lumenflow/core/dist/context/location-resolver.js';
39
44
  import { existsSync, readFileSync, mkdirSync, appendFileSync, unlinkSync, statSync } from 'node:fs';
40
45
  import path from 'node:path';
41
46
  // WU-1825: Import from unified code-path-validator (consolidates 3 validators)
@@ -54,7 +59,9 @@ validateAllPreCommitHooks,
54
59
  validateTypeVsCodePathsPreflight, buildTypeVsCodePathsErrorMessage, } from '@lumenflow/core/dist/wu-done-validators.js';
55
60
  // WU-1825: validateCodePathsExist moved to unified code-path-validator
56
61
  import { validateCodePathsExist } from '@lumenflow/core/dist/code-path-validator.js';
57
- import { BRANCHES, REMOTES, PATTERNS, DEFAULTS, LOG_PREFIX, EMOJI, GIT, SESSION, WU_STATUS, PKG_MANAGER, SCRIPTS, CLI_FLAGS, FILE_SYSTEM, EXIT_CODES, STRING_LITERALS, MICRO_WORKTREE_OPERATIONS, TELEMETRY_STEPS, SKIP_GATES_REASONS, CHECKPOINT_MESSAGES, } from '@lumenflow/core/dist/wu-constants.js';
62
+ import { BRANCHES, REMOTES, PATTERNS, DEFAULTS, LOG_PREFIX, EMOJI, GIT, SESSION, WU_STATUS, PKG_MANAGER, SCRIPTS, CLI_FLAGS, FILE_SYSTEM, EXIT_CODES, STRING_LITERALS, MICRO_WORKTREE_OPERATIONS, TELEMETRY_STEPS, SKIP_GATES_REASONS, CHECKPOINT_MESSAGES,
63
+ // WU-1223: Location types for worktree detection
64
+ CONTEXT_VALIDATION, } from '@lumenflow/core/dist/wu-constants.js';
58
65
  import { printGateFailureBox, printStatusPreview } from '@lumenflow/core/dist/wu-done-ui.js';
59
66
  import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
60
67
  import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
@@ -86,6 +93,7 @@ import { SpawnStatus } from '@lumenflow/core/dist/spawn-registry-schema.js';
86
93
  // WU-1999: Exposure validation for UI pairing
87
94
  // WU-2022: Feature accessibility validation (blocking)
88
95
  import { validateExposure, validateFeatureAccessibility, } from '@lumenflow/core/dist/wu-validation.js';
96
+ import { ensureCleanWorktree } from './wu-done-check.js';
89
97
  // WU-1588: Memory layer constants
90
98
  const MEMORY_SIGNAL_TYPES = {
91
99
  WU_COMPLETION: 'wu_completion',
@@ -148,7 +156,7 @@ async function validateClaimMetadataBeforeGates(id, worktreePath, yamlStatus) {
148
156
  ` pnpm wu:repair-claim --id ${id}\n\n` +
149
157
  `After repair, retry:\n` +
150
158
  ` pnpm wu:done --id ${id}\n\n` +
151
- `See: docs/04-operations/_frameworks/lumenflow/agent/onboarding/troubleshooting-wu-done.md for more recovery options.`);
159
+ `See: https://lumenflow.dev/reference/troubleshooting-wu-done/ for more recovery options.`);
152
160
  }
153
161
  export function printExposureWarnings(wu, options = {}) {
154
162
  // Validate exposure
@@ -445,7 +453,7 @@ export async function isBranchAlreadyMerged(branch) {
445
453
  return false;
446
454
  }
447
455
  }
448
- // WU-1281: isDocsOnlyByPaths removed - use shouldSkipWebTests from path-classifiers.mjs
456
+ // WU-1281: isDocsOnlyByPaths removed - use shouldSkipWebTests from path-classifiers.ts
449
457
  // The validators already use shouldSkipWebTests via detectDocsOnlyByPaths wrapper.
450
458
  // Keeping the export for backward compatibility but re-exporting the canonical function.
451
459
  export { shouldSkipWebTests as isDocsOnlyByPaths } from '@lumenflow/core/dist/path-classifiers.js';
@@ -501,7 +509,7 @@ function getCommitHeaderLimit() {
501
509
  return DEFAULTS.MAX_COMMIT_SUBJECT; // Fallback if config is malformed or missing
502
510
  }
503
511
  }
504
- // ensureOnMain() moved to wu-helpers.mjs (WU-1256)
512
+ // ensureOnMain() moved to wu-helpers.ts (WU-1256)
505
513
  /**
506
514
  * Ensure working tree is clean before wu:done operations.
507
515
  *
@@ -719,7 +727,7 @@ async function ensureMainUpToDate() {
719
727
  * by calling /usr/bin/git directly or if PATH was not set up correctly.
720
728
  *
721
729
  * Context: WU-630 (detective layer, Layer 3 of 4)
722
- * See: docs/04-operations/_frameworks/lumenflow/02-playbook.md §4.6
730
+ * See: https://lumenflow.dev/reference/playbook/ §4.6
723
731
  */
724
732
  function runTripwireCheck() {
725
733
  const violations = scanLogForViolations();
@@ -764,7 +772,7 @@ function runTripwireCheck() {
764
772
  console.error(' 3. Escalate to human if critical files were deleted\n');
765
773
  }
766
774
  console.error('📖 See detailed recovery steps:');
767
- console.error(' docs/04-operations/_frameworks/lumenflow/02-playbook.md §4.6\n');
775
+ console.error(' https://lumenflow.dev/reference/playbook/ §4.6\n');
768
776
  console.error('🚫 DO NOT proceed with wu:done until violations are remediated.\n');
769
777
  console.error('Fix violations first, then retry wu:done.\n');
770
778
  // Also rotate log (cleanup old entries)
@@ -847,7 +855,7 @@ async function auditSkipCosGates(id, reason) {
847
855
  appendFileSync(auditPath, `${line}\n`, { encoding: FILE_SYSTEM.UTF8 });
848
856
  console.log(`${LOG_PREFIX.DONE} ${EMOJI.MEMO} Skip-COS-gates event logged to ${auditPath}`);
849
857
  }
850
- // WU-2308: validateAllPreCommitHooks moved to wu-done-validators.mjs
858
+ // WU-2308: validateAllPreCommitHooks moved to wu-done-validators.ts
851
859
  // Now accepts worktreePath parameter to run audit from worktree context
852
860
  /**
853
861
  * Check if node_modules in worktree may be stale
@@ -1000,13 +1008,13 @@ async function validateStagedFiles(id, isDocsOnly = false) {
1000
1008
  }
1001
1009
  }
1002
1010
  // Note: updateStatusRemoveInProgress, addToStatusCompleted, and moveWUToDoneBacklog
1003
- // have been extracted to tools/lib/wu-status-updater.mjs and imported above (WU-1163)
1011
+ // have been extracted to tools/lib/wu-status-updater.ts and imported above (WU-1163)
1004
1012
  //
1005
- // Note: ensureStamp has been replaced with createStamp from tools/lib/stamp-utils.mjs (WU-1163)
1013
+ // Note: ensureStamp has been replaced with createStamp from tools/lib/stamp-utils.ts (WU-1163)
1006
1014
  //
1007
1015
  // Note: readWUPreferWorktree, detectCurrentWorktree, defaultWorktreeFrom, detectWorkspaceMode,
1008
1016
  // defaultBranchFrom, branchExists, runCleanup have been extracted to
1009
- // tools/lib/wu-done-validators.mjs and imported above (WU-1215)
1017
+ // tools/lib/wu-done-validators.ts and imported above (WU-1215)
1010
1018
  /**
1011
1019
  * Validate Branch-Only mode requirements before proceeding
1012
1020
  * @param {string} laneBranch - Expected lane branch name
@@ -1862,7 +1870,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
1862
1870
  console.error(`\n${LOG_PREFIX.DONE} ${EMOJI.FAILURE} COS governance gates failed`);
1863
1871
  console.error('\nTo fix:');
1864
1872
  console.error(' 1. Add required evidence to governance.evidence field in WU YAML');
1865
- console.error(' 2. See: docs/04-operations/_frameworks/cos/evidence-format.md');
1873
+ console.error(' 2. See: https://lumenflow.dev/reference/evidence-format/');
1866
1874
  console.error('\nEmergency bypass (creates audit trail):');
1867
1875
  console.error(` pnpm wu:done --id ${id} --skip-cos-gates --reason "explanation"`);
1868
1876
  die('Abort: WU not completed. Fix governance evidence and retry pnpm wu:done.');
@@ -1915,8 +1923,22 @@ function printStateHUD({ id, docMain, isBranchOnly, isDocsOnly, derivedWorktree,
1915
1923
  async function main() {
1916
1924
  // Allow pre-push hook to recognize wu:done automation (WU-1030)
1917
1925
  process.env.LUMENFLOW_WU_TOOL = 'wu-done';
1918
- // Validate CLI arguments and WU ID format (extracted to wu-done-validators.mjs)
1926
+ // Validate CLI arguments and WU ID format (extracted to wu-done-validators.ts)
1919
1927
  const { args, id } = validateInputs(process.argv);
1928
+ // WU-1223: Check if running from worktree - wu:done now requires main checkout
1929
+ // Agents should use wu:prep from worktree, then wu:done from main
1930
+ const { LOCATION_TYPES } = CONTEXT_VALIDATION;
1931
+ const currentLocation = await resolveLocation();
1932
+ if (currentLocation.type === LOCATION_TYPES.WORKTREE) {
1933
+ die(`${EMOJI.FAILURE} wu:done must be run from main checkout, not from a worktree.\n\n` +
1934
+ `Current location: ${currentLocation.cwd}\n\n` +
1935
+ `WU-1223 NEW WORKFLOW:\n` +
1936
+ ` 1. From worktree, run: pnpm wu:prep --id ${id}\n` +
1937
+ ` (This runs gates and prepares for completion)\n\n` +
1938
+ ` 2. From main, run: cd ${currentLocation.mainCheckout} && pnpm wu:done --id ${id}\n` +
1939
+ ` (This does merge + cleanup only)\n\n` +
1940
+ `Use wu:prep to run gates in the worktree, then wu:done from main for merge/cleanup.`);
1941
+ }
1920
1942
  // Detect workspace mode and calculate paths (WU-1215: extracted to validators module)
1921
1943
  const pathInfo = await detectModeAndPaths(id, args);
1922
1944
  const { WU_PATH, STATUS_PATH, BACKLOG_PATH, STAMPS_DIR, docMain, isBranchOnly, derivedWorktree, docForValidation: initialDocForValidation, isDocsOnly, } = pathInfo;
@@ -1940,6 +1962,11 @@ async function main() {
1940
1962
  }
1941
1963
  const effectiveDerivedWorktree = effectiveBranchOnly ? null : derivedWorktree;
1942
1964
  const effectiveWorktreePath = effectiveBranchOnly ? null : resolvedWorktreePath;
1965
+ // WU-1169: Ensure worktree is clean before proceeding
1966
+ // This prevents WU-1943 rollback loops if rebase fails due to dirty state
1967
+ if (effectiveWorktreePath && existsSync(effectiveWorktreePath)) {
1968
+ await ensureCleanWorktree(effectiveWorktreePath);
1969
+ }
1943
1970
  // Pre-flight checks (WU-1215: extracted to executePreFlightChecks function)
1944
1971
  const preFlightResult = await executePreFlightChecks({
1945
1972
  id,