@lumenflow/cli 2.18.2 → 2.19.0

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 (104) hide show
  1. package/README.md +42 -41
  2. package/dist/delegation-list.js +140 -0
  3. package/dist/delegation-list.js.map +1 -0
  4. package/dist/doctor.js +35 -99
  5. package/dist/doctor.js.map +1 -1
  6. package/dist/gates-plan-resolvers.js +150 -0
  7. package/dist/gates-plan-resolvers.js.map +1 -0
  8. package/dist/gates-runners.js +533 -0
  9. package/dist/gates-runners.js.map +1 -0
  10. package/dist/gates-types.js +3 -0
  11. package/dist/gates-types.js.map +1 -1
  12. package/dist/gates-utils.js +316 -0
  13. package/dist/gates-utils.js.map +1 -0
  14. package/dist/gates.js +44 -1016
  15. package/dist/gates.js.map +1 -1
  16. package/dist/hooks/enforcement-generator.js +16 -880
  17. package/dist/hooks/enforcement-generator.js.map +1 -1
  18. package/dist/hooks/enforcement-sync.js +1 -4
  19. package/dist/hooks/enforcement-sync.js.map +1 -1
  20. package/dist/hooks/generators/auto-checkpoint.js +123 -0
  21. package/dist/hooks/generators/auto-checkpoint.js.map +1 -0
  22. package/dist/hooks/generators/enforce-worktree.js +188 -0
  23. package/dist/hooks/generators/enforce-worktree.js.map +1 -0
  24. package/dist/hooks/generators/index.js +16 -0
  25. package/dist/hooks/generators/index.js.map +1 -0
  26. package/dist/hooks/generators/pre-compact-checkpoint.js +134 -0
  27. package/dist/hooks/generators/pre-compact-checkpoint.js.map +1 -0
  28. package/dist/hooks/generators/require-wu.js +115 -0
  29. package/dist/hooks/generators/require-wu.js.map +1 -0
  30. package/dist/hooks/generators/session-start-recovery.js +101 -0
  31. package/dist/hooks/generators/session-start-recovery.js.map +1 -0
  32. package/dist/hooks/generators/signal-utils.js +52 -0
  33. package/dist/hooks/generators/signal-utils.js.map +1 -0
  34. package/dist/hooks/generators/warn-incomplete.js +65 -0
  35. package/dist/hooks/generators/warn-incomplete.js.map +1 -0
  36. package/dist/init-detection.js +228 -0
  37. package/dist/init-detection.js.map +1 -0
  38. package/dist/init-scaffolding.js +146 -0
  39. package/dist/init-scaffolding.js.map +1 -0
  40. package/dist/init-templates.js +1928 -0
  41. package/dist/init-templates.js.map +1 -0
  42. package/dist/init.js +136 -2425
  43. package/dist/init.js.map +1 -1
  44. package/dist/initiative-edit.js +42 -11
  45. package/dist/initiative-edit.js.map +1 -1
  46. package/dist/initiative-remove-wu.js +0 -0
  47. package/dist/initiative-status.js +29 -2
  48. package/dist/initiative-status.js.map +1 -1
  49. package/dist/mem-context.js +22 -9
  50. package/dist/mem-context.js.map +1 -1
  51. package/dist/orchestrate-init-status.js +32 -1
  52. package/dist/orchestrate-init-status.js.map +1 -1
  53. package/dist/orchestrate-monitor.js +38 -38
  54. package/dist/orchestrate-monitor.js.map +1 -1
  55. package/dist/public-manifest.js +12 -5
  56. package/dist/public-manifest.js.map +1 -1
  57. package/dist/shared-validators.js +1 -0
  58. package/dist/shared-validators.js.map +1 -1
  59. package/dist/spawn-list.js +0 -0
  60. package/dist/wu-claim-branch.js +121 -0
  61. package/dist/wu-claim-branch.js.map +1 -0
  62. package/dist/wu-claim-output.js +83 -0
  63. package/dist/wu-claim-output.js.map +1 -0
  64. package/dist/wu-claim-resume-handler.js +85 -0
  65. package/dist/wu-claim-resume-handler.js.map +1 -0
  66. package/dist/wu-claim-state.js +572 -0
  67. package/dist/wu-claim-state.js.map +1 -0
  68. package/dist/wu-claim-validation.js +439 -0
  69. package/dist/wu-claim-validation.js.map +1 -0
  70. package/dist/wu-claim-worktree.js +221 -0
  71. package/dist/wu-claim-worktree.js.map +1 -0
  72. package/dist/wu-claim.js +54 -1402
  73. package/dist/wu-claim.js.map +1 -1
  74. package/dist/wu-create-content.js +254 -0
  75. package/dist/wu-create-content.js.map +1 -0
  76. package/dist/wu-create-readiness.js +57 -0
  77. package/dist/wu-create-readiness.js.map +1 -0
  78. package/dist/wu-create-validation.js +149 -0
  79. package/dist/wu-create-validation.js.map +1 -0
  80. package/dist/wu-create.js +39 -441
  81. package/dist/wu-create.js.map +1 -1
  82. package/dist/wu-done.js +144 -249
  83. package/dist/wu-done.js.map +1 -1
  84. package/dist/wu-edit-operations.js +432 -0
  85. package/dist/wu-edit-operations.js.map +1 -0
  86. package/dist/wu-edit-validators.js +280 -0
  87. package/dist/wu-edit-validators.js.map +1 -0
  88. package/dist/wu-edit.js +27 -713
  89. package/dist/wu-edit.js.map +1 -1
  90. package/dist/wu-prep.js +32 -2
  91. package/dist/wu-prep.js.map +1 -1
  92. package/dist/wu-repair.js +1 -1
  93. package/dist/wu-repair.js.map +1 -1
  94. package/dist/wu-spawn-prompt-builders.js +1123 -0
  95. package/dist/wu-spawn-prompt-builders.js.map +1 -0
  96. package/dist/wu-spawn-strategy-resolver.js +314 -0
  97. package/dist/wu-spawn-strategy-resolver.js.map +1 -0
  98. package/dist/wu-spawn.js +9 -1398
  99. package/dist/wu-spawn.js.map +1 -1
  100. package/package.json +10 -7
  101. package/templates/core/LUMENFLOW.md.template +29 -99
  102. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +1 -1
  103. package/templates/core/ai/onboarding/quick-ref-commands.md.template +29 -4
  104. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +8 -8
