@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-edit.js CHANGED
@@ -24,9 +24,8 @@
24
24
  * pnpm wu:edit --id WU-123 --acceptance "Criterion 1" --acceptance "Criterion 2"
25
25
  *
26
26
  * Part of WU-1274: Add wu:edit command for spec-only changes
27
- * @see {@link tools/lib/micro-worktree.mjs} - Shared micro-worktree logic
27
+ * @see {@link packages/@lumenflow/cli/src/lib/micro-worktree.ts} - Shared micro-worktree logic
28
28
  */
29
- import { fileURLToPath } from 'node:url';
30
29
  import { getGitForCwd, createGitForPath } from '@lumenflow/core/dist/git-adapter.js';
31
30
  import { die } from '@lumenflow/core/dist/error-handler.js';
32
31
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
@@ -182,19 +181,30 @@ const EDIT_OPTIONS = {
182
181
  codePaths: {
183
182
  name: 'codePaths',
184
183
  flags: '--code-paths <path>',
185
- description: 'Code path (repeatable, replaces existing; use --append to add)',
184
+ description: 'Code path (repeatable, appends to existing; use --replace-code-paths to overwrite)',
186
185
  isRepeatable: true,
187
186
  },
187
+ replaceCodePaths: {
188
+ name: 'replaceCodePaths',
189
+ flags: '--replace-code-paths',
190
+ description: 'Replace existing code_paths instead of appending',
191
+ },
188
192
  risks: {
189
193
  name: 'risks',
190
194
  flags: '--risks <risk>',
191
- description: 'Risk entry (repeatable, replaces existing; use --append to add)',
195
+ description: 'Risk entry (repeatable, appends to existing; use --replace-risks to overwrite)',
192
196
  isRepeatable: true,
193
197
  },
198
+ replaceRisks: {
199
+ name: 'replaceRisks',
200
+ flags: '--replace-risks',
201
+ description: 'Replace existing risks instead of appending',
202
+ },
203
+ // WU-1225: Deprecated --append flag (kept for backwards compatibility)
194
204
  append: {
195
205
  name: 'append',
196
206
  flags: '--append',
197
- description: 'Append to existing array values instead of replacing (for --code-paths, --test-paths-*, --blocked-by, --add-dep)',
207
+ description: '[DEPRECATED] Arrays now append by default. Use --replace-* flags to replace.',
198
208
  },
199
209
  // WU-1456: Add lane reassignment support
200
210
  lane: {
@@ -228,12 +238,22 @@ const EDIT_OPTIONS = {
228
238
  blockedBy: {
229
239
  name: 'blockedBy',
230
240
  flags: '--blocked-by <wuIds>',
231
- description: 'Comma-separated WU IDs that block this WU (replaces existing; use --append to add)',
241
+ description: 'Comma-separated WU IDs that block this WU (appends to existing; use --replace-blocked-by to overwrite)',
242
+ },
243
+ replaceBlockedBy: {
244
+ name: 'replaceBlockedBy',
245
+ flags: '--replace-blocked-by',
246
+ description: 'Replace existing blocked_by instead of appending',
232
247
  },
233
248
  addDep: {
234
249
  name: 'addDep',
235
250
  flags: '--add-dep <wuIds>',
236
- description: 'Comma-separated WU IDs to add to dependencies array (replaces existing; use --append to add)',
251
+ description: 'Comma-separated WU IDs to add to dependencies array (appends to existing; use --replace-dependencies to overwrite)',
252
+ },
253
+ replaceDependencies: {
254
+ name: 'replaceDependencies',
255
+ flags: '--replace-dependencies',
256
+ description: 'Replace existing dependencies instead of appending',
237
257
  },
238
258
  };
239
259
  /**
@@ -334,7 +354,9 @@ function parseArgs() {
334
354
  EDIT_OPTIONS.replaceNotes,
335
355
  EDIT_OPTIONS.replaceAcceptance,
336
356
  EDIT_OPTIONS.codePaths,
357
+ EDIT_OPTIONS.replaceCodePaths,
337
358
  EDIT_OPTIONS.risks,
359
+ EDIT_OPTIONS.replaceRisks,
338
360
  EDIT_OPTIONS.append,
339
361
  // WU-1390: Add test path flags
340
362
  WU_OPTIONS.testPathsManual,
@@ -350,7 +372,9 @@ function parseArgs() {
350
372
  EDIT_OPTIONS.phase,
351
373
  // WU-2564: Add blocked_by and dependencies
352
374
  EDIT_OPTIONS.blockedBy,
375
+ EDIT_OPTIONS.replaceBlockedBy,
353
376
  EDIT_OPTIONS.addDep,
377
+ EDIT_OPTIONS.replaceDependencies,
354
378
  // WU-1039: Add exposure for done WU metadata updates
355
379
  WU_OPTIONS.exposure,
356
380
  ],
@@ -702,7 +726,7 @@ export function applyEdits(wu, opts) {
702
726
  }
703
727
  updated.phase = phaseNum;
704
728
  }
705
- // Handle repeatable --code-paths flags (WU-1388: replace by default, append with --append)
729
+ // Handle repeatable --code-paths flags (WU-1225: append by default, replace with --replace-code-paths)
706
730
  // WU-1816: Split comma-separated string into array (same pattern as test paths)
707
731
  // WU-1870: Fix to split comma-separated values WITHIN array elements (Commander passes ['a,b'] not 'a,b')
708
732
  if (opts.codePaths && opts.codePaths.length > 0) {
@@ -716,9 +740,12 @@ export function applyEdits(wu, opts) {
716
740
  .split(',')
717
741
  .map((p) => p.trim())
718
742
  .filter(Boolean);
719
- updated.code_paths = mergeArrayField(wu.code_paths, codePaths, opts.append);
743
+ // WU-1225: Invert logic - append by default, replace with --replace-code-paths
744
+ // Also support legacy --append flag for backwards compatibility
745
+ const shouldAppend = !opts.replaceCodePaths || opts.append;
746
+ updated.code_paths = mergeArrayField(wu.code_paths, codePaths, shouldAppend);
720
747
  }
721
- // WU-1073: Handle repeatable --risks flags (replace by default, append with --append)
748
+ // WU-1225: Handle repeatable --risks flags (append by default, replace with --replace-risks)
722
749
  // Split comma-separated values within each entry for consistency with other list fields
723
750
  if (opts.risks && opts.risks.length > 0) {
724
751
  const rawRisks = opts.risks;
@@ -731,9 +758,12 @@ export function applyEdits(wu, opts) {
731
758
  .split(',')
732
759
  .map((risk) => risk.trim())
733
760
  .filter(Boolean);
734
- updated.risks = mergeArrayField(wu.risks, risks, opts.append);
761
+ // WU-1225: Invert logic - append by default
762
+ const shouldAppend = !opts.replaceRisks || opts.append;
763
+ updated.risks = mergeArrayField(wu.risks, risks, shouldAppend);
735
764
  }
736
765
  // WU-1390: Handle test path flags (DRY refactor)
766
+ // WU-1225: Test paths now append by default (consistent with --acceptance and --code-paths)
737
767
  const testPathMappings = [
738
768
  { optKey: 'testPathsManual', field: 'manual' },
739
769
  { optKey: 'testPathsUnit', field: 'unit' },
@@ -754,28 +784,32 @@ export function applyEdits(wu, opts) {
754
784
  .map((p) => p.trim())
755
785
  .filter(Boolean);
756
786
  updated.tests = updated.tests || {};
757
- updated.tests[field] = mergeArrayField(wu.tests?.[field], paths, opts.append);
787
+ // WU-1225: Append by default (no individual replace flags for test paths yet)
788
+ const shouldAppend = true;
789
+ updated.tests[field] = mergeArrayField(wu.tests?.[field], paths, shouldAppend);
758
790
  }
759
791
  }
760
792
  // WU-2564: Handle --blocked-by flag
761
- // Comma-separated WU IDs that block this WU
793
+ // WU-1225: Append by default, replace with --replace-blocked-by
762
794
  if (opts.blockedBy) {
763
795
  const rawBlockedBy = opts.blockedBy;
764
796
  const blockedByIds = rawBlockedBy
765
797
  .split(',')
766
798
  .map((id) => id.trim())
767
799
  .filter(Boolean);
768
- updated.blocked_by = mergeArrayField(wu.blocked_by, blockedByIds, opts.append);
800
+ const shouldAppend = !opts.replaceBlockedBy || opts.append;
801
+ updated.blocked_by = mergeArrayField(wu.blocked_by, blockedByIds, shouldAppend);
769
802
  }
770
803
  // WU-2564: Handle --add-dep flag
771
- // Comma-separated WU IDs to add to dependencies array
804
+ // WU-1225: Append by default, replace with --replace-dependencies
772
805
  if (opts.addDep) {
773
806
  const rawAddDep = opts.addDep;
774
807
  const depIds = rawAddDep
775
808
  .split(',')
776
809
  .map((id) => id.trim())
777
810
  .filter(Boolean);
778
- updated.dependencies = mergeArrayField(wu.dependencies, depIds, opts.append);
811
+ const shouldAppend = !opts.replaceDependencies || opts.append;
812
+ updated.dependencies = mergeArrayField(wu.dependencies, depIds, shouldAppend);
779
813
  }
780
814
  // WU-1039: Handle --exposure flag with validation
781
815
  if (opts.exposure) {
@@ -845,21 +879,20 @@ async function main() {
845
879
  ' --description <text> Update description field\n' +
846
880
  ' --acceptance <text> Append acceptance criteria (repeatable; use --replace-acceptance to overwrite)\n' +
847
881
  ' --notes <text> Append to notes (use --replace-notes to overwrite)\n' +
848
- ' --replace-notes Replace existing notes instead of appending\n' +
849
- ' --replace-acceptance Replace existing acceptance instead of appending\n' +
850
- ' --code-paths <paths> Replace code paths (repeatable; use --append to add)\n' +
851
- ' --risks <risk> Replace risks (repeatable; use --append to add)\n' +
882
+ ' --code-paths <paths> Append code paths (repeatable; use --replace-code-paths to overwrite)\n' +
883
+ ' --risks <risk> Append risks (repeatable; use --replace-risks to overwrite)\n' +
852
884
  ' --lane <lane> Update lane assignment (e.g., "Operations: Tooling")\n' +
853
885
  ' --type <type> Update WU type (feature, bug, refactor, documentation)\n' +
854
886
  ' --priority <priority> Update priority (P0, P1, P2, P3)\n' +
855
887
  ' --initiative <initId> Update initiative (bidirectional update)\n' +
856
888
  ' --phase <number> Update phase within initiative\n' +
857
- ' --test-paths-manual <t> Add manual test descriptions (repeatable; use --append to add)\n' +
858
- ' --test-paths-unit <path> Add unit test paths (repeatable; use --append to add)\n' +
859
- ' --test-paths-e2e <path> Add e2e test paths (repeatable; use --append to add)\n' +
860
- ' --blocked-by <wuIds> WU IDs that block this WU (comma-separated; use --append to add)\n' +
861
- ' --add-dep <wuIds> Add WU IDs to dependencies (comma-separated; use --append to add)\n' +
862
- ' --exposure <type> Update exposure level (ui, api, backend-only, documentation)');
889
+ ' --test-paths-manual <t> Append manual test descriptions (repeatable)\n' +
890
+ ' --test-paths-unit <path> Append unit test paths (repeatable)\n' +
891
+ ' --test-paths-e2e <path> Append e2e test paths (repeatable)\n' +
892
+ ' --blocked-by <wuIds> Append WU IDs that block this WU (use --replace-blocked-by to overwrite)\n' +
893
+ ' --add-dep <wuIds> Append WU IDs to dependencies (use --replace-dependencies to overwrite)\n' +
894
+ ' --exposure <type> Update exposure level (ui, api, backend-only, documentation)\n\n' +
895
+ 'Note: All array fields now append by default (WU-1225). Use --replace-* flags to overwrite.');
863
896
  }
864
897
  // Apply edits to get updated WU
865
898
  const updatedWU = applyEdits(originalWU, opts);
@@ -1001,8 +1034,10 @@ async function main() {
1001
1034
  displayReadinessSummary(id);
1002
1035
  }
1003
1036
  }
1004
- // Guard main() execution for testability (WU-1366)
1005
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
1037
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
1038
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
1039
+ // path but import.meta.url resolves to the real path - they never match
1040
+ if (import.meta.main) {
1006
1041
  main().catch((err) => {
1007
1042
  console.error(`${PREFIX} ❌ ${err.message}`);
1008
1043
  process.exit(EXIT_CODES.ERROR);
@@ -3,14 +3,14 @@
3
3
  * WU Lane Inference CLI (WU-908)
4
4
  *
5
5
  * Suggests sub-lane for a WU based on code paths and description.
6
- * Wrapper around lib/lane-inference.mjs for standalone CLI usage.
6
+ * Wrapper around lib/lane-inference.ts for standalone CLI usage.
7
7
  *
8
8
  * Usage:
9
9
  * # Infer from existing WU
10
- * node tools/wu-infer-lane.mjs --id WU-123
10
+ * node tools/wu-infer-lane.ts --id WU-123
11
11
  *
12
12
  * # Infer from manual inputs
13
- * node tools/wu-infer-lane.mjs --paths "tools/**" "docs/**" --desc "Tooling improvements"
13
+ * node tools/wu-infer-lane.ts --paths "tools/**" "docs/**" --desc "Tooling improvements"
14
14
  *
15
15
  * Returns suggested lane and confidence score (0-100).
16
16
  */
@@ -128,9 +128,10 @@ async function main() {
128
128
  die(`Lane inference failed: ${err.message}`);
129
129
  }
130
130
  }
131
- // Guard main() for testability (WU-1366)
132
- import { fileURLToPath } from 'node:url';
131
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
132
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
133
+ // path but import.meta.url resolves to the real path - they never match
133
134
  import { runCLI } from './cli-entry-point.js';
134
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
135
+ if (import.meta.main) {
135
136
  runCLI(main);
136
137
  }
@@ -5,6 +5,9 @@
5
5
  * WU-1803: Fast validation of code_paths and test paths before gates run.
6
6
  * Completes in under 5 seconds vs 2+ minutes for full gates.
7
7
  *
8
+ * WU-1180: Migrated from deprecated parseWUArgs to createWUParser for
9
+ * proper Commander --help output and consistency with other WU commands.
10
+ *
8
11
  * This catches YAML mismatches early, preventing wasted time running full
9
12
  * gates only to fail on code_paths validation at the end of wu:done.
10
13
  *
@@ -17,78 +20,14 @@
17
20
  * - test file paths exist (unit, e2e, integration)
18
21
  * - WU YAML schema is valid
19
22
  */
20
- import { fileURLToPath } from 'node:url';
21
23
  import { existsSync } from 'node:fs';
22
- import { parseWUArgs } from '@lumenflow/core/dist/arg-parser.js';
24
+ import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
23
25
  import { validatePreflight, formatPreflightResult, } from '@lumenflow/core/dist/wu-preflight-validators.js';
24
26
  import { PATTERNS, EXIT_CODES, LOG_PREFIX, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
25
27
  import { defaultWorktreeFrom, WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
26
28
  import { readWURaw } from '@lumenflow/core/dist/wu-yaml.js';
29
+ import { die } from '@lumenflow/core/dist/error-handler.js';
27
30
  /* eslint-disable security/detect-non-literal-fs-filename */
28
- /**
29
- * Parse command-line arguments
30
- * @param {string[]} argv - Process arguments
31
- * @returns {object} Parsed arguments
32
- */
33
- function parseArgs(argv) {
34
- const args = parseWUArgs(argv);
35
- // Handle help
36
- if (args.help) {
37
- return { help: true };
38
- }
39
- // Validate WU ID
40
- if (!args.id) {
41
- return { error: 'Missing required argument: --id WU-XXX' };
42
- }
43
- const id = args.id.toUpperCase();
44
- if (!PATTERNS.WU_ID.test(id)) {
45
- return { error: `Invalid WU ID format: ${args.id}. Expected WU-NNN` };
46
- }
47
- return {
48
- id,
49
- worktree: args.worktree || null,
50
- help: false,
51
- };
52
- }
53
- /**
54
- * Display help message
55
- */
56
- function showHelp() {
57
- console.log(`
58
- WU Preflight Validation - Fast code_paths and test paths check
59
-
60
- Usage:
61
- pnpm wu:preflight --id WU-XXX [OPTIONS]
62
-
63
- Options:
64
- --id <WU-ID> WU ID to validate (required)
65
- --worktree <path> Worktree path to validate files in (auto-detected if not provided)
66
- --help, -h Show this help
67
-
68
- Description:
69
- Validates code_paths and test file paths exist BEFORE running full gates.
70
- Completes in under 5 seconds vs 2+ minutes for gates.
71
-
72
- This prevents wasting time running full gates only to fail on
73
- code_paths validation at the end of wu:done.
74
-
75
- Checks performed:
76
- ${EMOJI.SUCCESS} code_paths files exist in worktree/main
77
- ${EMOJI.SUCCESS} Test file paths exist (unit, e2e, integration)
78
- ${EMOJI.SUCCESS} WU YAML schema is valid (required fields present)
79
- ${EMOJI.SUCCESS} Manual tests are skipped (descriptions, not files)
80
-
81
- Recommended workflow:
82
- 1. Implement feature/fix
83
- 2. Run: pnpm wu:preflight --id WU-XXX (fast check)
84
- 3. Run: pnpm gates (full validation)
85
- 4. Run: pnpm wu:done --id WU-XXX (complete WU)
86
-
87
- Example:
88
- pnpm wu:preflight --id WU-1803
89
- pnpm wu:preflight --id WU-1803 --worktree worktrees/operations-gates-wu-1803
90
- `);
91
- }
92
31
  /**
93
32
  * Detect worktree path from WU YAML or calculate from lane
94
33
  * @param {string} id - WU ID
@@ -116,23 +55,23 @@ function detectWorktreePath(id) {
116
55
  */
117
56
  async function main() {
118
57
  const PREFIX = LOG_PREFIX.PREFLIGHT;
119
- const args = parseArgs(process.argv);
120
- // Handle help
121
- if (args.help) {
122
- showHelp();
123
- process.exit(EXIT_CODES.SUCCESS);
124
- }
125
- // Handle parse errors
126
- if (args.error) {
127
- console.error(`${PREFIX} ${EMOJI.FAILURE} ${args.error}`);
128
- console.error(`${PREFIX} Run: pnpm wu:preflight --help for usage`);
129
- process.exit(EXIT_CODES.ERROR);
58
+ // WU-1180: Use createWUParser for proper Commander help output
59
+ const args = createWUParser({
60
+ name: 'wu-preflight',
61
+ description: 'Fast validation of code_paths and test paths before gates run. ' +
62
+ 'Completes in under 5 seconds vs 2+ minutes for full gates.',
63
+ options: [WU_OPTIONS.id, WU_OPTIONS.worktree],
64
+ required: ['id'],
65
+ allowPositionalId: true,
66
+ });
67
+ const id = args.id.toUpperCase();
68
+ if (!PATTERNS.WU_ID.test(id)) {
69
+ die(`Invalid WU ID format: ${args.id}. Expected WU-NNN`);
130
70
  }
131
- const { id, worktree } = args;
132
71
  console.log(`${PREFIX} Preflight Validation for ${id}`);
133
72
  console.log(`${PREFIX} ${'='.repeat(30)}\n`);
134
73
  // Determine worktree path
135
- let worktreePath = worktree;
74
+ let worktreePath = args.worktree || null;
136
75
  if (!worktreePath) {
137
76
  worktreePath = detectWorktreePath(id);
138
77
  if (worktreePath) {
@@ -160,9 +99,12 @@ async function main() {
160
99
  process.exit(EXIT_CODES.SUCCESS);
161
100
  }
162
101
  // Guard main() for testability
102
+ // WU-1071: Use import.meta.main instead of process.argv[1] comparison
103
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
104
+ // path but import.meta.url resolves to the real path - they never match
163
105
  import { runCLI } from './cli-entry-point.js';
164
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
106
+ if (import.meta.main) {
165
107
  runCLI(main);
166
108
  }
167
109
  // Export for testing
168
- export { parseArgs, detectWorktreePath };
110
+ export { detectWorktreePath };
@@ -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
  }