@lumenflow/cli 2.3.2 → 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 (135) 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 +199 -3
  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/flow-report.js +3 -2
  23. package/dist/gates.js +202 -2
  24. package/dist/init.js +720 -40
  25. package/dist/initiative-add-wu.js +112 -16
  26. package/dist/initiative-plan.js +3 -2
  27. package/dist/initiative-remove-wu.js +248 -0
  28. package/dist/mem-context.js +0 -0
  29. package/dist/metrics-snapshot.js +3 -2
  30. package/dist/onboarding-smoke-test.js +400 -0
  31. package/dist/plan-create.js +199 -0
  32. package/dist/plan-edit.js +235 -0
  33. package/dist/plan-link.js +233 -0
  34. package/dist/plan-promote.js +231 -0
  35. package/dist/rotate-progress.js +8 -5
  36. package/dist/spawn-list.js +4 -3
  37. package/dist/state-bootstrap.js +6 -4
  38. package/dist/state-doctor-fix.js +5 -4
  39. package/dist/state-doctor.js +32 -2
  40. package/dist/trace-gen.js +6 -3
  41. package/dist/wu-block.js +16 -5
  42. package/dist/wu-claim.js +15 -9
  43. package/dist/wu-create.js +50 -2
  44. package/dist/wu-deps.js +3 -1
  45. package/dist/wu-done.js +14 -5
  46. package/dist/wu-edit.js +35 -0
  47. package/dist/wu-infer-lane.js +3 -1
  48. package/dist/wu-spawn.js +8 -0
  49. package/dist/wu-unblock.js +34 -2
  50. package/dist/wu-validate.js +25 -17
  51. package/package.json +12 -6
  52. package/templates/core/AGENTS.md.template +2 -2
  53. package/dist/__tests__/init-plan.test.js +0 -340
  54. package/dist/agent-issues-query.d.ts +0 -16
  55. package/dist/agent-log-issue.d.ts +0 -10
  56. package/dist/agent-session-end.d.ts +0 -10
  57. package/dist/agent-session.d.ts +0 -10
  58. package/dist/backlog-prune.d.ts +0 -84
  59. package/dist/cli-entry-point.d.ts +0 -8
  60. package/dist/deps-add.d.ts +0 -91
  61. package/dist/deps-remove.d.ts +0 -17
  62. package/dist/docs-sync.d.ts +0 -50
  63. package/dist/file-delete.d.ts +0 -84
  64. package/dist/file-edit.d.ts +0 -82
  65. package/dist/file-read.d.ts +0 -92
  66. package/dist/file-write.d.ts +0 -90
  67. package/dist/flow-bottlenecks.d.ts +0 -16
  68. package/dist/flow-report.d.ts +0 -16
  69. package/dist/gates.d.ts +0 -94
  70. package/dist/git-branch.d.ts +0 -65
  71. package/dist/git-diff.d.ts +0 -58
  72. package/dist/git-log.d.ts +0 -69
  73. package/dist/git-status.d.ts +0 -58
  74. package/dist/guard-locked.d.ts +0 -62
  75. package/dist/guard-main-branch.d.ts +0 -50
  76. package/dist/guard-worktree-commit.d.ts +0 -59
  77. package/dist/index.d.ts +0 -10
  78. package/dist/init-plan.d.ts +0 -80
  79. package/dist/init-plan.js +0 -337
  80. package/dist/init.d.ts +0 -46
  81. package/dist/initiative-add-wu.d.ts +0 -22
  82. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  83. package/dist/initiative-create.d.ts +0 -28
  84. package/dist/initiative-edit.d.ts +0 -34
  85. package/dist/initiative-list.d.ts +0 -12
  86. package/dist/initiative-status.d.ts +0 -11
  87. package/dist/lumenflow-upgrade.d.ts +0 -103
  88. package/dist/mem-checkpoint.d.ts +0 -16
  89. package/dist/mem-cleanup.d.ts +0 -29
  90. package/dist/mem-create.d.ts +0 -17
  91. package/dist/mem-export.d.ts +0 -10
  92. package/dist/mem-inbox.d.ts +0 -35
  93. package/dist/mem-init.d.ts +0 -15
  94. package/dist/mem-ready.d.ts +0 -16
  95. package/dist/mem-signal.d.ts +0 -16
  96. package/dist/mem-start.d.ts +0 -16
  97. package/dist/mem-summarize.d.ts +0 -22
  98. package/dist/mem-triage.d.ts +0 -22
  99. package/dist/metrics-cli.d.ts +0 -90
  100. package/dist/metrics-snapshot.d.ts +0 -18
  101. package/dist/orchestrate-init-status.d.ts +0 -11
  102. package/dist/orchestrate-initiative.d.ts +0 -12
  103. package/dist/orchestrate-monitor.d.ts +0 -11
  104. package/dist/release.d.ts +0 -117
  105. package/dist/rotate-progress.d.ts +0 -48
  106. package/dist/session-coordinator.d.ts +0 -74
  107. package/dist/spawn-list.d.ts +0 -16
  108. package/dist/state-bootstrap.d.ts +0 -92
  109. package/dist/sync-templates.d.ts +0 -52
  110. package/dist/trace-gen.d.ts +0 -84
  111. package/dist/validate-agent-skills.d.ts +0 -50
  112. package/dist/validate-agent-sync.d.ts +0 -36
  113. package/dist/validate-backlog-sync.d.ts +0 -37
  114. package/dist/validate-skills-spec.d.ts +0 -40
  115. package/dist/validate.d.ts +0 -60
  116. package/dist/wu-block.d.ts +0 -16
  117. package/dist/wu-claim.d.ts +0 -74
  118. package/dist/wu-cleanup.d.ts +0 -35
  119. package/dist/wu-create.d.ts +0 -69
  120. package/dist/wu-delete.d.ts +0 -21
  121. package/dist/wu-deps.d.ts +0 -13
  122. package/dist/wu-done.d.ts +0 -225
  123. package/dist/wu-edit.d.ts +0 -63
  124. package/dist/wu-infer-lane.d.ts +0 -17
  125. package/dist/wu-preflight.d.ts +0 -47
  126. package/dist/wu-prune.d.ts +0 -16
  127. package/dist/wu-recover.d.ts +0 -37
  128. package/dist/wu-release.d.ts +0 -19
  129. package/dist/wu-repair.d.ts +0 -60
  130. package/dist/wu-spawn-completion.d.ts +0 -10
  131. package/dist/wu-spawn.d.ts +0 -192
  132. package/dist/wu-status.d.ts +0 -25
  133. package/dist/wu-unblock.d.ts +0 -16
  134. package/dist/wu-unlock-lane.d.ts +0 -19
  135. package/dist/wu-validate.d.ts +0 -16