package/dist/wu-edit.js CHANGED
@@ -24,130 +24,39 @@
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
+ * WU-1650: Decomposed into wu-edit-validators.ts and wu-edit-operations.ts
27
28
  * @see {@link packages/@lumenflow/cli/src/lib/micro-worktree.ts} - Shared micro-worktree logic
28
29
  */
29
- import { getGitForCwd, createGitForPath } from '@lumenflow/core/git-adapter';
30
+ import { getGitForCwd } from '@lumenflow/core/git-adapter';
30
31
  import { die } from '@lumenflow/core/error-handler';
31
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
32
+ import { writeFileSync } from 'node:fs';
32
33
  import { join, resolve } from 'node:path';
33
- // WU-1352: Use centralized YAML helper instead of raw js-yaml (Emergency fix Session 2)
34
- // WU-1620: Import readWU for readiness summary
35
- import { parseYAML, stringifyYAML, readWU } from '@lumenflow/core/wu-yaml';
34
+ import { stringifyYAML } from '@lumenflow/core/wu-yaml';
36
35
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/arg-parser';
37
- import { WU_PATHS, getStateStoreDirFromBacklog } from '@lumenflow/core/wu-paths';
38
- import { generateBacklog } from '@lumenflow/core/backlog-generator';
39
- import { WUStateStore } from '@lumenflow/core/wu-state-store';
40
- import { FILE_SYSTEM, MICRO_WORKTREE_OPERATIONS, LOG_PREFIX, COMMIT_FORMATS, WU_STATUS, getLaneBranch, PKG_MANAGER, SCRIPTS, PRETTIER_FLAGS, READINESS_UI,
41
- // WU-1039: Import exposure values for validation (Library-First, no magic strings)
42
- WU_EXPOSURE_VALUES, } from '@lumenflow/core/wu-constants';
36
+ import { WU_PATHS } from '@lumenflow/core/wu-paths';
37
+ import { FILE_SYSTEM, MICRO_WORKTREE_OPERATIONS, LOG_PREFIX, COMMIT_FORMATS, getLaneBranch, } from '@lumenflow/core/wu-constants';
43
38
  // WU-1593: Use centralized validateWUIDFormat (DRY)
44
39
  import { ensureOnMain, ensureMainUpToDate, validateWUIDFormat } from '@lumenflow/core/wu-helpers';
45
40
  import { withMicroWorktree } from '@lumenflow/core/micro-worktree';
46
- import { validateLaneFormat } from '@lumenflow/core/lane-checker';
47
- // WU-1620: Import validateSpecCompleteness for readiness summary
48
- // WU-1806: Import detectCurrentWorktree for worktree path resolution
49
- import { defaultWorktreeFrom, validateSpecCompleteness, detectCurrentWorktree, } from '@lumenflow/core/wu-done-validators';
50
41
  import { validateReadyWU } from '@lumenflow/core/wu-schema';
51
- import { execSync } from 'node:child_process';
52
- // WU-1442: Import date normalization to fix date corruption from js-yaml
53
- import { normalizeToDateString } from '@lumenflow/core/date-utils';
54
- // WU-1929: Import initiative-related modules for bidirectional initiative updates
55
- import { INIT_PATTERNS } from '@lumenflow/initiatives/constants';
56
- import { INIT_PATHS } from '@lumenflow/initiatives/paths';
57
- import { readInitiative, writeInitiative } from '@lumenflow/initiatives/yaml';
42
+ // WU-1806: Import detectCurrentWorktree for worktree path resolution
43
+ import { defaultWorktreeFrom, detectCurrentWorktree } from '@lumenflow/core/wu-done-validators';
58
44
  // WU-2004: Import schema normalization for legacy WU formats
59
45
  import { normalizeWUSchema } from '@lumenflow/core/wu-schema-normalization';
60
46
  // WU-2253: Import WU spec linter for acceptance/code_paths validation
61
47
  import { lintWUSpec, formatLintErrors } from '@lumenflow/core/wu-lint';
62
48
  // WU-1329: Import path existence validators for strict validation
63
49
  import { validateCodePathsExistence, validateTestPathsExistence, } from '@lumenflow/core/wu-preflight-validators';
64
- import { BRANCH_PR_EDIT_MODE, BLOCKED_EDIT_MODE, resolveInProgressEditMode, } from './wu-state-cloud.js';
50
+ import { validateLaneFormat } from '@lumenflow/core/lane-checker';
65
51
  import { runCLI } from './cli-entry-point.js';
66
- import { checkCodePathCoverageBeforeGates, formatCodePathCoverageFailure } from './wu-prep.js';
52
+ // WU-1650: Import from decomposed modules
53
+ import { validateDoneWUEdits, validateWUEditable, validateWorktreeExists, validateWorktreeClean, validateWorktreeBranch, normalizeReplaceCodePathsArgv, EDIT_MODE, } from './wu-edit-validators.js';
54
+ import { applyEdits, applyEditsInWorktree, getWuEditCommitFiles, regenerateBacklogFromState, normalizeWUDates, updateInitiativeWusArrays, enforceInProgressCodePathCoverage, displayReadinessSummary, } from './wu-edit-operations.js';
55
+ // WU-1650: Re-export for backwards compatibility
56
+ // All test files and external consumers import from wu-edit.ts
57
+ export { validateDoneWUEdits, validateExposureValue, normalizeReplaceCodePathsArgv, hasScopeRelevantBranchChanges, } from './wu-edit-validators.js';
58
+ export { applyExposureEdit, applyEdits, mergeStringField, getWuEditCommitFiles, } from './wu-edit-operations.js';
67
59
  const PREFIX = LOG_PREFIX.EDIT;
