@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
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * WU Prep Helper (WU-1223)
4
+ *
5
+ * Prepares a WU for completion by running gates and generating docs in the worktree.
6
+ * After successful prep, prints copy-paste instruction to run wu:done from main.
7
+ *
8
+ * Workflow:
9
+ * 1. Verify we're in a worktree (error if in main checkout)
10
+ * 2. Run gates in the worktree
11
+ * 3. Generate docs (if applicable)
12
+ * 4. Print copy-paste instruction for wu:done from main
13
+ *
14
+ * Usage:
15
+ * pnpm wu:prep --id WU-XXX [--docs-only]
16
+ *
17
+ * @module
18
+ */
19
+ import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
20
+ import { die } from '@lumenflow/core/dist/error-handler.js';
21
+ import { resolveLocation } from '@lumenflow/core/dist/context/location-resolver.js';
22
+ import { readWU } from '@lumenflow/core/dist/wu-yaml.js';
23
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
24
+ import { CONTEXT_VALIDATION, PATTERNS, EXIT_CODES, WU_STATUS, EMOJI, } from '@lumenflow/core/dist/wu-constants.js';
25
+ import { runGates } from './gates.js';
26
+ const { LOCATION_TYPES } = CONTEXT_VALIDATION;
27
+ /**
28
+ * Log prefix for wu:prep command output.
29
+ */
30
+ const PREP_PREFIX = '[wu-prep]';
31
+ /**
32
+ * GATES_OPTIONS for wu:prep command.
33
+ * Subset of gates options relevant for prep.
34
+ */
35
+ const PREP_OPTIONS = {
36
+ docsOnly: {
37
+ name: 'docsOnly',
38
+ flags: '--docs-only',
39
+ description: 'Run docs-only gates (format, spec-linter)',
40
+ },
41
+ };
42
+ /**
43
+ * Print success message with copy-paste instruction.
44
+ */
45
+ function printSuccessMessage(wuId, mainCheckout) {
46
+ console.log('');
47
+ console.log(`${PREP_PREFIX} ${EMOJI.SUCCESS} ${wuId}: Prep completed successfully!`);
48
+ console.log('');
49
+ console.log(`${PREP_PREFIX} Gates passed in worktree.`);
50
+ console.log('');
51
+ console.log(`${PREP_PREFIX} Next step - copy and paste this command:`);
52
+ console.log('');
53
+ console.log(` cd ${mainCheckout} && pnpm wu:done --id ${wuId}`);
54
+ console.log('');
55
+ }
56
+ /**
57
+ * Main entry point.
58
+ */
59
+ async function main() {
60
+ // Parse arguments
61
+ const args = createWUParser({
62
+ name: 'wu-prep',
63
+ description: 'Prepare WU for completion (run gates in worktree)',
64
+ options: [WU_OPTIONS.id, PREP_OPTIONS.docsOnly],
65
+ required: ['id'],
66
+ allowPositionalId: true,
67
+ });
68
+ const id = args.id.toUpperCase();
69
+ if (!PATTERNS.WU_ID.test(id)) {
70
+ die(`Invalid WU id '${args.id}'. Expected format WU-123`);
71
+ }
72
+ // Detect location
73
+ const location = await resolveLocation();
74
+ // WU-1223: wu:prep MUST be run from worktree, not main
75
+ if (location.type !== LOCATION_TYPES.WORKTREE) {
76
+ die(`${EMOJI.FAILURE} wu:prep must be run from a worktree, not ${location.type}.\n\n` +
77
+ `Current location: ${location.cwd}\n\n` +
78
+ `If you have a worktree for ${id}, navigate to it first:\n` +
79
+ ` cd worktrees/<lane>-${id.toLowerCase()}\n\n` +
80
+ `If you don't have a worktree yet, claim the WU first:\n` +
81
+ ` pnpm wu:claim --id ${id} --lane "<lane>"`);
82
+ }
83
+ // Verify the worktree is for the correct WU
84
+ if (location.worktreeWuId && location.worktreeWuId !== id) {
85
+ console.warn(`${PREP_PREFIX} ${EMOJI.WARNING} Worktree is for ${location.worktreeWuId}, but you specified ${id}.`);
86
+ console.warn(`${PREP_PREFIX} Proceeding with ${id} as specified.`);
87
+ }
88
+ // Read WU YAML to validate it exists and check status
89
+ const wuPath = WU_PATHS.WU(id);
90
+ let doc;
91
+ try {
92
+ doc = readWU(wuPath, id);
93
+ }
94
+ catch (error) {
95
+ die(`Failed to read WU ${id}: ${error.message}\n\n` +
96
+ `Options:\n` +
97
+ ` 1. Check if WU file exists: ls -la ${wuPath}\n` +
98
+ ` 2. Validate YAML syntax: pnpm wu:validate --id ${id}`);
99
+ }
100
+ // Validate WU status is in_progress
101
+ if (doc.status !== WU_STATUS.IN_PROGRESS) {
102
+ die(`${EMOJI.FAILURE} WU ${id} status is '${doc.status}', expected '${WU_STATUS.IN_PROGRESS}'.\n\n` +
103
+ `wu:prep can only be run on WUs that are in progress.`);
104
+ }
105
+ console.log(`${PREP_PREFIX} Preparing ${id} for completion...`);
106
+ console.log(`${PREP_PREFIX} Location: ${location.cwd}`);
107
+ console.log(`${PREP_PREFIX} Main checkout: ${location.mainCheckout}`);
108
+ console.log('');
109
+ // Run gates in the worktree
110
+ console.log(`${PREP_PREFIX} Running gates in worktree...`);
111
+ const gatesResult = await runGates({
112
+ cwd: location.cwd,
113
+ docsOnly: args.docsOnly,
114
+ });
115
+ if (!gatesResult) {
116
+ die(`${EMOJI.FAILURE} Gates failed in worktree.\n\n` +
117
+ `Fix the gate failures and run wu:prep again.`);
118
+ }
119
+ // Success - print copy-paste instruction
120
+ printSuccessMessage(id, location.mainCheckout);
121
+ }
122
+ main().catch((e) => {
123
+ console.error(e.message);
124
+ process.exit(EXIT_CODES.ERROR);
125
+ });
package/dist/wu-prune.js CHANGED
@@ -260,9 +260,10 @@ This tool:
260
260
  }
