@lumenflow/cli 2.4.0 → 2.5.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 (124) hide show
  1. package/dist/__tests__/init-config-lanes.test.js +131 -0
  2. package/dist/__tests__/init-docs-structure.test.js +119 -0
  3. package/dist/__tests__/init-lane-inference.test.js +125 -0
  4. package/dist/__tests__/init-onboarding-docs.test.js +132 -0
  5. package/dist/__tests__/init-quick-ref.test.js +145 -0
  6. package/dist/__tests__/init-scripts.test.js +96 -0
  7. package/dist/__tests__/init-template-portability.test.js +97 -0
  8. package/dist/__tests__/init.test.js +7 -2
  9. package/dist/__tests__/initiative-add-wu.test.js +420 -0
  10. package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
  11. package/dist/__tests__/initiative-remove-wu.test.js +458 -0
  12. package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
  13. package/dist/__tests__/path-centralization-cli.test.js +234 -0
  14. package/dist/__tests__/plan-create.test.js +126 -0
  15. package/dist/__tests__/plan-edit.test.js +157 -0
  16. package/dist/__tests__/plan-link.test.js +239 -0
  17. package/dist/__tests__/plan-promote.test.js +181 -0
  18. package/dist/__tests__/wu-create-strict.test.js +118 -0
  19. package/dist/__tests__/wu-edit-strict.test.js +109 -0
  20. package/dist/__tests__/wu-validate-strict.test.js +113 -0
  21. package/dist/flow-bottlenecks.js +4 -2
  22. package/dist/gates.js +22 -0
  23. package/dist/init.js +580 -87
  24. package/dist/initiative-add-wu.js +112 -16
  25. package/dist/initiative-remove-wu.js +248 -0
  26. package/dist/onboarding-smoke-test.js +400 -0
  27. package/dist/plan-create.js +199 -0
  28. package/dist/plan-edit.js +235 -0
  29. package/dist/plan-link.js +233 -0
  30. package/dist/plan-promote.js +231 -0
  31. package/dist/wu-block.js +16 -5
  32. package/dist/wu-claim.js +15 -9
  33. package/dist/wu-create.js +50 -2
  34. package/dist/wu-deps.js +3 -1
  35. package/dist/wu-done.js +14 -5
  36. package/dist/wu-edit.js +35 -0
  37. package/dist/wu-spawn.js +8 -0
  38. package/dist/wu-unblock.js +34 -2
  39. package/dist/wu-validate.js +25 -17
  40. package/package.json +10 -6
  41. package/templates/core/AGENTS.md.template +2 -2
  42. package/dist/__tests__/init-plan.test.js +0 -340
  43. package/dist/agent-issues-query.d.ts +0 -16
  44. package/dist/agent-log-issue.d.ts +0 -10
  45. package/dist/agent-session-end.d.ts +0 -10
  46. package/dist/agent-session.d.ts +0 -10
  47. package/dist/backlog-prune.d.ts +0 -84
  48. package/dist/cli-entry-point.d.ts +0 -8
  49. package/dist/deps-add.d.ts +0 -91
  50. package/dist/deps-remove.d.ts +0 -17
  51. package/dist/docs-sync.d.ts +0 -50
  52. package/dist/file-delete.d.ts +0 -84
  53. package/dist/file-edit.d.ts +0 -82
  54. package/dist/file-read.d.ts +0 -92
  55. package/dist/file-write.d.ts +0 -90
  56. package/dist/flow-bottlenecks.d.ts +0 -16
  57. package/dist/flow-report.d.ts +0 -16
  58. package/dist/gates.d.ts +0 -94
  59. package/dist/git-branch.d.ts +0 -65
  60. package/dist/git-diff.d.ts +0 -58
  61. package/dist/git-log.d.ts +0 -69
  62. package/dist/git-status.d.ts +0 -58
  63. package/dist/guard-locked.d.ts +0 -62
  64. package/dist/guard-main-branch.d.ts +0 -50
  65. package/dist/guard-worktree-commit.d.ts +0 -59
  66. package/dist/index.d.ts +0 -10
  67. package/dist/init-plan.d.ts +0 -80
  68. package/dist/init-plan.js +0 -337
  69. package/dist/init.d.ts +0 -46
  70. package/dist/initiative-add-wu.d.ts +0 -22
  71. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  72. package/dist/initiative-create.d.ts +0 -28
  73. package/dist/initiative-edit.d.ts +0 -34
  74. package/dist/initiative-list.d.ts +0 -12
  75. package/dist/initiative-status.d.ts +0 -11
  76. package/dist/lumenflow-upgrade.d.ts +0 -103
  77. package/dist/mem-checkpoint.d.ts +0 -16
  78. package/dist/mem-cleanup.d.ts +0 -29
  79. package/dist/mem-create.d.ts +0 -17
  80. package/dist/mem-export.d.ts +0 -10
  81. package/dist/mem-inbox.d.ts +0 -35
  82. package/dist/mem-init.d.ts +0 -15
  83. package/dist/mem-ready.d.ts +0 -16
  84. package/dist/mem-signal.d.ts +0 -16
  85. package/dist/mem-start.d.ts +0 -16
  86. package/dist/mem-summarize.d.ts +0 -22
  87. package/dist/mem-triage.d.ts +0 -22
  88. package/dist/metrics-cli.d.ts +0 -90
  89. package/dist/metrics-snapshot.d.ts +0 -18
  90. package/dist/orchestrate-init-status.d.ts +0 -11
  91. package/dist/orchestrate-initiative.d.ts +0 -12
  92. package/dist/orchestrate-monitor.d.ts +0 -11
  93. package/dist/release.d.ts +0 -117
  94. package/dist/rotate-progress.d.ts +0 -48
  95. package/dist/session-coordinator.d.ts +0 -74
  96. package/dist/spawn-list.d.ts +0 -16
  97. package/dist/state-bootstrap.d.ts +0 -92
  98. package/dist/sync-templates.d.ts +0 -52
  99. package/dist/trace-gen.d.ts +0 -84
  100. package/dist/validate-agent-skills.d.ts +0 -50
  101. package/dist/validate-agent-sync.d.ts +0 -36
  102. package/dist/validate-backlog-sync.d.ts +0 -37
  103. package/dist/validate-skills-spec.d.ts +0 -40
  104. package/dist/validate.d.ts +0 -60
  105. package/dist/wu-block.d.ts +0 -16
  106. package/dist/wu-claim.d.ts +0 -74
  107. package/dist/wu-cleanup.d.ts +0 -35
  108. package/dist/wu-create.d.ts +0 -69
  109. package/dist/wu-delete.d.ts +0 -21
  110. package/dist/wu-deps.d.ts +0 -13
  111. package/dist/wu-done.d.ts +0 -225
  112. package/dist/wu-edit.d.ts +0 -63
  113. package/dist/wu-infer-lane.d.ts +0 -17
  114. package/dist/wu-preflight.d.ts +0 -47
  115. package/dist/wu-prune.d.ts +0 -16
  116. package/dist/wu-recover.d.ts +0 -37
  117. package/dist/wu-release.d.ts +0 -19
  118. package/dist/wu-repair.d.ts +0 -60
  119. package/dist/wu-spawn-completion.d.ts +0 -10
  120. package/dist/wu-spawn.d.ts +0 -192
  121. package/dist/wu-status.d.ts +0 -25
  122. package/dist/wu-unblock.d.ts +0 -16
  123. package/dist/wu-unlock-lane.d.ts +0 -19
  124. package/dist/wu-validate.d.ts +0 -16