68
- /**
69
- * WU-1039: Validate which edits are allowed on done WUs
70
- *
71
- * Done WUs only allow metadata reassignment: initiative, phase, and exposure.
72
- * All other edits are blocked to preserve WU immutability after completion.
73
- *
74
- * @param opts - Parsed CLI options
75
- * @returns { valid: boolean, disallowedEdits: string[] }
76
- */
77
- export function validateDoneWUEdits(opts) {
78
- const disallowedEdits = [];
79
- // Check for disallowed edits on done WUs
80
- if (opts.specFile)
81
- disallowedEdits.push('--spec-file');
82
- if (opts.description)
83
- disallowedEdits.push('--description');
84
- if (opts.acceptance && Array.isArray(opts.acceptance) && opts.acceptance.length > 0) {
85
- disallowedEdits.push('--acceptance');
86
- }
87
- if (opts.notes)
88
- disallowedEdits.push('--notes');
89
- if (opts.codePaths && Array.isArray(opts.codePaths) && opts.codePaths.length > 0) {
90
- disallowedEdits.push('--code-paths');
91
- }
92
- if (opts.risks && Array.isArray(opts.risks) && opts.risks.length > 0) {
93
- disallowedEdits.push('--risks');
94
- }
95
- if (opts.lane)
96
- disallowedEdits.push('--lane');
97
- if (opts.type)
98
- disallowedEdits.push('--type');
99
- if (opts.priority)
100
- disallowedEdits.push('--priority');
101
- if (opts.testPathsManual &&
102
- Array.isArray(opts.testPathsManual) &&
103
- opts.testPathsManual.length > 0) {
104
- disallowedEdits.push('--test-paths-manual');
105
- }
106
- if (opts.testPathsUnit && Array.isArray(opts.testPathsUnit) && opts.testPathsUnit.length > 0) {
107
- disallowedEdits.push('--test-paths-unit');
108
- }
109
- if (opts.testPathsE2e && Array.isArray(opts.testPathsE2e) && opts.testPathsE2e.length > 0) {
110
- disallowedEdits.push('--test-paths-e2e');
111
- }
112
- return {
113
- valid: disallowedEdits.length === 0,
114
- disallowedEdits,
115
- };
116
- }
117
- /**
118
- * WU-1039: Validate exposure value against schema
119
- *
120
- * Uses WU_EXPOSURE_VALUES from core constants (Library-First, no magic strings).
121
- *
122
- * @param exposure - Exposure value to validate
123
- * @returns { valid: boolean, error?: string }
124
- */
125
- export function validateExposureValue(exposure) {
126
- // WU_EXPOSURE_VALUES is readonly array, need to cast for includes check
127
- const validValues = WU_EXPOSURE_VALUES;
128
- if (!validValues.includes(exposure)) {
129
- return {
130
- valid: false,
131
- error: `Invalid exposure value: "${exposure}"\n\nValid values: ${WU_EXPOSURE_VALUES.join(', ')}`,
132
- };
133
- }
134
- return { valid: true };
135
- }
136
- /**
137
- * WU-1039: Apply exposure edit to WU object
138
- *
139
- * Returns a new WU object with updated exposure (immutable pattern).
140
- *
141
- * @param wu - Original WU object
142
- * @param exposure - New exposure value
143
- * @returns Updated WU object (does not mutate original)
144
- */
145
- export function applyExposureEdit(wu, exposure) {
146
- return {
147
- ...wu,
148
- exposure,
149
- };
150
- }
151
60
  /**
152
61
  * Custom options for wu-edit (not in shared WU_OPTIONS)
153
62
  */
@@ -262,121 +171,6 @@ const EDIT_OPTIONS = {
262
171
  description: 'Replace existing dependencies instead of appending',
263
172
  },
264
173
  };
265
- /**
266
- * WU-1929: Update initiative wus: arrays bidirectionally
267
- *
268
- * When a WU's initiative field changes, this function:
269
- * 1. Removes the WU ID from the old initiative's wus: array (if exists)
270
- * 2. Adds the WU ID to the new initiative's wus: array
271
- *
272
- * @param {string} worktreePath - Path to the worktree (for file operations)
273
- * @param {string} wuId - WU ID being updated
274
- * @param {string|undefined} oldInitId - Previous initiative ID (may be undefined)
275
- * @param {string} newInitId - New initiative ID
276
- * @returns {Array<string>} Array of relative file paths that were modified
277
- */
278
- function updateInitiativeWusArrays(worktreePath, wuId, oldInitId, newInitId) {
279
- const modifiedFiles = [];
280
- // Remove from old initiative if it exists and is different from new
281
- if (oldInitId && oldInitId !== newInitId) {
282
- const oldInitPath = join(worktreePath, INIT_PATHS.INITIATIVE(oldInitId));
283
- if (existsSync(oldInitPath)) {
284
- try {
285
- const oldInit = readInitiative(oldInitPath, oldInitId);
286
- if (Array.isArray(oldInit.wus) && oldInit.wus.includes(wuId)) {
287
- oldInit.wus = oldInit.wus.filter((id) => id !== wuId);
288
- writeInitiative(oldInitPath, oldInit);
289
- modifiedFiles.push(INIT_PATHS.INITIATIVE(oldInitId));
290
- console.log(`${PREFIX} ✅ Removed ${wuId} from ${oldInitId} wus: array`);
291
- }
292
- }
293
- catch (err) {
294
- // Old initiative may not exist or be invalid - log warning but continue
295
- console.warn(`${PREFIX} ⚠️ Could not update old initiative ${oldInitId}: ${err.message}`);
296
- }
297
- }
298
- }
299
- // Add to new initiative
300
- const newInitPath = join(worktreePath, INIT_PATHS.INITIATIVE(newInitId));
301
- if (existsSync(newInitPath)) {
302
- try {
303
- const newInit = readInitiative(newInitPath, newInitId);
304
- if (!Array.isArray(newInit.wus)) {
305
- newInit.wus = [];
306
- }
307
- if (!newInit.wus.includes(wuId)) {
308
- newInit.wus.push(wuId);
309
- writeInitiative(newInitPath, newInit);
310
- modifiedFiles.push(INIT_PATHS.INITIATIVE(newInitId));
311
- console.log(`${PREFIX} ✅ Added ${wuId} to ${newInitId} wus: array`);
312
- }
313
- }
314
- catch (err) {
315
- die(`Failed to update new initiative ${newInitId}: ${err.message}`);
316
- }
317
- }
318
- return modifiedFiles;
319
- }
320
- /**
321
- * WU-1929: Validate initiative ID format
322
- * @param {string} initId - Initiative ID to validate
323
- */
324
- function validateInitiativeFormat(initId) {
325
- if (!INIT_PATTERNS.INIT_ID.test(initId)) {
326
- die(`Invalid Initiative ID format: "${initId}"\n\n` +
327
- `Expected format: INIT-<number> or INIT-<NAME> (e.g., INIT-001, INIT-TOOLING)`);
328
- }
329
- }
330
- /**
331
- * WU-1929: Validate initiative exists on disk
332
- * @param {string} initId - Initiative ID to check
333
- * @returns {string} Path to initiative file
334
- */
335
- function validateInitiativeExists(initId) {
336
- const initPath = INIT_PATHS.INITIATIVE(initId);
337
- if (!existsSync(initPath)) {
338
- die(`Initiative not found: ${initId}\n\nFile does not exist: ${initPath}`);
339
- }
340
- return initPath;
341
- }
342
- const NON_SCOPE_RELEVANT_PATHS = new Set([
343
- '.lumenflow/state/wu-events.jsonl',
344
- 'docs/04-operations/tasks/backlog.md',
345
- 'docs/04-operations/tasks/status.md',
346
- ]);
347
- /**
348
- * WU-1618: Treat backlog/state bookkeeping files as non-scope signals.
349
- */
350
- export function hasScopeRelevantBranchChanges(changedFiles) {
351
- return changedFiles.some((filePath) => {
352
- const normalized = filePath.trim().replace(/\\/g, '/');
353
- if (!normalized) {
354
- return false;
355
- }
356
- if (NON_SCOPE_RELEVANT_PATHS.has(normalized)) {
357
- return false;
358
- }
359
- return !normalized.startsWith('docs/04-operations/tasks/wu/');
360
- });
361
- }
362
- /**
363
- * WU-1618: Support `--replace-code-paths <paths>` shorthand by normalizing to
364
- * `--replace-code-paths --code-paths <paths>` before Commander parsing.
365
- */
366
- export function normalizeReplaceCodePathsArgv(argv) {
367
- const normalized = [...argv];
368
- for (let i = 0; i < normalized.length; i += 1) {
369
- if (normalized[i] !== '--replace-code-paths') {
370
- continue;
371
- }
372
- const next = normalized[i + 1];
373
- if (next && !next.startsWith('-')) {
374
- normalized.splice(i + 1, 0, '--code-paths');
375
- i += 1;
376
- }
377
- }
378
- return normalized;
379
- }
380
174
  /**
381
175
  * Parse command line arguments
382
176
  */