261
261
  process.exit(totalErrors > 0 ? EXIT_CODES.ERROR : EXIT_CODES.SUCCESS);
262
262
  }
263
- // Guard main() for testability (WU-1366)
264
- import { fileURLToPath } from 'node:url';
263
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
264
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
265
+ // path but import.meta.url resolves to the real path - they never match
265
266
  import { runCLI } from './cli-entry-point.js';
266
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
267
+ if (import.meta.main) {
267
268
  runCLI(main);
268
269
  }
@@ -24,9 +24,11 @@ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
24
24
  import { readWU, writeWU } from '@lumenflow/core/dist/wu-yaml.js';
25
25
  import { CONTEXT_VALIDATION, EMOJI, WU_STATUS, DEFAULTS, toKebab, } from '@lumenflow/core/dist/wu-constants.js';
26
26
  import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
27
- import { join } from 'node:path';
27
+ import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
28
+ import { join, relative } from 'node:path';
28
29
  const { RECOVERY_ACTIONS } = CONTEXT_VALIDATION;
29
30
  const LOG_PREFIX = '[wu:recover]';
31
+ const OPERATION_NAME = 'wu-recover';
30
32
  /**
31
33
  * Valid recovery action types
32
34
  */
@@ -107,6 +109,9 @@ function getWorktreePath(wuId, lane) {
107
109
  }
108
110
  /**
109
111
  * Execute resume action - reconcile state and continue
112
+ *
113
+ * WU-1226: Uses micro-worktree isolation for all state changes.
114
+ * Changes are pushed via merge, not direct file modification on main.
110
115
  */
