@lumenflow/cli 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +11 -8
  2. package/dist/__tests__/init-config-lanes.test.js +131 -0
  3. package/dist/__tests__/init-docs-structure.test.js +119 -0
  4. package/dist/__tests__/init-lane-inference.test.js +125 -0
  5. package/dist/__tests__/init-onboarding-docs.test.js +132 -0
  6. package/dist/__tests__/init-quick-ref.test.js +145 -0
  7. package/dist/__tests__/init-scripts.test.js +207 -0
  8. package/dist/__tests__/init-template-portability.test.js +97 -0
  9. package/dist/__tests__/init.test.js +7 -2
  10. package/dist/__tests__/initiative-add-wu.test.js +420 -0
  11. package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
  12. package/dist/__tests__/initiative-remove-wu.test.js +458 -0
  13. package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
  14. package/dist/__tests__/path-centralization-cli.test.js +234 -0
  15. package/dist/__tests__/plan-create.test.js +126 -0
  16. package/dist/__tests__/plan-edit.test.js +157 -0
  17. package/dist/__tests__/plan-link.test.js +239 -0
  18. package/dist/__tests__/plan-promote.test.js +181 -0
  19. package/dist/__tests__/templates-sync.test.js +219 -0
  20. package/dist/__tests__/wu-create-strict.test.js +118 -0
  21. package/dist/__tests__/wu-edit-strict.test.js +109 -0
  22. package/dist/__tests__/wu-validate-strict.test.js +113 -0
  23. package/dist/flow-bottlenecks.js +4 -2
  24. package/dist/gates.js +22 -0
  25. package/dist/init.js +670 -87
  26. package/dist/initiative-add-wu.js +112 -16
  27. package/dist/initiative-remove-wu.js +248 -0
  28. package/dist/onboarding-smoke-test.js +400 -0
  29. package/dist/orchestrate-init-status.js +37 -9
  30. package/dist/orchestrate-initiative.js +10 -4
  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/sync-templates.js +137 -5
  36. package/dist/wu-block.js +16 -5
  37. package/dist/wu-claim.js +15 -9
  38. package/dist/wu-create.js +50 -2
  39. package/dist/wu-deps.js +3 -1
  40. package/dist/wu-done.js +14 -5
  41. package/dist/wu-edit.js +35 -0
  42. package/dist/wu-prep.js +131 -8
  43. package/dist/wu-spawn.js +14 -1
  44. package/dist/wu-unblock.js +34 -2
  45. package/dist/wu-validate.js +25 -17
  46. package/package.json +11 -7
  47. package/templates/core/.lumenflow/constraints.md.template +61 -3
  48. package/templates/core/AGENTS.md.template +2 -2
  49. package/templates/core/LUMENFLOW.md.template +85 -23
  50. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
  51. package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
  52. package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
  53. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
  54. package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
  55. package/templates/core/ai/onboarding/release-process.md.template +8 -2
  56. package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
  57. package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
  58. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
  59. package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
  60. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
  61. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
  62. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
  63. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
  64. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
  65. package/dist/__tests__/init-plan.test.js +0 -340
  66. package/dist/agent-issues-query.d.ts +0 -16
  67. package/dist/agent-log-issue.d.ts +0 -10
  68. package/dist/agent-session-end.d.ts +0 -10
  69. package/dist/agent-session.d.ts +0 -10
  70. package/dist/backlog-prune.d.ts +0 -84
  71. package/dist/cli-entry-point.d.ts +0 -8
  72. package/dist/deps-add.d.ts +0 -91
  73. package/dist/deps-remove.d.ts +0 -17
  74. package/dist/docs-sync.d.ts +0 -50
  75. package/dist/file-delete.d.ts +0 -84
  76. package/dist/file-edit.d.ts +0 -82
  77. package/dist/file-read.d.ts +0 -92
  78. package/dist/file-write.d.ts +0 -90
  79. package/dist/flow-bottlenecks.d.ts +0 -16
  80. package/dist/flow-report.d.ts +0 -16
  81. package/dist/gates.d.ts +0 -94
  82. package/dist/git-branch.d.ts +0 -65
  83. package/dist/git-diff.d.ts +0 -58
  84. package/dist/git-log.d.ts +0 -69
  85. package/dist/git-status.d.ts +0 -58
  86. package/dist/guard-locked.d.ts +0 -62
  87. package/dist/guard-main-branch.d.ts +0 -50
  88. package/dist/guard-worktree-commit.d.ts +0 -59
  89. package/dist/index.d.ts +0 -10
  90. package/dist/init-plan.d.ts +0 -80
  91. package/dist/init-plan.js +0 -337
  92. package/dist/init.d.ts +0 -46
  93. package/dist/initiative-add-wu.d.ts +0 -22
  94. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  95. package/dist/initiative-create.d.ts +0 -28
  96. package/dist/initiative-edit.d.ts +0 -34
  97. package/dist/initiative-list.d.ts +0 -12
  98. package/dist/initiative-status.d.ts +0 -11
  99. package/dist/lumenflow-upgrade.d.ts +0 -103
  100. package/dist/mem-checkpoint.d.ts +0 -16
  101. package/dist/mem-cleanup.d.ts +0 -29
  102. package/dist/mem-create.d.ts +0 -17
  103. package/dist/mem-export.d.ts +0 -10
  104. package/dist/mem-inbox.d.ts +0 -35
  105. package/dist/mem-init.d.ts +0 -15
  106. package/dist/mem-ready.d.ts +0 -16
  107. package/dist/mem-signal.d.ts +0 -16
  108. package/dist/mem-start.d.ts +0 -16
  109. package/dist/mem-summarize.d.ts +0 -22
  110. package/dist/mem-triage.d.ts +0 -22
  111. package/dist/metrics-cli.d.ts +0 -90
  112. package/dist/metrics-snapshot.d.ts +0 -18
  113. package/dist/orchestrate-init-status.d.ts +0 -11
  114. package/dist/orchestrate-initiative.d.ts +0 -12
  115. package/dist/orchestrate-monitor.d.ts +0 -11
  116. package/dist/release.d.ts +0 -117
  117. package/dist/rotate-progress.d.ts +0 -48
  118. package/dist/session-coordinator.d.ts +0 -74
  119. package/dist/spawn-list.d.ts +0 -16
  120. package/dist/state-bootstrap.d.ts +0 -92
  121. package/dist/sync-templates.d.ts +0 -52
  122. package/dist/trace-gen.d.ts +0 -84
  123. package/dist/validate-agent-skills.d.ts +0 -50
  124. package/dist/validate-agent-sync.d.ts +0 -36
  125. package/dist/validate-backlog-sync.d.ts +0 -37
  126. package/dist/validate-skills-spec.d.ts +0 -40
  127. package/dist/validate.d.ts +0 -60
  128. package/dist/wu-block.d.ts +0 -16
  129. package/dist/wu-claim.d.ts +0 -74
  130. package/dist/wu-cleanup.d.ts +0 -35
  131. package/dist/wu-create.d.ts +0 -69
  132. package/dist/wu-delete.d.ts +0 -21
  133. package/dist/wu-deps.d.ts +0 -13
  134. package/dist/wu-done.d.ts +0 -225
  135. package/dist/wu-edit.d.ts +0 -63
  136. package/dist/wu-infer-lane.d.ts +0 -17
  137. package/dist/wu-preflight.d.ts +0 -47
  138. package/dist/wu-prune.d.ts +0 -16
  139. package/dist/wu-recover.d.ts +0 -37
  140. package/dist/wu-release.d.ts +0 -19
  141. package/dist/wu-repair.d.ts +0 -60
  142. package/dist/wu-spawn-completion.d.ts +0 -10
  143. package/dist/wu-spawn.d.ts +0 -192
  144. package/dist/wu-status.d.ts +0 -25
  145. package/dist/wu-unblock.d.ts +0 -16
  146. package/dist/wu-unlock-lane.d.ts +0 -19
  147. package/dist/wu-validate.d.ts +0 -16
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-prep.js CHANGED
@@ -1,21 +1,28 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI command uses console for status output */
2
3
  /**
3
4
  * WU Prep Helper (WU-1223)
4
5
  *
5
6
  * Prepares a WU for completion by running gates and generating docs in the worktree.
6
7
  * After successful prep, prints copy-paste instruction to run wu:done from main.
7
8
  *
9
+ * WU-1344: When gates fail on spec:linter due to pre-existing WU validation errors
10
+ * (not caused by the current WU), prints a ready-to-copy wu:done --skip-gates
11
+ * command with reason and fix-wu placeholders.
12
+ *
8
13
  * Workflow:
9
14
  * 1. Verify we're in a worktree (error if in main checkout)
10
15
  * 2. Run gates in the worktree
11
- * 3. Generate docs (if applicable)
12
- * 4. Print copy-paste instruction for wu:done from main
16
+ * 3. If gates fail, check if failures are pre-existing on main
17
+ * 4. If pre-existing, print skip-gates command; otherwise, print fix guidance
18
+ * 5. On success, print copy-paste instruction for wu:done from main
13
19
  *
14
20
  * Usage:
15
21
  * pnpm wu:prep --id WU-XXX [--docs-only]
16
22
  *
17
23
  * @module
18
24
  */