@@ -432,260 +226,6 @@ function parseArgs() {
432
226
  process.argv = originalArgv;
433
227
  }
434
228
  }
435
- function enforceInProgressCodePathCoverage(options) {
436
- const { id, editOpts, codePaths = [], cwd } = options;
437
- const hasCodePathEdit = Array.isArray(editOpts.codePaths)
438
- ? editOpts.codePaths.length > 0
439
- : Boolean(editOpts.codePaths);
440
- if (!hasCodePathEdit) {
441
- return;
442
- }
443
- if (!Array.isArray(codePaths) || codePaths.length === 0) {
444
- return;
445
- }
446
- const coverage = checkCodePathCoverageBeforeGates({
447
- wuId: id,
448
- codePaths,
449
- cwd,
450
- });
451
- if (coverage.valid) {
452
- return;
453
- }
454
- if (!hasScopeRelevantBranchChanges(coverage.changedFiles)) {
455
- console.warn(`${PREFIX} ⚠️ code_paths coverage check deferred (no scope-relevant branch changes yet).`);
456
- return;
457
- }
458
- die(`${formatCodePathCoverageFailure({
459
- wuId: id,
460
- missingCodePaths: coverage.missingCodePaths,
461
- changedFiles: coverage.changedFiles,
462
- error: coverage.error,
463
- })}\n\n` +
464
- `${PREFIX} Tip: if you're still defining scope before code changes, run wu:edit again after your first scope-relevant commit.`);
465
- }
466
- /**
467
- * WU-1620: Display readiness summary after edit
468
- *
469
- * Shows whether WU is ready for wu:claim based on spec completeness.
470
- * Non-blocking - just informational to help agents understand what's missing.
471
- *
472
- * @param {string} id - WU ID
473
- */
474
- function displayReadinessSummary(id) {
475
- try {
476
- const wuPath = WU_PATHS.WU(id);
477
- const wuDoc = readWU(wuPath, id);
478
- const { valid, errors } = validateSpecCompleteness(wuDoc, id);
479
- const { BOX, BOX_WIDTH, MESSAGES, ERROR_MAX_LENGTH, ERROR_TRUNCATE_LENGTH, TRUNCATION_SUFFIX, PADDING, } = READINESS_UI;
480
- console.log(`\n${BOX.TOP_LEFT}${BOX.HORIZONTAL.repeat(BOX_WIDTH)}${BOX.TOP_RIGHT}`);
481
- if (valid) {
482
- console.log(`${BOX.VERTICAL} ${MESSAGES.READY_YES}${''.padEnd(PADDING.READY_YES)}${BOX.VERTICAL}`);
483
- console.log(`${BOX.VERTICAL}${''.padEnd(BOX_WIDTH)}${BOX.VERTICAL}`);
484
- const claimCmd = `Run: pnpm wu:claim --id ${id}`;
485
- console.log(`${BOX.VERTICAL} ${claimCmd}${''.padEnd(BOX_WIDTH - claimCmd.length - 1)}${BOX.VERTICAL}`);
486
- }
487
- else {
488
- console.log(`${BOX.VERTICAL} ${MESSAGES.READY_NO}${''.padEnd(PADDING.READY_NO)}${BOX.VERTICAL}`);
489
- console.log(`${BOX.VERTICAL}${''.padEnd(BOX_WIDTH)}${BOX.VERTICAL}`);
490
- console.log(`${BOX.VERTICAL} ${MESSAGES.MISSING_HEADER}${''.padEnd(PADDING.MISSING_HEADER)}${BOX.VERTICAL}`);
491
- for (const error of errors) {
492
- // Truncate long error messages to fit box
493
- const truncated = error.length > ERROR_MAX_LENGTH
494
- ? `${error.substring(0, ERROR_TRUNCATE_LENGTH)}${TRUNCATION_SUFFIX}`
495
- : error;
496
- console.log(`${BOX.VERTICAL} ${MESSAGES.BULLET} ${truncated}${''.padEnd(Math.max(0, PADDING.ERROR_BULLET - truncated.length))}${BOX.VERTICAL}`);
497
- }
498
- console.log(`${BOX.VERTICAL}${''.padEnd(BOX_WIDTH)}${BOX.VERTICAL}`);
499
- const editCmd = `Run: pnpm wu:edit --id ${id} --help`;
500
- console.log(`${BOX.VERTICAL} ${editCmd}${''.padEnd(BOX_WIDTH - editCmd.length - 1)}${BOX.VERTICAL}`);
501
- }
502
- console.log(`${BOX.BOTTOM_LEFT}${BOX.HORIZONTAL.repeat(BOX_WIDTH)}${BOX.BOTTOM_RIGHT}`);
503
- }
504
- catch (err) {
505
- // Non-blocking - if validation fails, just warn
506
- console.warn(`${PREFIX} ⚠️ Could not validate readiness: ${err.message}`);
507
- }
508
- }
509
- /**
510
- * Edit modes for WU editing
511
- * WU-1365: Worktree-aware editing support
512
- */
513
- const EDIT_MODE = {
514
- /** Ready WUs: Use micro-worktree on main (existing behavior) */
515
- MICRO_WORKTREE: 'micro_worktree',
516
- /** In-progress worktree WUs: Apply edits directly in active worktree (WU-1365) */
517
- WORKTREE: 'worktree',
518
- /** In-progress branch-pr WUs: apply edits directly on the claimed branch */
519
- BRANCH_PR: BRANCH_PR_EDIT_MODE,
520
- };
521
- /**
522
- * Normalize date fields in WU object to prevent date corruption
523
- *
524
- * WU-1442: js-yaml parses unquoted YYYY-MM-DD dates as Date objects.
525
- * When yaml.dump() serializes them back, it outputs ISO timestamps.
526
- * This function normalizes Date objects back to YYYY-MM-DD strings.
527
- *
528
- * @param {object} wu - WU object from yaml.load()
529
- * @returns {object} WU object with normalized date fields
530
- */
531
- function normalizeWUDates(wu) {
532
- if (wu.created !== undefined) {
533
- wu.created = normalizeToDateString(wu.created);
534
- }
535
- return wu;
536
- }
537
- /**
538
- * Check WU exists and determine edit mode
539
- * WU-1365: Now supports worktree-aware editing for in_progress WUs
540
- *
541
- * @param {string} id - WU ID
542
- * @returns {{ wu: object, editMode: string }} WU object and edit mode
543
- */
544
- function validateWUEditable(id) {
545
- const wuPath = WU_PATHS.WU(id);
546
- if (!existsSync(wuPath)) {
547
- die(`WU ${id} not found at ${wuPath}\n\nEnsure the WU exists and you're in the repo root.`);
548
- }
549
- const content = readFileSync(wuPath, { encoding: FILE_SYSTEM.ENCODING });
550
- const wu = parseYAML(content);
551
- // WU-1929: Done WUs allow initiative/phase edits only (metadata reassignment)
552
- // WU-1365: Other fields on done WUs are immutable
553
- if (wu.status === WU_STATUS.DONE) {
554
- // Return done status - main() will validate allowed fields
555
- return { wu, editMode: EDIT_MODE.MICRO_WORKTREE, isDone: true };
556
- }
557
- // Handle in_progress WUs based on claimed_mode (WU-1365)
558
- if (wu.status === WU_STATUS.IN_PROGRESS) {
559
- const editMode = resolveInProgressEditMode(typeof wu.claimed_mode === 'string' ? wu.claimed_mode : undefined);
560
- if (editMode === BLOCKED_EDIT_MODE) {
561
- die(`Cannot edit branch-only WU ${id} via wu:edit.\n\n` +
562
- `WUs claimed with claimed_mode='branch-only' cannot be edited via wu:edit.\n` +
563
- `To modify the spec, edit the file directly on the lane branch and commit.`);
564
- }
565
- if (editMode === EDIT_MODE.BRANCH_PR) {
566
- return { wu, editMode: EDIT_MODE.BRANCH_PR, isDone: false };
567
- }
568
- return { wu, editMode: EDIT_MODE.WORKTREE, isDone: false };
569
- }
570
- // Ready WUs use micro-worktree (existing behavior)
571
- if (wu.status === WU_STATUS.READY) {
572
- return { wu, editMode: EDIT_MODE.MICRO_WORKTREE, isDone: false };
573
- }
574
- // Block other statuses (blocked, etc.)
575
- die(`Cannot edit WU ${id}: status is '${wu.status}'.\n\n` +
576
- `Only WUs in '${WU_STATUS.READY}' or '${WU_STATUS.IN_PROGRESS}' (worktree mode) can be edited.`);
577
- }
578
- /**
579
- * Validate worktree exists on disk
580
- * WU-1365: Required check before worktree editing
581
- *
582
- * @param {string} worktreePath - Absolute path to worktree
583
- * @param {string} id - WU ID (for error messages)
584
- */
585
- function validateWorktreeExists(worktreePath, id) {
586
- if (!existsSync(worktreePath)) {
587
- die(`Cannot edit WU ${id}: worktree path missing from disk.\n\n` +
588
- `Expected worktree at: ${worktreePath}\n\n` +
589
- `The worktree may have been removed or the path is incorrect.\n` +
590
- `If the worktree was accidentally deleted, you may need to re-claim the WU.`);
591
- }
592
- }
593
- /**
594
- * Validate worktree has no uncommitted changes
595
- * WU-1365: Required check to prevent edit conflicts
596
- *
597
- * @param {string} worktreePath - Absolute path to worktree
598
- * @param {string} id - WU ID (for error messages)
599
- */
600
- async function validateWorktreeClean(worktreePath, id) {
601
- try {
602
- const gitAdapter = createGitForPath(worktreePath);
603
- const status = (await gitAdapter.raw(['status', '--porcelain'])).trim();
604
- if (status !== '') {
605
- die(`Cannot edit WU ${id}: worktree has uncommitted changes.\n\n` +
606
- `Uncommitted changes in ${worktreePath}:\n${status}\n\n` +
607
- `Commit or discard your changes before editing the WU spec:\n` +
608
- ` cd ${worktreePath}\n` +
609
- ` git add . && git commit -m "wip: save progress"\n\n` +
610
- `Then retry wu:edit.`);
611
- }
612
- }
613
- catch (err) {
614
- die(`Cannot edit WU ${id}: failed to check worktree status.\n\n` +
615
- `Error: ${err.message}\n\n` +
616
- `Worktree path: ${worktreePath}`);
617
- }
618
- }
619
- /**
620
- * Validate worktree is on expected lane branch
621
- * WU-1365: Prevents editing WUs in worktrees with mismatched branches
622
- *
623
- * @param {string} worktreePath - Absolute path to worktree
624
- * @param {string} expectedBranch - Expected branch name (e.g., lane/operations-tooling/wu-1365)
625
- * @param {string} id - WU ID (for error messages)
626
- */
627
- async function validateWorktreeBranch(worktreePath, expectedBranch, id) {
628
- try {
629
- const gitAdapter = createGitForPath(worktreePath);
630
- const actualBranch = (await gitAdapter.raw(['rev-parse', '--abbrev-ref', 'HEAD'])).trim();
631
- if (actualBranch !== expectedBranch) {
632
- die(`Cannot edit WU ${id}: worktree branch does not match expected lane branch.\n\n` +
633
- `Expected branch: ${expectedBranch}\n` +
634
- `Actual branch: ${actualBranch}\n\n` +
635
- `This may indicate a corrupted worktree state.\n` +
636
- `Verify the worktree is correctly set up for this WU.`);
637
- }
638
- }
639
- catch (err) {
640
- die(`Cannot edit WU ${id}: failed to check worktree branch.\n\n` +
641
- `Error: ${err.message}\n\n` +
642
- `Worktree path: ${worktreePath}`);
643
- }
644
- }
645
- /**
646
- * Apply edits directly in an active worktree (WU-1365)
647
- * Used for in_progress WUs with claimed_mode=worktree
648
- *
649
- * @param {object} params - Parameters
650
- * @param {string} params.worktreePath - Absolute path to worktree
651
- * @param {string} params.id - WU ID
652
- * @param {object} params.updatedWU - Updated WU object
653
- */
654
- async function applyEditsInWorktree({ worktreePath, id, updatedWU }) {
655
- const wuPath = join(worktreePath, WU_PATHS.WU(id));
656
- // WU-1442: Normalize dates before dumping to prevent ISO timestamp corruption
657
- normalizeWUDates(updatedWU);
658
- // Emergency fix Session 2: Use centralized stringifyYAML helper
659
- const yamlContent = stringifyYAML(updatedWU);
660
- writeFileSync(wuPath, yamlContent, { encoding: FILE_SYSTEM.ENCODING });
661
- console.log(`${PREFIX} ✅ Updated ${id}.yaml in worktree`);
662
- // Format the file
663
- try {
664
- execSync(`${PKG_MANAGER} ${SCRIPTS.PRETTIER} ${PRETTIER_FLAGS.WRITE} "${wuPath}"`, {
665
- cwd: worktreePath,
666
- encoding: FILE_SYSTEM.ENCODING,
667
- stdio: 'pipe',
668
- });
669
- console.log(`${PREFIX} ✅ Formatted ${id}.yaml`);
670
- }
671
- catch (err) {
672
- console.warn(`${PREFIX} ⚠️ Could not format file: ${err.message}`);
673
- }
674
- // Stage and commit using git adapter (library-first)
675
- const commitMsg = COMMIT_FORMATS.SPEC_UPDATE(id);
676
- try {
677
- const gitAdapter = createGitForPath(worktreePath);
678
- await gitAdapter.add(wuPath);
679
- await gitAdapter.commit(commitMsg);
680
- console.log(`${PREFIX} ✅ Committed: ${commitMsg}`);
681
- }
682
- catch (err) {
683
- die(`Failed to commit edit in worktree.\n\n` +
684
- `Error: ${err.message}\n\n` +
685
- `The WU file was updated but could not be committed.\n` +
686
- `You may need to commit manually in the worktree.`);
687
- }
688
- }
689
229
  /**
690
230
  * Ensure working tree is clean
691
231
  */