111
116
  async function executeResume(wuId) {
112
117
  console.log(`${LOG_PREFIX} Executing resume action for ${wuId}...`);
@@ -116,17 +121,46 @@ async function executeResume(wuId) {
116
121
  return false;
117
122
  }
118
123
  const doc = readWU(wuPath, wuId);
119
- // Update status to in_progress if it was ready
120
- if (doc.status === WU_STATUS.READY) {
121
- doc.status = WU_STATUS.IN_PROGRESS;
122
- writeWU(wuPath, doc);
124
+ // If status is already in_progress, nothing to do
125
+ if (doc.status !== WU_STATUS.READY) {
126
+ console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Resume completed - WU already has status '${doc.status}'`);
127
+ return true;
128
+ }
129
+ // WU-1226: Use micro-worktree isolation for state changes
130
+ try {
131
+ await withMicroWorktree({
132
+ operation: OPERATION_NAME,
133
+ id: wuId,
134
+ logPrefix: LOG_PREFIX,
135
+ pushOnly: true, // Don't modify local main
136
+ execute: async ({ worktreePath }) => {
137
+ // Read WU in micro-worktree context
138
+ const microWuPath = join(worktreePath, relative(process.cwd(), wuPath));
139
+ const microDoc = readWU(microWuPath, wuId);
140
+ // Update status to in_progress
141
+ microDoc.status = WU_STATUS.IN_PROGRESS;
142
+ writeWU(microWuPath, microDoc);
143
+ return {
144
+ commitMessage: `fix(wu-recover): resume ${wuId} - set status to in_progress`,
145
+ files: [relative(process.cwd(), wuPath)],
146
+ };
147
+ },
148
+ });
123
149
  console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Updated ${wuId} status to in_progress`);
150
+ console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Resume completed - you can continue working in the worktree`);
151
+ return true;
152
+ }
153
+ catch (err) {
154
+ console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Micro-worktree operation failed: ${err.message}`);
155
+ return false;
124
156
  }