25
+ import { spawnSync } from 'node:child_process';
19
26
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
20
27
  import { die } from '@lumenflow/core/dist/error-handler.js';
21
28
  import { resolveLocation } from '@lumenflow/core/dist/context/location-resolver.js';
@@ -39,6 +46,84 @@ const PREP_OPTIONS = {
39
46
  description: 'Run docs-only gates (format, spec-linter)',
40
47
  },
41
48
  };
49
+ /**
50
+ * WU-1344: Check if a gate name is the spec:linter gate.
51
+ * Used to identify when spec:linter fails so we can check for pre-existing failures.
52
+ *
53
+ * @param gateName - Name of the gate that failed
54
+ * @returns true if this is the spec:linter gate
55
+ */
56
+ export function isPreExistingSpecLinterFailure(gateName) {
57
+ const normalizedName = gateName.toLowerCase().replace(/[:-]/g, '');
58
+ return normalizedName === 'speclinter';
59
+ }
60
+ /**
61
+ * WU-1344: Format a skip-gates command for wu:done.
62
+ * Includes --reason and --fix-wu placeholders.
63
+ *
64
+ * @param options - Configuration options
65
+ * @param options.wuId - The WU ID being completed
66
+ * @param options.mainCheckout - Path to main checkout
67
+ * @returns Formatted command string ready to copy-paste
68
+ */
69
+ export function formatSkipGatesCommand(options) {
70
+ const { wuId, mainCheckout } = options;
71
+ return `cd ${mainCheckout} && pnpm wu:done --id ${wuId} --skip-gates --reason "pre-existing on main" --fix-wu WU-XXXX`;
72
+ }
73
+ /**
74
+ * WU-1344: Default implementation of execOnMain using spawnSync.
75
+ * Uses spawnSync with pnpm executable for safety (no shell injection risk).
76
+ */
77
+ function defaultExecOnMain(mainCheckout) {
78
+ return async (cmd) => {
79
+ // Parse command to extract pnpm script name and args
80
+ // Expected format: "pnpm spec:linter" or similar
81
+ const parts = cmd.split(/\s+/);
82
+ const executable = parts[0];
83
+ const args = parts.slice(1);
84
+ const result = spawnSync(executable, args, {
85
+ cwd: mainCheckout,
86
+ encoding: 'utf-8',
87
+ stdio: ['pipe', 'pipe', 'pipe'],
88
+ });
89
+ return {
90
+ exitCode: result.status ?? 1,
91
+ stdout: result.stdout ?? '',
92
+ stderr: result.stderr ?? '',
93
+ };
94
+ };
95
+ }
96
+ /**
97
+ * WU-1344: Check if spec:linter failures are pre-existing on main branch.
98
+ *
99
+ * Runs spec:linter on the main checkout to determine if the failures
100
+ * already exist there (i.e., not caused by the current WU).
101
+ *
102
+ * @param options - Configuration options
103
+ * @param options.mainCheckout - Path to main checkout
104
+ * @param options.execOnMain - Optional function to execute commands on main (for testing)
105
+ * @returns Result indicating whether failures are pre-existing
106
+ */
107
+ export async function checkPreExistingFailures(options) {
108
+ const { mainCheckout, execOnMain = defaultExecOnMain(mainCheckout) } = options;
109
+ try {
110
+ // Run spec:linter on main checkout
111
+ const result = await execOnMain('pnpm spec:linter');
112
+ // If spec:linter fails on main, the failures are pre-existing
113
+ if (result.exitCode !== 0) {
114
+ return { hasPreExisting: true };
115
+ }
116
+ return { hasPreExisting: false };
117
+ }
118
+ catch (error) {
119
+ // If we can't check main, assume failures are NOT pre-existing
120
+ // (safer to require fixing rather than skipping)
121
+ return {
122
+ hasPreExisting: false,
123
+ error: error.message,
124
+ };
125
+ }
126
+ }
42
127
  /**
43
128
  * Print success message with copy-paste instruction.
44
129
  */