@@ -695,232 +235,6 @@ async function ensureCleanWorkingTree() {
695
235
  die(`Working tree is not clean. Cannot edit WU.\n\nUncommitted changes:\n${status}\n\nCommit or stash changes before editing:\n git add . && git commit -m "..."\n`);
696
236
  }
697
237
  }
698
- /**
699
- * Merge array values: replace by default, append if --append flag is set (WU-1388)
700
- * @param {Array} existing - Current array value from WU
701
- * @param {Array} newValues - New values from CLI
702
- * @param {boolean} shouldAppend - Whether to append instead of replace
703
- * @returns {Array} Merged array
704
- */
705
- function mergeArrayField(existing, newValues, shouldAppend) {
706
- if (!shouldAppend) {
707
- return newValues;
708
- }
709
- const existingArray = Array.isArray(existing) ? existing : [];
710
- return [...existingArray, ...newValues];
711
- }
712
- /**
713
- * WU-1144: Merge string field values with append-by-default behavior
714
- *
715
- * Notes and acceptance criteria should append by default (preserving original),
716
- * with explicit --replace-notes and --replace-acceptance flags for overwrite.
717
- *
718
- * @param {string | undefined} existing - Current string value from WU
719
- * @param {string} newValue - New value from CLI
720
- * @param {boolean} shouldReplace - Whether to replace instead of append
721
- * @returns {string} Merged string value
722
- */
723
- export function mergeStringField(existing, newValue, shouldReplace) {
724
- // If replace mode or no existing value, just use new value
725
- if (shouldReplace || !existing || existing.trim() === '') {
726
- return newValue;
727
- }
728
- // Append with double newline separator
729
- return `${existing}\n\n${newValue}`;
730
- }
731
- /**
732
- * WU-1594: Ensure wu:edit commits always include regenerated backlog projection.
733
- *
734
- * @param {string} id - WU ID
735
- * @param {string[]} extraFiles - Additional files modified during edit
736
- * @returns {string[]} Deduplicated list of files for commit
737
- */
738
- export function getWuEditCommitFiles(id, extraFiles = []) {
739
- return [...new Set([WU_PATHS.WU(id), ...extraFiles, WU_PATHS.BACKLOG()])];
740
- }
741
- /**
742
- * WU-1594: Regenerate backlog.md from state store after wu:edit updates.
743
- *
744
- * @param {string} backlogPath - Absolute path to backlog.md in micro-worktree
745
- */
746
- async function regenerateBacklogFromState(backlogPath) {
747
- const stateDir = getStateStoreDirFromBacklog(backlogPath);
748
- const store = new WUStateStore(stateDir);
749
- await store.load();
750
- const content = await generateBacklog(store);
751
- writeFileSync(backlogPath, content, { encoding: FILE_SYSTEM.ENCODING });
752
- }
753
- /**
754
- * Load spec file and merge with original WU (preserving id and status)
755
- * @param {string} specPath - Path to spec file
756
- * @param {object} originalWU - Original WU object
757
- * @returns {object} Merged WU object
758
- */
759
- function loadSpecFile(specPath, originalWU) {
760
- const resolvedPath = resolve(specPath);
761
- if (!existsSync(resolvedPath)) {
762
- die(`Spec file not found: ${resolvedPath}`);
763
- }
764
- const specContent = readFileSync(resolvedPath, {
765
- encoding: FILE_SYSTEM.ENCODING,
766
- });
767
- const newSpec = parseYAML(specContent);
768
- // Preserve id and status from original (cannot be changed via edit)
769
- return {
770
- ...newSpec,
771
- id: originalWU.id,
772
- status: originalWU.status,
773
- };
774
- }
775
- /**
776
- * Apply edits to WU YAML
777
- * Returns the updated WU object
778
- */
779
- // eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
780
- export function applyEdits(wu, opts) {
781
- // Full spec replacement from file
782
- if (opts.specFile) {
783
- return loadSpecFile(opts.specFile, wu);
784
- }
785
- const updated = { ...wu };
786
- // Field-level updates
787
- if (opts.description) {
788
- updated.description = opts.description;
789
- }
790
- // WU-1144: Handle --acceptance with append-by-default behavior
791
- // Appends to existing acceptance criteria unless --replace-acceptance is set
792
- if (opts.acceptance && opts.acceptance.length > 0) {
793
- // Invert the logic: append by default, replace with --replace-acceptance
794
- const shouldAppend = !opts.replaceAcceptance;
795
- updated.acceptance = mergeArrayField(wu.acceptance, opts.acceptance, shouldAppend);
796
- }
797
- // WU-1144: Handle --notes with append-by-default behavior
798
- // Appends to existing notes unless --replace-notes is set
799
- if (opts.notes) {
800
- updated.notes = mergeStringField(wu.notes, opts.notes, opts.replaceNotes ?? false);
801
- }
802
- // WU-1456: Handle lane reassignment
803
- if (opts.lane) {
804
- validateLaneFormat(opts.lane);
805
- updated.lane = opts.lane;
806
- }
807
- // WU-1620: Handle type and priority updates
808
- if (opts.type) {
809
- updated.type = opts.type;
810
- }
811
- if (opts.priority) {
812
- updated.priority = opts.priority;
813
- }
814
- // WU-1929: Handle initiative and phase updates
815
- // Note: Initiative bidirectional updates (initiative wus: arrays) are handled separately
816
- // in the main function after applyEdits, since they require file I/O
817
- if (opts.initiative) {
818
- validateInitiativeFormat(opts.initiative);
819
- validateInitiativeExists(opts.initiative);
820
- updated.initiative = opts.initiative;
821
- }
822
- if (opts.phase !== undefined && opts.phase !== null) {
823
- const phaseNum = parseInt(opts.phase, 10);
824
- if (isNaN(phaseNum) || phaseNum < 1) {
825
- die(`Invalid phase number: "${opts.phase}"\n\nPhase must be a positive integer (e.g., 1, 2, 3)`);
826
- }
827
- updated.phase = phaseNum;
828
- }
829
- // Handle repeatable --code-paths flags (WU-1225: append by default, replace with --replace-code-paths)
830
- // WU-1816: Split comma-separated string into array (same pattern as test paths)
831
- // WU-1870: Fix to split comma-separated values WITHIN array elements (Commander passes ['a,b'] not 'a,b')
832
- if (opts.codePaths && opts.codePaths.length > 0) {
833
- const rawCodePaths = opts.codePaths;
834
- const codePaths = Array.isArray(rawCodePaths)
835
- ? rawCodePaths
836
- .flatMap((p) => p.split(','))
837
- .map((p) => p.trim())
838
- .filter(Boolean)
839
- : rawCodePaths
840
- .split(',')
841
- .map((p) => p.trim())
842
- .filter(Boolean);
843
- // WU-1225: Invert logic - append by default, replace with --replace-code-paths
844
- // Also support legacy --append flag for backwards compatibility
845
- const shouldAppend = !opts.replaceCodePaths || opts.append;
846
- updated.code_paths = mergeArrayField(wu.code_paths, codePaths, shouldAppend);
847
- }
848
- // WU-1225: Handle repeatable --risks flags (append by default, replace with --replace-risks)
849
- // Split comma-separated values within each entry for consistency with other list fields
850
- if (opts.risks && opts.risks.length > 0) {
851
- const rawRisks = opts.risks;
852
- const risks = Array.isArray(rawRisks)
853
- ? rawRisks
854
- .flatMap((risk) => risk.split(','))
855
- .map((risk) => risk.trim())
856
- .filter(Boolean)
857
- : rawRisks
858
- .split(',')
859
- .map((risk) => risk.trim())
860
- .filter(Boolean);
861
- // WU-1225: Invert logic - append by default
862
- const shouldAppend = !opts.replaceRisks || opts.append;
863
- updated.risks = mergeArrayField(wu.risks, risks, shouldAppend);
864
- }
865
- // WU-1390: Handle test path flags (DRY refactor)
866
- // WU-1225: Test paths now append by default (consistent with --acceptance and --code-paths)
867
- const testPathMappings = [
868
- { optKey: 'testPathsManual', field: 'manual' },
869
- { optKey: 'testPathsUnit', field: 'unit' },
870
- { optKey: 'testPathsE2e', field: 'e2e' },
871
- ];
872
- for (const { optKey, field } of testPathMappings) {
873
- const rawPaths = opts[optKey];
874
- if (rawPaths && rawPaths.length > 0) {
875
- // Split comma-separated string into array (options are comma-separated per description)
876
- // WU-1870: Fix to split comma-separated values WITHIN array elements
877
- const paths = Array.isArray(rawPaths)
878
- ? rawPaths
879
- .flatMap((p) => p.split(','))
880
- .map((p) => p.trim())
881
- .filter(Boolean)
882
- : rawPaths
883
- .split(',')
884
- .map((p) => p.trim())
885
- .filter(Boolean);
886
- updated.tests = updated.tests || {};
887
- // WU-1225: Append by default (no individual replace flags for test paths yet)
888
- const shouldAppend = true;
889
- updated.tests[field] = mergeArrayField(wu.tests?.[field], paths, shouldAppend);
890
- }
891
- }
892
- // WU-2564: Handle --blocked-by flag
893
- // WU-1225: Append by default, replace with --replace-blocked-by
894
- if (opts.blockedBy) {
895
- const rawBlockedBy = opts.blockedBy;
896
- const blockedByIds = rawBlockedBy
897
- .split(',')
898
- .map((id) => id.trim())
899
- .filter(Boolean);
900
- const shouldAppend = !opts.replaceBlockedBy || opts.append;
901
- updated.blocked_by = mergeArrayField(wu.blocked_by, blockedByIds, shouldAppend);
902
- }
903
- // WU-2564: Handle --add-dep flag
904
- // WU-1225: Append by default, replace with --replace-dependencies
905
- if (opts.addDep) {
906
- const rawAddDep = opts.addDep;
907
- const depIds = rawAddDep
908
- .split(',')
909
- .map((id) => id.trim())
910
- .filter(Boolean);
911
- const shouldAppend = !opts.replaceDependencies || opts.append;
912
- updated.dependencies = mergeArrayField(wu.dependencies, depIds, shouldAppend);
913
- }
914
- // WU-1039: Handle --exposure flag with validation
915
- if (opts.exposure) {
916
- const exposureResult = validateExposureValue(opts.exposure);
917
- if (!exposureResult.valid) {
918
- die(exposureResult.error);
919
- }
920
- updated.exposure = opts.exposure;
921
- }
922
- return updated;
923
- }
924
238
  /**
925
239
  * Main entry point
926
240
  */