@@ -15,7 +15,8 @@
15
15
  import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
17
  import { parse as parseYaml } from 'yaml';
18
- import { EXIT_CODES, STATUS_SECTIONS, DIRECTORIES, FILE_SYSTEM, } from '@lumenflow/core/dist/wu-constants.js';
18
+ import { EXIT_CODES, STATUS_SECTIONS, FILE_SYSTEM, } from '@lumenflow/core/dist/wu-constants.js';
19
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
19
20
  import { runCLI } from './cli-entry-point.js';
20
21
  /** Log prefix for console output */
21
22
  const LOG_PREFIX = '[rotate:progress]';
@@ -49,7 +50,8 @@ export function parseRotateArgs(argv) {
49
50
  * Get WU status from YAML file
50
51
  */
51
52
  function getWuStatus(wuId, baseDir = process.cwd()) {
52
- const yamlPath = join(baseDir, DIRECTORIES.WU_DIR, `${wuId}.yaml`);
53
+ // WU-1301: Use config-based paths
54
+ const yamlPath = join(baseDir, WU_PATHS.WU(wuId));
53
55
  if (!existsSync(yamlPath)) {
54
56
  return null;
55
57
  }
@@ -67,7 +69,8 @@ function getWuStatus(wuId, baseDir = process.cwd()) {
67
69
  */
68
70
  function getAllWuStatuses(baseDir = process.cwd()) {
69
71
  const statuses = new Map();
70
- const wuDir = join(baseDir, DIRECTORIES.WU_DIR);
72
+ // WU-1301: Use config-based paths
73
+ const wuDir = join(baseDir, WU_PATHS.WU_DIR());
71
74
  if (!existsSync(wuDir)) {
72
75
  return statuses;
73
76
  }
@@ -203,8 +206,8 @@ async function main() {
203
206
  printHelp();
204
207
  process.exit(EXIT_CODES.SUCCESS);
205
208
  }
206
- // Read status.md
207
- const statusPath = join(process.cwd(), DIRECTORIES.STATUS_PATH);
209
+ // Read status.md - WU-1301: Use config-based paths
210
+ const statusPath = join(process.cwd(), WU_PATHS.STATUS());
208
211
  if (!existsSync(statusPath)) {
209
212
  console.error(`${LOG_PREFIX} Error: ${statusPath} not found`);
210
213
  process.exit(EXIT_CODES.ERROR);
@@ -15,7 +15,8 @@
15
15
  */
16
16
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
17
17
  import { die } from '@lumenflow/core/dist/error-handler.js';
18
- import { PATTERNS, LUMENFLOW_PATHS, DIRECTORIES } from '@lumenflow/core/dist/wu-constants.js';
18
+ import { PATTERNS, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
19
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
19
20
  /** Local EMOJI constants for spawn-list output */
20
21
  const EMOJI = {
21
22
  WARNING: '⚠️',
@@ -38,10 +39,10 @@ const SPAWN_LIST_OPTIONS = {
38
39
  import { buildSpawnTree, formatSpawnTree, getSpawnsByWU, getSpawnsByInitiative, treeToJSON, STATUS_INDICATORS, } from '@lumenflow/core/dist/spawn-tree.js';
39
40
  import { SpawnStatus } from '@lumenflow/core/dist/spawn-registry-schema.js';
40
41
  const LOG_PREFIX = '[spawn:list]';
41
- /** Default paths for spawn registry and WU files */
42
+ /** Default paths for spawn registry and WU files (WU-1301: uses config-based paths) */
42
43
  const DEFAULT_PATHS = Object.freeze({
43
44
  REGISTRY_DIR: LUMENFLOW_PATHS.STATE_DIR,
44
- WU_DIR: DIRECTORIES.WU_DIR,
45
+ WU_DIR: WU_PATHS.WU_DIR(),
45
46
  });
46
47
  /** Initiative ID pattern */
47
48
  const INIT_PATTERN = /^INIT-\d+$/;
@@ -15,18 +15,20 @@ import { readdirSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
15
15
  import path from 'node:path';
16
16
  import { parse as parseYaml } from 'yaml';
17
17
  import { readFileSync } from 'node:fs';
18
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
18
19
  import { CLI_FLAGS, EXIT_CODES, EMOJI, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
19
20
  /* eslint-disable security/detect-non-literal-fs-filename */
20
21
  /** Log prefix for consistent output */
21
22
  const LOG_PREFIX = '[state-bootstrap]';
22
23
  /**
23
24
  * Default configuration for state bootstrap
25
+ * WU-1301: Uses config-based paths instead of hardcoded values
24
26
  */
25
27
  export const STATE_BOOTSTRAP_DEFAULTS = {
26
- /** Default WU directory path */
27
- wuDir: 'docs/04-operations/tasks/wu',
28
- /** Default state directory path */
29
- stateDir: '.lumenflow/state',
28
+ /** Default WU directory path (from config) */
29
+ wuDir: WU_PATHS.WU_DIR(),
30
+ /** Default state directory path (from config) */
31
+ stateDir: WU_PATHS.STATE_DIR(),
30
32
  };
31
33
  /**
32
34
  * Parse command line arguments for state-bootstrap
@@ -14,6 +14,7 @@
14
14
  import fs from 'node:fs/promises';
15
15
  import path from 'node:path';
16
16
  import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
17
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
17
18
  /**
18
19
  * Operation name for micro-worktree isolation
19
20
  */
@@ -31,13 +32,13 @@ const SIGNALS_FILE = '.lumenflow/memory/signals.jsonl';
31
32
  */
32
33
  const WU_EVENTS_FILE = '.lumenflow/state/wu-events.jsonl';
33
34
  /**
34
- * Backlog file path (relative to project root)
35
+ * Backlog file path (WU-1301: uses config-based paths)
35
36
  */
36
- const BACKLOG_FILE = 'docs/04-operations/tasks/backlog.md';
37
+ const BACKLOG_FILE = WU_PATHS.BACKLOG();
37
38
  /**
38
- * Status file path (relative to project root)
39
+ * Status file path (WU-1301: uses config-based paths)
39
40
  */
40
- const STATUS_FILE = 'docs/04-operations/tasks/status.md';
41
+ const STATUS_FILE = WU_PATHS.STATUS();
41
42
  /**
42
43
  * Remove lines containing a WU reference from markdown content
43
44
  *
@@ -32,7 +32,8 @@ import { parse as parseYaml } from 'yaml';
32
32
  import { diagnoseState, ISSUE_TYPES, ISSUE_SEVERITY, } from '@lumenflow/core/dist/state-doctor-core.js';
33
33
  import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
34
34
  import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
35
- import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
35
+ import { getConfig, getResolvedPaths } from '@lumenflow/core/dist/lumenflow-config.js';
36
+ import { existsSync } from 'node:fs';
36
37
  import { createStamp } from '@lumenflow/core/dist/stamp-utils.js';
37
38
  import { createStateDoctorFixDeps } from './state-doctor-fix.js';
38
39
  /**
@@ -170,10 +171,11 @@ async function createDeps(baseDir) {
170
171
  },
171
172
  /**
172
173
  * List all stamp file IDs
174
+ * WU-1301: Use config-based paths instead of LUMENFLOW_PATHS
173
175
  */
174
176
  listStamps: async () => {
175
177
  try {
176
- const stampsDir = path.join(baseDir, LUMENFLOW_PATHS.STAMPS_DIR);
178
+ const stampsDir = path.join(baseDir, config.beacon.stampsDir);
177
179
  const stampFiles = await fg('WU-*.done', { cwd: stampsDir });
178
180
  return stampFiles.map((file) => file.replace('.done', ''));
179
181
  }
@@ -438,6 +440,32 @@ function buildAuditOutput(result) {
438
440
  dryRun: result.dryRun,
439
441
  };
440
442
  }
443
+ /**
444
+ * WU-1301: Warn if configured paths don't exist
445
+ * This helps consumers detect misconfiguration early.
446
+ */
447
+ function warnMissingPaths(baseDir, quiet) {
448
+ if (quiet)
449
+ return;
450
+ const paths = getResolvedPaths({ projectRoot: baseDir });
451
+ const missing = [];
452
+ if (!existsSync(paths.wuDir)) {
453
+ missing.push(`WU directory: ${paths.wuDir}`);
454
+ }
455
+ if (!existsSync(paths.stampsDir)) {
456
+ missing.push(`Stamps directory: ${paths.stampsDir}`);
457
+ }
458
+ if (!existsSync(paths.stateDir)) {
459
+ missing.push(`State directory: ${paths.stateDir}`);
460
+ }
461
+ if (missing.length > 0) {
462
+ console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Configured paths not found:`);
463
+ for (const p of missing) {
464
+ console.warn(` - ${p}`);
465
+ }
466
+ console.warn(' Tip: Run `pnpm setup` or check .lumenflow.config.yaml');
467
+ }
468
+ }
441
469
  /**
442
470
  * Main CLI entry point
443
471
  */
@@ -446,6 +474,8 @@ async function main() {
446
474
  const baseDir = args.baseDir || process.cwd();
447
475
  const startedAt = new Date().toISOString();
448
476
  const startTime = Date.now();
477
+ // WU-1301: Warn about missing configured paths
478
+ warnMissingPaths(baseDir, args.quiet ?? false);
449
479
  let result = null;
450
480
  let error = null;
451
481
  try {
package/dist/trace-gen.js CHANGED
@@ -17,7 +17,8 @@ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
18
  import { execSync } from 'node:child_process';
19
19
  import { parse as parseYaml } from 'yaml';
20
- import { EXIT_CODES, DIRECTORIES, FILE_SYSTEM } from '@lumenflow/core/dist/wu-constants.js';
20
+ import { EXIT_CODES, FILE_SYSTEM } from '@lumenflow/core/dist/wu-constants.js';
21
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
21
22
  import { runCLI } from './cli-entry-point.js';
22
23
  /** Log prefix for console output */
23
24
  const LOG_PREFIX = '[trace:gen]';
@@ -131,7 +132,8 @@ function getWuFiles(wuId) {
131
132
  * Get WU info from YAML file
132
133
  */
133
134
  function getWuInfo(wuId) {
134
- const yamlPath = join(process.cwd(), DIRECTORIES.WU_DIR, `${wuId}.yaml`);
135
+ // WU-1301: Use config-based paths
136
+ const yamlPath = join(process.cwd(), WU_PATHS.WU(wuId));
135
137
  if (!existsSync(yamlPath)) {
136
138
  return null;
137
139
  }
@@ -268,7 +270,8 @@ async function main() {
268
270
  else {
269
271
  // Trace all WUs
270
272
  console.error(`${LOG_PREFIX} Scanning all WUs...`);
271
- const wuDir = join(process.cwd(), DIRECTORIES.WU_DIR);
273
+ // WU-1301: Use config-based paths
274
+ const wuDir = join(process.cwd(), WU_PATHS.WU_DIR());
272
275
  if (!existsSync(wuDir)) {
273
276
  console.error(`${LOG_PREFIX} Error: WU directory not found`);
274
277
  process.exit(EXIT_CODES.ERROR);
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);
@@ -20,6 +20,7 @@ import { parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
20
20
  import { inferSubLane } from '@lumenflow/core/dist/lane-inference.js';
21
21
  import { die } from '@lumenflow/core/dist/error-handler.js';
22
22
  import { FILE_SYSTEM, EXIT_CODES } from '@lumenflow/core/dist/wu-constants.js';
23
+ import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
23
24
  function parseArgs(argv) {
24
25
  const args = { paths: [], desc: '', id: null };
25
26
  for (let i = 2; i < argv.length; i++) {
@@ -62,7 +63,8 @@ Options:
62
63
  return args;
63
64
  }
64
65
  function loadWuYaml(id) {
65
- const wuPath = path.join(process.cwd(), 'docs/04-operations/tasks/wu', `${id}.yaml`);
66
+ // WU-1301: Use config-based paths instead of hardcoded path
67
+ const wuPath = path.join(process.cwd(), WU_PATHS.WU(id));
66
68
  if (!existsSync(wuPath)) {
67
69
  die(`WU file not found: ${wuPath}\n\n` +
68
70
  `Options:\n` +
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;