@@ -106,6 +191,34 @@ async function main() {
106
191
  console.log(`${PREP_PREFIX} Location: ${location.cwd}`);
107
192
  console.log(`${PREP_PREFIX} Main checkout: ${location.mainCheckout}`);
108
193
  console.log('');
194
+ // WU-1344: Check for pre-existing spec:linter failures on main BEFORE running gates.
195
+ // We do this first because runGates() calls die() on failure, which exits the process
196
+ // before we can check. By checking first, we can set up an exit handler to show
197
+ // the skip-gates command if gates fail.
198
+ console.log(`${PREP_PREFIX} Checking for pre-existing spec:linter failures on main...`);
199
+ const preExistingCheck = await checkPreExistingFailures({
200
+ mainCheckout: location.mainCheckout,
201
+ });
202
+ if (preExistingCheck.hasPreExisting) {
203
+ console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Pre-existing failures detected on main.`);
204
+ // Set up an exit handler to print the skip-gates command when gates fail
205
+ // This runs before process.exit() fully terminates the process
206
+ process.on('exit', (code) => {
207
+ if (code !== EXIT_CODES.SUCCESS) {
208
+ console.log('');
209
+ console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Since failures are pre-existing on main, you can skip gates:`);
210
+ console.log('');
211
+ console.log(` ${formatSkipGatesCommand({ wuId: id, mainCheckout: location.mainCheckout })}`);
212
+ console.log('');
213
+ console.log(`${PREP_PREFIX} Replace WU-XXXX with the WU that will fix these spec issues.`);
214
+ console.log('');
215
+ }
216
+ });
217
+ }
218
+ else {
219
+ console.log(`${PREP_PREFIX} No pre-existing failures on main.`);
220
+ }
221
+ console.log('');
109
222
  // Run gates in the worktree