@@ -1003,16 +317,16 @@ async function main() {
1003
317
  // Apply edits to get updated WU
1004
318
  const updatedWU = applyEdits(originalWU, opts);
1005
319
  // WU-2004: Normalize legacy schema fields before validation
1006
- // Converts: summarydescription, string risksarray, test_pathstests, etc.
320
+ // Converts: summary->description, string risks->array, test_paths->tests, etc.
1007
321
  const normalizedForValidation = normalizeWUSchema(updatedWU);
1008
322
  // WU-1539: Validate WU structure after applying edits (fail-fast, allows placeholders)
1009
323
  // WU-1750: Zod transforms normalize embedded newlines in arrays and strings
1010
324
  const validationResult = validateReadyWU(normalizedForValidation);
1011
325
  if (!validationResult.success) {
1012
326
  const errors = validationResult.error.issues
1013
- .map((issue) => ` ${issue.path.join('.')}: ${issue.message}`)
327
+ .map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)
1014
328
  .join('\n');
1015
- die(`${PREFIX} WU YAML validation failed:\n\n${errors}\n\nFix the issues above and retry.`);
329
+ die(`${PREFIX} WU YAML validation failed:\n\n${errors}\n\nFix the issues above and retry.`);
1016
330
  }
1017
331
  // WU-2253: Validate acceptance/code_paths consistency and invariants compliance