package/dist/wu-block.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * WU Block Helper
4
5
  *
@@ -32,6 +33,8 @@ import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
32
33
  import { WUStateStore } from '@lumenflow/core/dist/wu-state-store.js';
33
34
  // WU-1603: Atomic lane locking - release lock when WU is blocked
34
35
  import { releaseLaneLock } from '@lumenflow/core/dist/lane-lock.js';
36
+ // WU-1325: Import lock policy getter to determine release behavior
37
+ import { getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
35
38
  // ensureOnMain() moved to wu-helpers.ts (WU-1256)
36
39
  // ensureStaged() moved to git-staged-validator.ts (WU-1341)
37
40
  // defaultWorktreeFrom() moved to wu-paths.ts (WU-1341)
@@ -215,15 +218,23 @@ async function main() {
215
218
  await getGitForCwd().push(REMOTES.ORIGIN, BRANCHES.MAIN);
216
219
  }
217
220
  await handleWorktreeRemoval(args, doc);
218
- // WU-1603: Release lane lock when WU is blocked
219
- // This allows another WU to be claimed in the same lane
221
+ // WU-1325: Release lane lock when WU is blocked (only for lock_policy=active)
222
+ // For policy=all, lock is held through block/unblock cycle
223
+ // For policy=none, no lock exists to release
220
224
  try {
221
225
  const lane = doc.lane;
222
226
  if (lane) {
223
- const releaseResult = releaseLaneLock(lane, { wuId: id });
224
- if (releaseResult.released && !releaseResult.notFound) {
225
- console.log(`${LOG_PREFIX.BLOCK} Lane lock released for "${lane}"`);
227
+ const lockPolicy = getLockPolicyForLane(lane);
228
+ if (lockPolicy === 'active') {
229
+ const releaseResult = releaseLaneLock(lane, { wuId: id });
230
+ if (releaseResult.released && !releaseResult.notFound) {
231
+ console.log(`${LOG_PREFIX.BLOCK} Lane lock released for "${lane}" (lock_policy=active)`);
232
+ }
226
233
  }
234
+ else if (lockPolicy === 'all') {
235
+ console.log(`${LOG_PREFIX.BLOCK} Lane lock retained for "${lane}" (lock_policy=all)`);
236
+ }
237
+ // For policy=none, no lock exists - nothing to do
227
238
  }
228
239
  }
229
240
  catch (err) {
package/dist/wu-claim.js CHANGED
@@ -67,9 +67,13 @@ async function ensureCleanOrClaimOnlyWhenNoAuto() {
67
67
  .split(STRING_LITERALS.NEWLINE)
68
68
  .filter(Boolean)
69
69
  .filter((l) => l.startsWith('A ') || l.startsWith('M ') || l.startsWith('R '));
70
- const hasClaimFiles = staged.some((l) => l.includes('docs/04-operations/tasks/status.md') ||
71
- l.includes('docs/04-operations/tasks/backlog.md') ||
72
- /docs\/04-operations\/tasks\/wu\/WU-\d+\.yaml/.test(l));
70
+ // WU-1311: Use config-based paths instead of hardcoded docs/04-operations paths
71
+ const config = getConfig();
72
+ const wuDirPattern = config.directories.wuDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
73
+ const wuYamlRegex = new RegExp(`${wuDirPattern}/WU-\\d+\\.yaml`);
74
+ const hasClaimFiles = staged.some((l) => l.includes(config.directories.statusPath) ||
75
+ l.includes(config.directories.backlogPath) ||
76
+ wuYamlRegex.test(l));
73
77
  if (!hasClaimFiles) {
74
78
  console.error(status);
75
79
  die('Stage claim-related files (status/backlog/WU YAML) before running with --no-auto.');
@@ -400,12 +404,12 @@ async function appendClaimEventOnly(stateDir, id, title, lane) {
400
404
  * @returns {string[]} List of files to commit
401
405
  */
402
406
  export function getWorktreeCommitFiles(wuId) {
407
+ // WU-1311: Use config-based paths instead of hardcoded docs/04-operations paths
408
+ const config = getConfig();
403
409
  return [
404
- `docs/04-operations/tasks/wu/${wuId}.yaml`,
410
+ `${config.directories.wuDir}/${wuId}.yaml`,
405
411
  LUMENFLOW_PATHS.WU_EVENTS, // WU-1740: Event store is source of truth
406
- // WU-1746: Explicitly NOT including:
407
- // - docs/04-operations/tasks/backlog.md
408
- // - docs/04-operations/tasks/status.md
412
+ // WU-1746: Explicitly NOT including backlog.md and status.md
409
413
  // These generated files cause merge conflicts when main advances
410
414
  ];
411
415
  }
@@ -694,7 +698,8 @@ function handleLaneOccupancy(laneCheck, lane, id, force) {
694
698
  ` 2. Choose a different lane\n` +
695
699
  ` 3. Increase wip_limit in .lumenflow.config.yaml\n` +
696
700
  ` 4. Use --force to override (P0 emergencies only)\n\n` +
697
- `To check lane status: grep "${STATUS_SECTIONS.IN_PROGRESS}" docs/04-operations/tasks/status.md`);
701
+ // WU-1311: Use config-based status path
702
+ `To check lane status: grep "${STATUS_SECTIONS.IN_PROGRESS}" ${getConfig().directories.statusPath}`);
698
703
  }
699
704
  /**
700
705
  * Handle code path overlap detection (WU-901)
@@ -726,13 +731,14 @@ function handleCodePathOverlap(WU_PATH, STATUS_PATH, id, args) {
726
731
  return ` - ${c.wuid}: ${displayedOverlaps}${suffix}`;
727
732
  })
728
733
  .join(STRING_LITERALS.NEWLINE);
734
+ // WU-1311: Use config-based status path in error message
729
735
  die(`Code path overlap detected with in-progress WUs:\n\n${conflictList}\n\n` +
730
736
  `Merge conflicts are guaranteed if both WUs proceed.\n\n` +
731
737
  `Options:\n` +
732
738
  ` 1. Wait for conflicting WU(s) to complete\n` +
733
739
  ` 2. Coordinate with agent working on conflicting WU\n` +
734
740
  ` 3. Use --force-overlap --reason "..." (emits telemetry for audit)\n\n` +
735
- `To check WU status: grep "${STATUS_SECTIONS.IN_PROGRESS}" docs/04-operations/tasks/status.md`);
741
+ `To check WU status: grep "${STATUS_SECTIONS.IN_PROGRESS}" ${getConfig().directories.statusPath}`);
736
742
  }
737
743
  if (args.forceOverlap) {
738
744
  if (!args.reason) {
package/dist/wu-create.js CHANGED
@@ -41,6 +41,7 @@ import { inferSubLane } from '@lumenflow/core/dist/lane-inference.js';
41
41
  import { parseBacklogFrontmatter } from '@lumenflow/core/dist/backlog-parser.js';
42
42
  import { createWUParser, WU_CREATE_OPTIONS, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
43
43
  import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
44
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
44
45
  import { validateWU } from '@lumenflow/core/dist/wu-schema.js';
45
46
  import { getPlanPath, getPlanProtocolRef, getPlansDir, } from '@lumenflow/core/dist/lumenflow-home.js';
46
47
  import { validateSpecRefs } from '@lumenflow/core/dist/wu-create-validators.js';
@@ -57,6 +58,8 @@ import { validateSpecCompleteness } from '@lumenflow/core/dist/wu-done-validator
57
58
  import { readWU } from '@lumenflow/core/dist/wu-yaml.js';
58
59
  // WU-2253: Import WU spec linter for acceptance/code_paths validation
59
60
  import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
61
+ // WU-1329: Import path existence validators for strict mode
62
+ import { validateCodePathsExistence, validateTestPathsExistence, } from '@lumenflow/core/dist/wu-preflight-validators.js';
60
63
  // WU-1025: Import placeholder validator for inline content validation
61
64
  import { validateNoPlaceholders, buildPlaceholderErrorMessage, } from '@lumenflow/core/dist/wu-validator.js';
62
65
  // WU-1211: Import initiative validation for phase check
@@ -247,9 +250,24 @@ function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
247
250
  ...(specRefs?.length && { spec_refs: specRefs }),
248
251
  };
249
252
  }
253
+ /**
254
+ * Validate WU spec for creation
255
+ *
256
+ * WU-1329: Strict mode (default) validates that code_paths and test_paths exist on disk.
257
+ * Use opts.strict = false to bypass path existence checks.
258
+ *
259
+ * @param params - Validation parameters
260
+ * @returns {{ valid: boolean, errors: string[] }}
261
+ */
250
262
  export function validateCreateSpec({ id, lane, title, priority, type, opts, }) {
251
263
  const errors = [];
252
264
  const effectiveType = type || DEFAULT_TYPE;
265
+ // WU-1329: Strict mode is the default
266
+ const strict = opts.strict !== false;
267
+ // WU-1329: Log when strict validation is bypassed
268
+ if (!strict) {
269
+ console.warn(`${LOG_PREFIX} WARNING: strict validation bypassed (--no-strict). Path existence checks skipped.`);
270
+ }
253
271
  if (!opts.description) {
254
272
  errors.push('--description is required');
255
273
  }
@@ -269,7 +287,9 @@ export function validateCreateSpec({ id, lane, title, priority, type, opts, }) {
269
287
  }
270
288
  }
271
289
  if (effectiveType === 'feature' && !opts.specRefs) {
272
- errors.push('--spec-refs is required for type: feature WUs');
290
+ errors.push('--spec-refs is required for type: feature WUs\n' +
291
+ ' Tip: Create a plan first with: pnpm plan:create --id <WU-ID> --title "..."\n' +
292
+ ' Then use --plan flag or --spec-refs lumenflow://plans/<WU-ID>-plan.md');
273
293
  }
274
294
  if (errors.length > 0) {
275
295
  return { valid: false, errors };
@@ -303,6 +323,29 @@ export function validateCreateSpec({ id, lane, title, priority, type, opts, }) {
303
323
  if (!completeness.valid) {
304
324
  return { valid: false, errors: completeness.errors };
305
325
  }
326
+ // WU-1329: Strict mode validates path existence
327
+ if (strict) {
328
+ const rootDir = process.cwd();
329
+ // Validate code_paths exist
330
+ if (opts.codePaths && opts.codePaths.length > 0) {
331
+ const codePathsResult = validateCodePathsExistence(opts.codePaths, rootDir);
332
+ if (!codePathsResult.valid) {
333
+ errors.push(...codePathsResult.errors);
334
+ }
335
+ }
336
+ // Validate test_paths exist (unit, e2e - not manual)
337
+ const testsObj = {
338
+ unit: opts.testPathsUnit || [],
339
+ e2e: opts.testPathsE2e || [],
340
+ };
341
+ const testPathsResult = validateTestPathsExistence(testsObj, rootDir);
342
+ if (!testPathsResult.valid) {
343
+ errors.push(...testPathsResult.errors);
344
+ }
345
+ if (errors.length > 0) {
346
+ return { valid: false, errors };
347
+ }
348
+ }
306
349
  return { valid: true, errors: [] };
307
350
  }
308
351
  /**
@@ -382,9 +425,10 @@ function updateBacklogInWorktree(worktreePath, id, lane, title) {
382
425
  const backlogRelativePath = WU_PATHS.BACKLOG();
383
426
  const backlogAbsolutePath = join(worktreePath, backlogRelativePath);
384
427
  if (!existsSync(backlogAbsolutePath)) {
428
+ // WU-1311: Use config-based backlog path in error message
385
429
  die(`Backlog not found in micro-worktree: ${backlogAbsolutePath}\n\n` +
386
430
  `Options:\n` +
387
- ` 1. Ensure backlog.md exists at docs/04-operations/tasks/backlog.md\n` +
431
+ ` 1. Ensure backlog.md exists at ${getConfig().directories.backlogPath}\n` +
388
432
  ` 2. Run from repository root directory`);
389
433
  }
390
434
  const { frontmatter, markdown } = parseBacklogFrontmatter(backlogAbsolutePath);
@@ -482,6 +526,8 @@ async function main() {
482
526
  WU_OPTIONS.uiPairingWus,
483
527
  // WU-1062: External plan options for wu:create
484
528
  WU_CREATE_OPTIONS.plan,
529
+ // WU-1329: Strict validation is default, --no-strict bypasses
530
+ WU_OPTIONS.noStrict,
485
531
  ],
486
532
  required: ['lane', 'title'], // WU-1246: --id is now optional (auto-generated if not provided)
487
533
  allowPositionalId: false,
@@ -558,6 +604,8 @@ async function main() {
558
604
  blocks: args.blocks,
559
605
  labels: args.labels,
560
606
  assignedTo,
607
+ // WU-1329: Strict validation is default, --no-strict bypasses
608
+ strict: !args.noStrict,
561
609
  },
562
610
  });
563
611
  if (!createSpecValidation.valid) {
package/dist/wu-deps.js CHANGED
@@ -15,6 +15,7 @@ import { die } from '@lumenflow/core/dist/error-handler.js';
15
15
  import { buildDependencyGraphAsync, renderASCII, renderMermaid, validateGraph, } from '@lumenflow/core/dist/dependency-graph.js';
16
16
  import { OUTPUT_FORMATS } from '@lumenflow/initiatives/dist/initiative-constants.js';
17
17
  import { PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
18
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
18
19
  async function main() {
19
20
  const args = createWUParser({
20
21
  name: 'wu-deps',
@@ -33,7 +34,8 @@ async function main() {
33
34
  console.log(`[wu:deps] Building dependency graph...`);
34
35
  const graph = await buildDependencyGraphAsync();
35
36
  if (!graph.has(wuId)) {
36
- die(`WU not found in graph: ${wuId}\n\nEnsure the WU exists in docs/04-operations/tasks/wu/`);
37
+ // WU-1311: Use config-based WU directory path
38
+ die(`WU not found in graph: ${wuId}\n\nEnsure the WU exists in ${getConfig().directories.wuDir}/`);
37
39
  }
38
40
  const format = args.format || OUTPUT_FORMATS.ASCII;
39
41
  const depth = args.depth ? parseInt(args.depth, 10) : 3;
package/dist/wu-done.js CHANGED
@@ -65,6 +65,7 @@ CONTEXT_VALIDATION, } from '@lumenflow/core/dist/wu-constants.js';
65
65
  import { printGateFailureBox, printStatusPreview } from '@lumenflow/core/dist/wu-done-ui.js';
66
66
  import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
67
67
  import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
68
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
68
69
  import { writeWU, appendNote, parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
69
70
  import { PLACEHOLDER_SENTINEL, validateWU, validateDoneWU, validateApprovalGates, } from '@lumenflow/core/dist/wu-schema.js';
70
71
  import { validateBacklogSync } from '@lumenflow/core/dist/backlog-sync-validator.js';
@@ -969,11 +970,14 @@ async function runGatesInWorktree(worktreePath, id, options = {}) {
969
970
  }
970
971
  async function validateStagedFiles(id, isDocsOnly = false) {
971
972
  const staged = await listStaged();
973
+ // WU-1311: Use config-based paths instead of hardcoded docs/04-operations paths
974
+ const config = getConfig();
975
+ const wuPath = `${config.directories.wuDir}/${id}.yaml`;
972
976
  // WU-1740: Include wu-events.jsonl to persist state store events
973
977
  const whitelist = [
974
- `docs/04-operations/tasks/wu/${id}.yaml`,
975
- 'docs/04-operations/tasks/status.md',
976
- 'docs/04-operations/tasks/backlog.md',
978
+ wuPath,
979
+ config.directories.statusPath,
980
+ config.directories.backlogPath,
977
981
  WU_EVENTS_PATH,
978
982
  ];
979
983
  if (isDocsOnly) {
@@ -998,7 +1002,10 @@ async function validateStagedFiles(id, isDocsOnly = false) {
998
1002
  return true;
999
1003
  });
1000
1004
  if (unexpected.length > 0) {
1001
- const otherWuYamlOnly = unexpected.every((f) => /^docs\/04-operations\/tasks\/wu\/WU-\d+\.yaml$/.test(f));
1005
+ // WU-1311: Use config-based pattern for WU YAML detection
1006
+ const wuDirPattern = config.directories.wuDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1007
+ const wuYamlRegex = new RegExp(`^${wuDirPattern}/WU-\\d+\\.yaml$`);
1008
+ const otherWuYamlOnly = unexpected.every((f) => wuYamlRegex.test(f));
1002
1009
  if (otherWuYamlOnly) {
1003
1010
  console.warn(`${LOG_PREFIX.DONE} Warning: other WU YAMLs are staged; proceeding and committing only current WU files.`);
1004
1011
  }
@@ -1620,8 +1627,10 @@ async function executePreFlightChecks({ id, args, isBranchOnly, isDocsOnly, docM
1620
1627
  if (!specResult.valid) {
1621
1628
  console.error(`\n❌ Spec completeness validation failed for ${id}:\n`);
1622
1629
  specResult.errors.forEach((err) => console.error(` - ${err}`));
1630
+ // WU-1311: Use config-based path in error message
1631
+ const specConfig = getConfig();
1623
1632
  console.error(`\nFix these issues before running wu:done:\n` +
1624
- ` 1. Update docs/04-operations/tasks/wu/${id}.yaml\n` +
1633
+ ` 1. Update ${specConfig.directories.wuDir}/${id}.yaml\n` +
1625
1634
  ` 2. Fill description with Context/Problem/Solution\n` +
1626
1635
  ` 3. Replace ${PLACEHOLDER_SENTINEL} text with specific criteria\n` +
1627
1636
  ` 4. List all modified files in code_paths\n` +
package/dist/wu-edit.js CHANGED
@@ -57,6 +57,8 @@ import { readInitiative, writeInitiative } from '@lumenflow/initiatives/dist/ini
57
57
  import { normalizeWUSchema } from '@lumenflow/core/dist/wu-schema-normalization.js';
58
58
  // WU-2253: Import WU spec linter for acceptance/code_paths validation
59
59
  import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
60
+ // WU-1329: Import path existence validators for strict validation
61
+ import { validateCodePathsExistence, validateTestPathsExistence, } from '@lumenflow/core/dist/wu-preflight-validators.js';
60
62
  /* eslint-disable security/detect-object-injection */
61
63
  const PREFIX = LOG_PREFIX.EDIT;
62
64
  /**
@@ -377,6 +379,8 @@ function parseArgs() {
377
379
  EDIT_OPTIONS.replaceDependencies,
378
380
  // WU-1039: Add exposure for done WU metadata updates
379
381
  WU_OPTIONS.exposure,
382
+ // WU-1329: Strict validation is default, --no-strict bypasses
383
+ WU_OPTIONS.noStrict,
380
384
  ],
381
385
  required: ['id'],
382
386
  allowPositionalId: true,
@@ -921,6 +925,37 @@ async function main() {
921
925
  // WU-1750: CRITICAL - Use transformed data for all subsequent operations
922
926
  // This ensures embedded newlines are normalized before YAML output
923
927
  const normalizedWU = validationResult.data;
928
+ // WU-1329: Strict validation of path existence (default behavior)
929
+ // --no-strict bypasses these checks
930
+ const strict = !opts.noStrict;
931
+ if (!strict) {
932
+ console.warn(`${PREFIX} WARNING: strict validation bypassed (--no-strict). Path existence checks skipped.`);
933
+ }
934
+ if (strict) {
935
+ const rootDir = process.cwd();
936
+ const strictErrors = [];
937
+ // Validate code_paths exist
938
+ if (normalizedWU.code_paths && normalizedWU.code_paths.length > 0) {
939
+ const codePathsResult = validateCodePathsExistence(normalizedWU.code_paths, rootDir);
940
+ if (!codePathsResult.valid) {
941
+ strictErrors.push(...codePathsResult.errors);
942
+ }
943
+ }
944
+ // Validate test_paths exist (unit, e2e - not manual)
945
+ if (normalizedWU.tests) {
946
+ const testPathsResult = validateTestPathsExistence(normalizedWU.tests, rootDir);
947
+ if (!testPathsResult.valid) {
948
+ strictErrors.push(...testPathsResult.errors);
949
+ }
950
+ }
951
+ if (strictErrors.length > 0) {
952
+ const errorList = strictErrors.map((e) => ` • ${e}`).join('\n');
953
+ die(`${PREFIX} ❌ Strict validation failed:\n\n${errorList}\n\n` +
954
+ `Options:\n` +
955
+ ` 1. Fix the paths in the WU spec to match actual files\n` +
956
+ ` 2. Use --no-strict to bypass path existence checks (not recommended)`);
957
+ }
958
+ }
924
959
  // Validate lane format if present (WU-923: block parent-only lanes with taxonomy)
925
960
  if (normalizedWU.lane) {
926
961
  validateLaneFormat(normalizedWU.lane);
package/dist/wu-spawn.js CHANGED
@@ -36,7 +36,9 @@ import { parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
36
36
  import { die } from '@lumenflow/core/dist/error-handler.js';
37
37
  import { WU_STATUS, PATTERNS, FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
38
38
  // WU-1603: Check lane lock status before spawning
39
+ // WU-1325: Import lock policy getter for lane availability check
39
40
  import { checkLaneLock } from '@lumenflow/core/dist/lane-lock.js';
41
+ import { getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
40
42
  import { minimatch } from 'minimatch';
41
43
  // WU-2252: Import invariants loader for spawn output injection
42
44
  import { loadInvariants, INVARIANT_TYPES } from '@lumenflow/core/dist/invariants-runner.js';
@@ -1146,11 +1148,17 @@ ${SPAWN_END_SENTINEL}
1146
1148
  }
1147
1149
  /**
1148
1150
  * WU-1603: Check if a lane is currently occupied by another WU
1151
+ * WU-1325: Now considers lock_policy - lanes with policy=none are never occupied
1149
1152
  *
1150
1153
  * @param {string} lane - Lane name (e.g., "Operations: Tooling")
1151
1154
  * @returns {import('@lumenflow/core/dist/lane-lock.js').LockMetadata|null} Lock metadata if occupied, null otherwise
1152
1155
  */
1153
1156
  export function checkLaneOccupation(lane) {
1157
+ // WU-1325: Lanes with lock_policy=none never report as occupied
1158
+ const lockPolicy = getLockPolicyForLane(lane);
1159
+ if (lockPolicy === 'none') {
1160
+ return null;
1161
+ }
1154
1162
  const lockStatus = checkLaneLock(lane);
1155
1163
  if (lockStatus.locked && lockStatus.metadata) {
1156
1164
  return lockStatus.metadata;
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * WU Unblock Helper
4
5
  *
@@ -16,12 +17,15 @@
16
17
  import { existsSync, writeFileSync } from 'node:fs';
17
18
  import path from 'node:path';
18
19
  import { assertTransition } from '@lumenflow/core/dist/state-machine.js';
19
- import { checkLaneFree } from '@lumenflow/core/dist/lane-checker.js';
20
+ import { checkLaneFree, getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
21
+ // WU-1325: Import lane lock functions for policy-based lock acquisition on unblock
22
+ import { acquireLaneLock } from '@lumenflow/core/dist/lane-lock.js';
20
23
  import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
21
24
  import { die } from '@lumenflow/core/dist/error-handler.js';
22
25
  import { todayISO } from '@lumenflow/core/dist/date-utils.js';
23
26
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
24
27
  import { WU_PATHS, defaultWorktreeFrom } from '@lumenflow/core/dist/wu-paths.js';
28
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
25
29
  import { readWU, writeWU, appendNote } from '@lumenflow/core/dist/wu-yaml.js';
26
30
  import { STATUS_SECTIONS, PATTERNS, LOG_PREFIX, WU_STATUS, REMOTES, BRANCHES, GIT_REFS, FILE_SYSTEM, EXIT_CODES, MICRO_WORKTREE_OPERATIONS, } from '@lumenflow/core/dist/wu-constants.js';
27
31
  import { defaultBranchFrom } from '@lumenflow/core/dist/wu-done-validators.js';
@@ -88,7 +92,8 @@ function handleLaneOccupancy(laneCheck, lane, id, force) {
88
92
  ` 1. Wait for ${laneCheck.occupiedBy} to complete or block\n` +
89
93
  ` 2. Move ${id} to a different lane\n` +
90
94
  ` 3. Use --force to override (P0 emergencies only)\n\n` +
91
- `To check lane status: grep "${STATUS_SECTIONS.IN_PROGRESS}" docs/04-operations/tasks/status.md`);
95
+ // WU-1311: Use config-based status path
96
+ `To check lane status: grep "${STATUS_SECTIONS.IN_PROGRESS}" ${getConfig().directories.statusPath}`);
92
97
  }
93
98
  /**
94
99
  * Handle optional worktree creation after unblock
@@ -221,6 +226,33 @@ async function main() {
221
226
  getGitForCwd().run(`git push ${REMOTES.ORIGIN} ${BRANCHES.MAIN}`);
222
227
  }
223
228
  handleWorktreeCreation(args, doc);
229
+ // WU-1325: Re-acquire lane lock when WU is unblocked (only for lock_policy=active)
230
+ // For policy=all, lock was retained through the block cycle
231
+ // For policy=none, no lock exists to acquire
232
+ try {
233
+ if (lane && lane !== 'Unknown') {
234
+ const lockPolicy = getLockPolicyForLane(lane);
235
+ if (lockPolicy === 'active') {
236
+ const lockResult = acquireLaneLock(lane, id);
237
+ if (lockResult.acquired && !lockResult.skipped) {
238
+ console.log(`${PREFIX} Lane lock re-acquired for "${lane}" (lock_policy=active)`);
239
+ }
240
+ else if (!lockResult.acquired) {
241
+ // Lock acquisition failed - another WU claimed the lane while we were blocked
242
+ console.warn(`${PREFIX} Warning: Could not re-acquire lane lock: ${lockResult.error}`);
243
+ console.warn(`${PREFIX} Another WU may have claimed lane "${lane}" while this WU was blocked.`);
244
+ }
245
+ }
246
+ else if (lockPolicy === 'all') {
247
+ console.log(`${PREFIX} Lane lock retained for "${lane}" (lock_policy=all)`);
248
+ }
249
+ // For policy=none, no lock exists - nothing to do
250
+ }
251
+ }
252
+ catch (err) {
253
+ // Non-blocking: lock acquisition failure should not block the unblocking operation
254
+ console.warn(`${PREFIX} Warning: Could not acquire lane lock: ${err.message}`);
255
+ }
224
256
  console.log(`\n${PREFIX} Marked in progress and pushed.`);
225
257
  console.log(`- WU: ${id} — ${title}`);
226
258
  if (args.reason)
@@ -3,13 +3,15 @@
3
3
  * WU Validation Tool
4
4
  *
5
5
  * Validates WU YAML files against schema and checks for quality warnings.
6
- * Returns exit code 0 if valid (warnings are advisory, not blocking).
7
- * Returns exit code 1 only for schema errors.
6
+ *
7
+ * WU-1329: Strict mode is now the DEFAULT behavior.
8
+ * - Warnings are treated as errors by default
9
+ * - Use --no-strict to restore legacy advisory-only warnings behavior
8
10
  *
9
11
  * Usage:
10
- * pnpm wu:validate --id WU-123 # Validate specific WU
11
- * pnpm wu:validate --all # Validate all WUs
12
- * pnpm wu:validate --all --strict # Fail on warnings too
12
+ * pnpm wu:validate --id WU-123 # Validate with strict mode (default)
13
+ * pnpm wu:validate --all # Validate all WUs with strict mode
14
+ * pnpm wu:validate --all --no-strict # Warnings are advisory (legacy behavior)
13
15
  *
14
16
  * @see {@link packages/@lumenflow/cli/src/lib/wu-schema.ts} - Schema definitions
15
17
  */
@@ -27,12 +29,14 @@ const LOG_PREFIX = '[wu:validate]';
27
29
  /**
28
30
  * Validate a single WU file
29
31
  *
32
+ * WU-1329: strict defaults to true (warnings treated as errors)
33
+ *
30
34
  * @param {string} wuPath - Path to WU YAML file
31
35
  * @param {object} options - Validation options
32
- * @param {boolean} options.strict - Treat warnings as errors
36
+ * @param {boolean} options.strict - Treat warnings as errors (default: true)
33
37
  * @returns {{valid: boolean, warnings: string[], errors: string[]}}
34
38
  */
35
- function validateSingleWU(wuPath, { strict = false } = {}) {
39
+ function validateSingleWU(wuPath, { strict = true } = {}) {
36
40
  const errors = [];
37
41
  const warnings = [];
38
42
  // Read and parse YAML
@@ -74,11 +78,13 @@ function validateSingleWU(wuPath, { strict = false } = {}) {
74
78
  /**
75
79
  * Validate all WU files
76
80
  *
81
+ * WU-1329: strict defaults to true (warnings treated as errors)
82
+ *
77
83
  * @param {object} options - Validation options
78
- * @param {boolean} options.strict - Treat warnings as errors
84
+ * @param {boolean} options.strict - Treat warnings as errors (default: true)
79
85
  * @returns {{totalValid: number, totalInvalid: number, totalWarnings: number, results: object[]}}
80
86
  */
81
- function validateAllWUs({ strict = false } = {}) {
87
+ function validateAllWUs({ strict = true } = {}) {
82
88
  const wuDir = WU_PATHS.WU_DIR();
83
89
  if (!existsSync(wuDir)) {
84
90
  die(`WU directory not found: ${wuDir}`);
@@ -110,7 +116,7 @@ function validateAllWUs({ strict = false } = {}) {
110
116
  async function main() {
111
117
  const args = createWUParser({
112
118
  name: 'wu-validate',
113
- description: 'Validate WU YAML files against schema',
119
+ description: 'Validate WU YAML files against schema (strict mode by default, WU-1329)',
114
120
  options: [
115
121
  WU_OPTIONS.id,
116
122
  {
@@ -119,17 +125,19 @@ async function main() {
119
125
  type: 'boolean',
120
126
  description: 'Validate all WUs',
121
127
  },
122
- {
123
- name: 'strict',
124
- flags: '-s, --strict',
125
- type: 'boolean',
126
- description: 'Treat warnings as errors',
127
- },
128
+ // WU-1329: Change from --strict to --no-strict (strict is now default)
129
+ WU_OPTIONS.noStrict,
128
130
  ],
129
131
  required: [],
130
132
  allowPositionalId: true,
131
133
  });
132
- const { id, all, strict } = args;
134
+ const { id, all, noStrict } = args;
135
+ // WU-1329: Strict mode is the default, --no-strict opts out
136
+ const strict = !noStrict;
137
+ // WU-1329: Log when strict validation is bypassed
138
+ if (noStrict) {
139
+ console.warn(`${LOG_PREFIX} WARNING: strict validation bypassed (--no-strict). Warnings will be advisory only.`);
140
+ }
133
141
  if (!id && !all) {
134
142
  die('Must specify --id WU-XXX or --all');
135
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/cli",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Command-line interface for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -75,6 +75,10 @@
75
75
  "initiative-add-wu": "./dist/initiative-add-wu.js",
76
76
  "initiative-plan": "./dist/initiative-plan.js",
77
77
  "init-plan": "./dist/init-plan.js",
78
+ "plan-create": "./dist/plan-create.js",
79
+ "plan-link": "./dist/plan-link.js",
80
+ "plan-edit": "./dist/plan-edit.js",
81
+ "plan-promote": "./dist/plan-promote.js",
78
82
  "agent-session": "./dist/agent-session.js",
79
83
  "agent-session-end": "./dist/agent-session-end.js",
80
84
  "agent-log-issue": "./dist/agent-log-issue.js",
@@ -144,11 +148,11 @@
144
148
  "pretty-ms": "^9.2.0",
145
149
  "simple-git": "^3.30.0",
146
150
  "yaml": "^2.8.2",
147
- "@lumenflow/core": "2.4.0",
148
- "@lumenflow/metrics": "2.4.0",
149
- "@lumenflow/memory": "2.4.0",
150
- "@lumenflow/initiatives": "2.4.0",
151
- "@lumenflow/agent": "2.4.0"
151
+ "@lumenflow/core": "2.5.0",
152
+ "@lumenflow/initiatives": "2.5.0",
153
+ "@lumenflow/memory": "2.5.0",
154
+ "@lumenflow/agent": "2.5.0",
155
+ "@lumenflow/metrics": "2.5.0"
152
156
  },
153
157
  "devDependencies": {
154
158
  "@vitest/coverage-v8": "^4.0.17",
@@ -17,11 +17,11 @@ cd worktrees/<lane>-wu-xxxx
17
17
  pnpm gates
18
18
 
19
19
  # 3. Complete (ALWAYS run this!)
20
- cd {{PROJECT_ROOT}}
20
+ cd <project-root>
21
21
  pnpm wu:done --id WU-XXXX
22
22
  ```
23
23
 
24
- > **Complete CLI reference:** See [quick-ref-commands.md](docs/04-operations/_frameworks/lumenflow/agent/onboarding/quick-ref-commands.md)
24
+ > **Complete CLI reference:** See [quick-ref-commands.md]({{QUICK_REF_LINK}})
25
25
 
26
26
  ---
27
27