110
223
  console.log(`${PREP_PREFIX} Running gates in worktree...`);
111
224
  const gatesResult = await runGates({
@@ -113,13 +226,23 @@ async function main() {
113
226
  docsOnly: args.docsOnly,
114
227
  });
115
228
  if (!gatesResult) {
116
- die(`${EMOJI.FAILURE} Gates failed in worktree.\n\n` +
117
- `Fix the gate failures and run wu:prep again.`);
229
+ // Gates failed - if pre-existing check was already done and showed failures,
230
+ // the exit handler above will print the skip-gates command.
231
+ // Otherwise, tell the user to fix the failures.
232
+ if (!preExistingCheck.hasPreExisting) {
233
+ die(`${EMOJI.FAILURE} Gates failed in worktree.\n\n` +
234
+ `Fix the gate failures and run wu:prep again.`);
235
+ }
236
+ // Pre-existing failures - exit with error, handler will print skip-gates command
237
+ process.exit(EXIT_CODES.ERROR);
118
238
  }
119
239
  // Success - print copy-paste instruction
120
240
  printSuccessMessage(id, location.mainCheckout);
121
241
  }
122
- main().catch((e) => {
123
- console.error(e.message);
124
- process.exit(EXIT_CODES.ERROR);
125
- });
242
+ // WU-1181: Use import.meta.main instead of process.argv[1] comparison
243
+ // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
244
+ // path but import.meta.url resolves to the real path - they never match
245
+ import { runCLI } from './cli-entry-point.js';
246
+ if (import.meta.main) {
247
+ void runCLI(main);
248
+ }
package/dist/wu-spawn.js CHANGED
@@ -36,11 +36,14 @@ 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, getWipLimitForLane } 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';
43
45
  import { validateSpawnArgs, generateExecutionModeSection, generateThinkToolGuidance, recordSpawnToRegistry, formatSpawnRecordedMessage, } from '@lumenflow/core/dist/wu-spawn-helpers.js';