1018
332
  // This blocks WU edits if acceptance references paths not in code_paths
@@ -1021,7 +335,7 @@ async function main() {
1021
335
  const lintResult = lintWUSpec(normalizedForValidation, { invariantsPath });
1022
336
  if (!lintResult.valid) {
1023
337
  const formatted = formatLintErrors(lintResult.errors);
1024
- die(`${PREFIX} WU SPEC LINT FAILED:\n\n${formatted}\n` +
338
+ die(`${PREFIX} WU SPEC LINT FAILED:\n\n${formatted}\n` +
1025
339
  `Fix the issues above before editing this WU.`);
1026
340
  }
1027
341
  // WU-1750: CRITICAL - Use transformed data for all subsequent operations
@@ -1051,8 +365,8 @@ async function main() {
1051
365
  }
1052
366
  }
1053
367
  if (strictErrors.length > 0) {
1054
- const errorList = strictErrors.map((e) => ` ${e}`).join('\n');
1055
- die(`${PREFIX} Strict validation failed:\n\n${errorList}\n\n` +
368
+ const errorList = strictErrors.map((e) => ` - ${e}`).join('\n');
369
+ die(`${PREFIX} Strict validation failed:\n\n${errorList}\n\n` +
1056
370
  `Options:\n` +
1057
371
  ` 1. Fix the paths in the WU spec to match actual files\n` +
1058
372
  ` 2. Use --no-strict to bypass path existence checks (not recommended)`);
@@ -1100,7 +414,7 @@ async function main() {
1100
414
  updatedWU: normalizedWU,
1101
415
  });
1102
416
  await getGitForCwd().push('origin', currentBranch);
1103
- console.log(`${PREFIX} Successfully edited ${id} on branch ${currentBranch}`);
417
+ console.log(`${PREFIX} Successfully edited ${id} on branch ${currentBranch}`);
1104
418
  console.log(`${PREFIX} Changes committed and pushed to origin/${currentBranch}`);
1105
419
  displayReadinessSummary(id);
1106
420
  }
@@ -1158,7 +472,7 @@ async function main() {
1158
472
  id,
1159
473
  updatedWU: normalizedWU,
1160
474
  });