125
- console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Resume completed - you can continue working in the worktree`);
126
- return true;
127
157
  }
128
158
  /**
129
159
  * Execute reset action - discard worktree and reset to ready
160
+ *
161
+ * WU-1226: Uses micro-worktree isolation for WU YAML state changes.
162
+ * Worktree removal still happens directly (git operation, not file write).
163
+ * Changes are pushed via merge, not direct file modification on main.
130
164
  */
131
165
  async function executeReset(wuId) {
132
166
  console.log(`${LOG_PREFIX} Executing reset action for ${wuId}...`);
@@ -137,7 +171,7 @@ async function executeReset(wuId) {
137
171
  }
138
172
  const doc = readWU(wuPath, wuId);
139
173
  const worktreePath = getWorktreePath(wuId, doc.lane || '');
140
- // Remove worktree if exists
174
+ // Remove worktree if exists (git operation, safe to do directly)
141
175
  // WU-1097: Use worktreeRemove() instead of deprecated run() with shell strings
142
176
  // This properly handles paths with spaces and special characters
143
177
  if (existsSync(worktreePath)) {
@@ -146,7 +180,7 @@ async function executeReset(wuId) {
146
180
  await git.worktreeRemove(worktreePath, { force: true });
147
181
  console.log(`${LOG_PREFIX} Removed worktree: ${worktreePath}`);
148
182
  }
149
- catch (e) {
183
+ catch {
150
184
  // Try manual removal if git command fails
151
185
  try {
152
186
  rmSync(worktreePath, { recursive: true, force: true });
@@ -158,14 +192,38 @@ async function executeReset(wuId) {
158
192
  }
159
193
  }
160
194
  }
161
- // Reset WU status to ready
162
- doc.status = WU_STATUS.READY;
163
- delete doc.worktree_path;
164
- delete doc.claimed_at;
165
- delete doc.session_id;
166
- writeWU(wuPath, doc);
167
- console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Reset completed - ${wuId} is now ready for re-claiming`);
168
- return true;
195
+ // WU-1226: Use micro-worktree isolation for WU YAML state changes
196
+ try {
197
+ await withMicroWorktree({
198
+ operation: OPERATION_NAME,
199
+ id: wuId,
200
+ logPrefix: LOG_PREFIX,
201
+ pushOnly: true, // Don't modify local main
202
+ execute: async ({ worktreePath: microPath }) => {
203
+ // Read WU in micro-worktree context
204
+ const microWuPath = join(microPath, relative(process.cwd(), wuPath));
205
+ const microDoc = readWU(microWuPath, wuId);
206
+ // Reset WU status to ready and clear claim fields
207
+ microDoc.status = WU_STATUS.READY;
208
+ // Use Reflect.deleteProperty to satisfy sonarjs/no-delete rule
209
+ Reflect.deleteProperty(microDoc, 'worktree_path');
210
+ Reflect.deleteProperty(microDoc, 'claimed_at');
211
+ Reflect.deleteProperty(microDoc, 'session_id');
212
+ Reflect.deleteProperty(microDoc, 'baseline_main_sha');
213
+ writeWU(microWuPath, microDoc);
214
+ return {
215
+ commitMessage: `fix(wu-recover): reset ${wuId} - clear claim and set status to ready`,
216
+ files: [relative(process.cwd(), wuPath)],
217
+ };
218
+ },
219
+ });
220
+ console.log(`${LOG_PREFIX} ${EMOJI.SUCCESS} Reset completed - ${wuId} is now ready for re-claiming`);
221
+ return true;
222
+ }
223
+ catch (err) {
224
+ console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Micro-worktree operation failed: ${err.message}`);
225
+ return false;
226
+ }
169
227
  }
170
228
  /**
171
229
  * Execute nuke action - remove all artifacts completely
@@ -241,8 +299,15 @@ async function executeCleanup(wuId) {
241
299
  }
242
300
  /**
243
301
  * Execute recovery action
302
+ *
303
+ * WU-1226: All state-modifying actions (resume, reset) now use micro-worktree
304
+ * isolation. Changes are pushed via merge, not direct file modification on main.
305
+ *
306
+ * @param action - Recovery action type
307
+ * @param wuId - WU ID to recover
308
+ * @returns Promise<boolean> - true if action succeeded
244
309
  */
245
- async function executeAction(action, wuId) {
310
+ export async function executeRecoveryAction(action, wuId) {
246
311
  switch (action) {
247
312
  case RECOVERY_ACTIONS.RESUME:
248
313
  return executeResume(wuId);
@@ -314,16 +379,17 @@ async function main() {
314
379
  die(`Action '${action}' requires --force flag`);
315
380
  }
316
381
  // Execute action
317
- const success = await executeAction(action, id);
382
+ const success = await executeRecoveryAction(action, id);
318
383
  if (!success) {
319
384
  console.error(`${LOG_PREFIX} ${EMOJI.FAILURE} Recovery action failed`);
320
385
  process.exit(1);
321
386
  }
322
387
  process.exit(0);
323
388
  }
324
- // Guard main() for testability
325
- import { fileURLToPath } from 'node:url';
389
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
390
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
391
+ // path but import.meta.url resolves to the real path - they never match
326
392
  import { runCLI } from './cli-entry-point.js';
327
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
393
+ if (import.meta.main) {
328
394
  runCLI(main);
329
395
  }
package/dist/wu-repair.js CHANGED
@@ -41,9 +41,9 @@
41
41
  * - pnpm wu:repair-claim is deprecated. Use: pnpm wu:repair --claim
42
42
  * - pnpm wu:admin-repair is deprecated. Use: pnpm wu:repair --admin
43
43
  *
44
- * @see {@link tools/lib/wu-repair-core.mjs} - Core repair logic
45
- * @see {@link tools/lib/wu-consistency-checker.mjs} - Consistency detection/repair
46
- * @see {@link tools/lib/wu-state-store.mjs} - State file repair (repairStateFile)
44
+ * @see {@link packages/@lumenflow/cli/src/lib/wu-repair-core.ts} - Core repair logic
45
+ * @see {@link packages/@lumenflow/cli/src/lib/wu-consistency-checker.ts} - Consistency detection/repair
46
+ * @see {@link packages/@lumenflow/cli/src/lib/wu-state-store.ts} - State file repair (repairStateFile)
47
47
  */
48
48
  import { Command } from 'commander';
49
49
  import path from 'node:path';
@@ -217,10 +217,11 @@ async function main() {
217
217
  const result = await routeToRepairMode(options);
218
218
  process.exit(result.exitCode);
219
219
  }
220
- // Guard main() for testability (WU-1366)
221
- import { fileURLToPath } from 'node:url';
220
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
221
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
222
+ // path but import.meta.url resolves to the real path - they never match
222
223
  import { runCLI } from './cli-entry-point.js';
223
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
224
+ if (import.meta.main) {
224
225
  runCLI(main);
225
226
  }
226
227
  // Export for testing