46
+ // eslint-disable-next-line sonarjs/deprecation -- legacy factory used by CLI spawns
44
47
  import { SpawnStrategyFactory } from '@lumenflow/core/dist/spawn-strategy.js';
45
48
  import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
46
49
  import { generateClientSkillsGuidance, generateSkillsSelectionSection, resolveClientConfig, } from '@lumenflow/core/dist/wu-spawn-skills.js';
@@ -1146,11 +1149,17 @@ ${SPAWN_END_SENTINEL}
1146
1149
  }
1147
1150
  /**
1148
1151
  * WU-1603: Check if a lane is currently occupied by another WU
1152
+ * WU-1325: Now considers lock_policy - lanes with policy=none are never occupied
1149
1153
  *
1150
1154
  * @param {string} lane - Lane name (e.g., "Operations: Tooling")
1151
1155
  * @returns {import('@lumenflow/core/dist/lane-lock.js').LockMetadata|null} Lock metadata if occupied, null otherwise
1152
1156
  */
1153
1157
  export function checkLaneOccupation(lane) {
1158
+ // WU-1325: Lanes with lock_policy=none never report as occupied
1159
+ const lockPolicy = getLockPolicyForLane(lane);
1160
+ if (lockPolicy === 'none') {
1161
+ return null;
1162
+ }
1154
1163
  const lockStatus = checkLaneLock(lane);
1155
1164
  if (lockStatus.locked && lockStatus.metadata) {
1156
1165
  return lockStatus.metadata;
@@ -1160,7 +1169,10 @@ export function checkLaneOccupation(lane) {
1160
1169
  export function generateLaneOccupationWarning(lockMetadata, targetWuId, options = {}) {
1161
1170
  const { isStale = false } = options;
1162
1171
  let warning = `⚠️ Lane "${lockMetadata.lane}" is occupied by ${lockMetadata.wuId}\n`;
1163
- warning += ` This violates WIP=1 (Work In Progress limit of 1 per lane).\n\n`;
1172
+ // WU-1346: Use injected values if provided, otherwise look up from config
1173
+ const lockPolicy = options.lockPolicy ?? getLockPolicyForLane(lockMetadata.lane);
1174
+ const wipLimit = options.wipLimit ?? getWipLimitForLane(lockMetadata.lane);
1175
+ warning += ` This violates WIP=${wipLimit} (lock_policy=${lockPolicy}).\n\n`;
1164
1176
  if (isStale) {
1165
1177
  warning += ` ⏰ This lock is STALE (>24 hours old) - the WU may be abandoned.\n`;
1166
1178
  warning += ` Consider using pnpm wu:block --id ${lockMetadata.wuId} if work is stalled.\n\n`;
@@ -1319,6 +1331,7 @@ async function main() {
1319
1331
  }
1320
1332
  }
1321
1333
  // Create strategy
1334
+ // eslint-disable-next-line sonarjs/deprecation -- legacy factory used by CLI spawns
1322
1335
  const strategy = SpawnStrategyFactory.create(clientName);
1323
1336
  const clientContext = { name: clientName, config: resolveClientConfig(config, clientName) };
1324
1337
  if (clientName === 'codex-cli' || args.codex) {
@@ -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.1",
4
4
  "description": "Command-line interface for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -74,7 +74,11 @@
74
74
  "initiative-status": "./dist/initiative-status.js",
75
75
  "initiative-add-wu": "./dist/initiative-add-wu.js",
76
76
  "initiative-plan": "./dist/initiative-plan.js",
77
- "init-plan": "./dist/init-plan.js",
77
+ "init-plan": "./dist/initiative-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/metrics": "2.5.1",
152
+ "@lumenflow/core": "2.5.1",
153
+ "@lumenflow/initiatives": "2.5.1",
154
+ "@lumenflow/agent": "2.5.1",
155
+ "@lumenflow/memory": "2.5.1"
152
156
  },
153
157
  "devDependencies": {
154
158
  "@vitest/coverage-v8": "^4.0.17",
@@ -1,13 +1,13 @@
1
1
  # LumenFlow Constraints Capsule
2
2
 
3
- **Version:** 1.0
3
+ **Version:** 1.1
4
4
  **Last updated:** {{DATE}}
5
5
 
6
- This document contains the 6 non-negotiable constraints that every agent must keep "in working memory" from first plan through `wu:done`.
6
+ This document contains the 7 non-negotiable constraints that every agent must keep "in working memory" from first plan through `wu:done`.
7
7
 
8
8
  ---
9
9
 
10
- ## The 6 Non-Negotiable Constraints
10
+ ## The 7 Non-Negotiable Constraints
11
11
 
12
12
  ### 1. Worktree Discipline and Git Safety
13
13
 
@@ -19,8 +19,40 @@ This document contains the 6 non-negotiable constraints that every agent must ke
19
19
  - Hooks block WU commits from main checkout
20
20
  - Forbidden commands on main: `git reset --hard`, `git stash`, `git clean -fd`, `git push --force`
21
21
 
22
+ **MANDATORY PRE-WRITE CHECK**
23
+
24
+ Before ANY Write/Edit/Read operation:
25
+
26
+ 1. **Verify worktree location**:
27
+
28
+ ```bash
29
+ pwd
30
+ # Must show: .../worktrees/<lane>-wu-xxx
31
+ ```
32
+
33
+ 2. **Verify relative paths only**:
34
+ - ✅ `docs/...`, `packages/...`, `apps/...`, `./...`
35
+ - ❌ `/home/...`, `/Users/...`, or full repo paths
36
+
37
+ 3. **Docs-only exception (documentation WUs)**:
38
+ - You may run **read-only** commands from main (e.g., `pnpm gates --docs-only`).
39
+ - **All file writes still require a worktree**. If no worktree exists, claim one first.
40
+
22
41
  **Why:** Worktree isolation prevents cross-contamination between parallel WUs and protects the main branch.
23
42
 
43
+ **NEVER "QUICK FIX" ON MAIN**
44
+
45
+ If you see something broken on main (failing gates, format issues, typos, lint errors):
46
+
47
+ - ❌ **Don't** fix it directly, even if it seems small or helpful
48
+ - ❌ **Don't** run `prettier --write`, `pnpm lint --fix`, or any command that modifies files
49
+ - ✅ **Do** report the issue to the user
50
+ - ✅ **Do** create a WU if a fix is needed
51
+
52
+ **Why this matters:** The "helpful fix" instinct causes agents to modify files on main without a worktree. While commits are blocked by hooks, the files are still modified, requiring manual cleanup. Every change—no matter how small—needs a worktree.
53
+
54
+ **If you're on main and want to change something: STOP. Create a WU first.**
55
+
24
56
  ---
25
57
 
26
58
  ### 2. WUs Are Specs, Not Code
@@ -94,6 +126,32 @@ This document contains the 6 non-negotiable constraints that every agent must ke
94
126
 
95
127
  ---
96
128
 
129
+ ### 7. Test Ratchet Pattern (WU-1253)
130
+
131
+ **Rule:** NEW test failures block gates; pre-existing failures (in baseline) warn but do not block.
132
+
133
+ **Baseline File:** `.lumenflow/test-baseline.json`
134
+
135
+ **Enforcement:**
136
+
137
+ - Gates compare current test results against baseline
138
+ - NEW failures (not in baseline) = **BLOCK** (must fix or add to baseline)
139
+ - Pre-existing failures (in baseline) = **WARNING** (continue, do not block WU)
140
+ - Fixed tests auto-removed from baseline (ratchet forward)
141
+
142
+ **When gates fail due to tests:**
143
+
144
+ 1. Check baseline: `cat .lumenflow/test-baseline.json`
145
+ 2. If failure is pre-existing: warning shown, WU proceeds
146
+ 3. If failure is NEW: fix the test or add to baseline with:
147
+ ```bash
148
+ pnpm baseline:add --test "<test_name>" --reason "<why>" --fix-wu WU-XXXX
149
+ ```
150
+
151
+ **Why:** Agents should not be blocked by unrelated failures. The ratchet ensures quality improves over time (failures can only be removed, never added without justification).
152
+
153
+ ---
154
+
97
155
  ## Mini Audit Checklist
98
156
 
99
157
  Before running `wu:done`, verify:
@@ -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