1161
- console.log(`${PREFIX} Successfully edited ${id} in worktree`);
475
+ console.log(`${PREFIX} Successfully edited ${id} in worktree`);
1162
476
  console.log(`${PREFIX} Changes committed to lane branch`);
1163
477
  // WU-1620: Display readiness summary
1164
478
  displayReadinessSummary(id);
@@ -1190,7 +504,7 @@ async function main() {
1190
504
  // Emergency fix Session 2: Use centralized stringifyYAML helper
1191
505
  const yamlContent = stringifyYAML(normalizedWU);
1192
506
  writeFileSync(wuPath, yamlContent, { encoding: FILE_SYSTEM.ENCODING });
1193
- console.log(`${PREFIX} Updated ${id}.yaml in micro-worktree`);
507
+ console.log(`${PREFIX} Updated ${id}.yaml in micro-worktree`);
1194
508
  // WU-1929: Handle bidirectional initiative updates
1195
509
  if (initiativeChanged) {
1196
510
  const initiativeFiles = updateInitiativeWusArrays(worktreePath, id, oldInitiative, newInitiative);
@@ -1199,7 +513,7 @@ async function main() {
1199
513
  // WU-1594: Keep backlog projection synchronized with WU lane/spec edits.
1200
514
  const backlogPath = join(worktreePath, WU_PATHS.BACKLOG());
1201
515
  await regenerateBacklogFromState(backlogPath);
1202
- console.log(`${PREFIX} Regenerated backlog.md in micro-worktree`);
516
+ console.log(`${PREFIX} Regenerated backlog.md in micro-worktree`);
1203
517
  return {
1204
518
  commitMessage: COMMIT_FORMATS.EDIT(id),
1205
519
  files: getWuEditCommitFiles(id, extraFiles),
@@ -1215,7 +529,7 @@ async function main() {
1215
529
  process.env.LUMENFLOW_WU_TOOL = previousWuTool;
1216
530
  }
1217
531
  }
1218
- console.log(`${PREFIX} Successfully edited ${id}`);
532
+ console.log(`${PREFIX} Successfully edited ${id}`);
1219
533
  console.log(`${PREFIX} Changes pushed to origin/main`);
1220
534
  // WU-1620: Display readiness summary
1221
535
  displayReadinessSummary(id);