@lumenflow/cli 1.1.0 → 1.3.2
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.
- package/dist/__tests__/cli-entry-point.test.js +50 -0
- package/dist/__tests__/cli-subprocess.test.js +64 -0
- package/dist/cli-entry-point.js +46 -0
- package/dist/gates.js +102 -39
- package/dist/init.js +241 -195
- package/dist/initiative-add-wu.js +2 -1
- package/dist/initiative-create.js +5 -8
- package/dist/initiative-edit.js +3 -3
- package/dist/initiative-list.js +2 -1
- package/dist/initiative-status.js +2 -1
- package/dist/wu-claim.js +297 -110
- package/dist/wu-cleanup.js +129 -57
- package/dist/wu-create.js +197 -122
- package/dist/wu-deps.js +2 -1
- package/dist/wu-done.js +46 -14
- package/dist/wu-edit.js +152 -61
- package/dist/wu-infer-lane.js +5 -4
- package/dist/wu-preflight.js +2 -1
- package/dist/wu-prune.js +12 -3
- package/dist/wu-repair.js +2 -1
- package/dist/wu-spawn.js +79 -159
- package/dist/wu-unlock-lane.js +6 -1
- package/dist/wu-validate.js +2 -1
- package/package.json +14 -14
- package/dist/gates.d.ts +0 -41
- package/dist/gates.d.ts.map +0 -1
- package/dist/gates.js.map +0 -1
- package/dist/initiative-add-wu.d.ts +0 -22
- package/dist/initiative-add-wu.d.ts.map +0 -1
- package/dist/initiative-add-wu.js.map +0 -1
- package/dist/initiative-create.d.ts +0 -28
- package/dist/initiative-create.d.ts.map +0 -1
- package/dist/initiative-create.js.map +0 -1
- package/dist/initiative-edit.d.ts +0 -34
- package/dist/initiative-edit.d.ts.map +0 -1
- package/dist/initiative-edit.js.map +0 -1
- package/dist/initiative-list.d.ts +0 -12
- package/dist/initiative-list.d.ts.map +0 -1
- package/dist/initiative-list.js.map +0 -1
- package/dist/initiative-status.d.ts +0 -11
- package/dist/initiative-status.d.ts.map +0 -1
- package/dist/initiative-status.js.map +0 -1
- package/dist/mem-checkpoint.d.ts +0 -16
- package/dist/mem-checkpoint.d.ts.map +0 -1
- package/dist/mem-checkpoint.js.map +0 -1
- package/dist/mem-cleanup.d.ts +0 -29
- package/dist/mem-cleanup.d.ts.map +0 -1
- package/dist/mem-cleanup.js.map +0 -1
- package/dist/mem-create.d.ts +0 -17
- package/dist/mem-create.d.ts.map +0 -1
- package/dist/mem-create.js.map +0 -1
- package/dist/mem-inbox.d.ts +0 -35
- package/dist/mem-inbox.d.ts.map +0 -1
- package/dist/mem-inbox.js.map +0 -1
- package/dist/mem-init.d.ts +0 -15
- package/dist/mem-init.d.ts.map +0 -1
- package/dist/mem-init.js.map +0 -1
- package/dist/mem-ready.d.ts +0 -16
- package/dist/mem-ready.d.ts.map +0 -1
- package/dist/mem-ready.js.map +0 -1
- package/dist/mem-signal.d.ts +0 -16
- package/dist/mem-signal.d.ts.map +0 -1
- package/dist/mem-signal.js.map +0 -1
- package/dist/mem-start.d.ts +0 -16
- package/dist/mem-start.d.ts.map +0 -1
- package/dist/mem-start.js.map +0 -1
- package/dist/mem-summarize.d.ts +0 -22
- package/dist/mem-summarize.d.ts.map +0 -1
- package/dist/mem-summarize.js.map +0 -1
- package/dist/mem-triage.d.ts +0 -22
- package/dist/mem-triage.d.ts.map +0 -1
- package/dist/mem-triage.js.map +0 -1
- package/dist/spawn-list.d.ts +0 -16
- package/dist/spawn-list.d.ts.map +0 -1
- package/dist/spawn-list.js.map +0 -1
- package/dist/wu-block.d.ts +0 -16
- package/dist/wu-block.d.ts.map +0 -1
- package/dist/wu-block.js.map +0 -1
- package/dist/wu-claim.d.ts +0 -32
- package/dist/wu-claim.d.ts.map +0 -1
- package/dist/wu-claim.js.map +0 -1
- package/dist/wu-cleanup.d.ts +0 -17
- package/dist/wu-cleanup.d.ts.map +0 -1
- package/dist/wu-cleanup.js.map +0 -1
- package/dist/wu-create.d.ts +0 -38
- package/dist/wu-create.d.ts.map +0 -1
- package/dist/wu-create.js.map +0 -1
- package/dist/wu-deps.d.ts +0 -13
- package/dist/wu-deps.d.ts.map +0 -1
- package/dist/wu-deps.js.map +0 -1
- package/dist/wu-done.d.ts +0 -153
- package/dist/wu-done.d.ts.map +0 -1
- package/dist/wu-done.js.map +0 -1
- package/dist/wu-edit.d.ts +0 -29
- package/dist/wu-edit.d.ts.map +0 -1
- package/dist/wu-edit.js.map +0 -1
- package/dist/wu-infer-lane.d.ts +0 -17
- package/dist/wu-infer-lane.d.ts.map +0 -1
- package/dist/wu-infer-lane.js.map +0 -1
- package/dist/wu-preflight.d.ts +0 -47
- package/dist/wu-preflight.d.ts.map +0 -1
- package/dist/wu-preflight.js.map +0 -1
- package/dist/wu-prune.d.ts +0 -16
- package/dist/wu-prune.d.ts.map +0 -1
- package/dist/wu-prune.js.map +0 -1
- package/dist/wu-repair.d.ts +0 -60
- package/dist/wu-repair.d.ts.map +0 -1
- package/dist/wu-repair.js.map +0 -1
- package/dist/wu-spawn-completion.d.ts +0 -10
- package/dist/wu-spawn.d.ts +0 -168
- package/dist/wu-spawn.d.ts.map +0 -1
- package/dist/wu-spawn.js.map +0 -1
- package/dist/wu-unblock.d.ts +0 -16
- package/dist/wu-unblock.d.ts.map +0 -1
- package/dist/wu-unblock.js.map +0 -1
- package/dist/wu-validate.d.ts +0 -16
- package/dist/wu-validate.d.ts.map +0 -1
- package/dist/wu-validate.js.map +0 -1
package/dist/wu-deps.js
CHANGED
|
@@ -114,6 +114,7 @@ function renderGraphJSON(graph, rootId, depth, direction) {
|
|
|
114
114
|
}
|
|
115
115
|
// Guard main() for testability (WU-1366)
|
|
116
116
|
import { fileURLToPath } from 'node:url';
|
|
117
|
+
import { runCLI } from './cli-entry-point.js';
|
|
117
118
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
118
|
-
main
|
|
119
|
+
runCLI(main);
|
|
119
120
|
}
|
package/dist/wu-done.js
CHANGED
|
@@ -147,7 +147,7 @@ async function validateClaimMetadataBeforeGates(id, worktreePath, yamlStatus) {
|
|
|
147
147
|
` pnpm wu:repair-claim --id ${id}\n\n` +
|
|
148
148
|
`After repair, retry:\n` +
|
|
149
149
|
` pnpm wu:done --id ${id}\n\n` +
|
|
150
|
-
`See:
|
|
150
|
+
`See: docs/04-operations/_frameworks/lumenflow/agent/onboarding/troubleshooting-wu-done.md for more recovery options.`);
|
|
151
151
|
}
|
|
152
152
|
export function printExposureWarnings(wu, options = {}) {
|
|
153
153
|
// Validate exposure
|
|
@@ -1793,7 +1793,8 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1793
1793
|
}
|
|
1794
1794
|
else {
|
|
1795
1795
|
die(`Worktree not found (${worktreePath || 'unknown'}). Gates must run in the lane worktree.\n` +
|
|
1796
|
-
`If the worktree was removed, recreate it and retry, or
|
|
1796
|
+
`If the worktree was removed, recreate it and retry, or rerun with --branch-only when the lane branch exists.\n` +
|
|
1797
|
+
`Use --skip-gates only with justification.`);
|
|
1797
1798
|
}
|
|
1798
1799
|
// Step 0.75: Run COS governance gates (WU-614, COS v1.3 §7)
|
|
1799
1800
|
if (!args.skipCosGates) {
|
|
@@ -1858,6 +1859,13 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1858
1859
|
* @param {string|null} params.derivedWorktree - Derived worktree path
|
|
1859
1860
|
* @param {string} params.STAMPS_DIR - Stamps directory path
|
|
1860
1861
|
*/
|
|
1862
|
+
export function computeBranchOnlyFallback({ isBranchOnly, branchOnlyRequested, worktreeExists, derivedWorktree, }) {
|
|
1863
|
+
const allowFallback = Boolean(branchOnlyRequested) && !isBranchOnly && !worktreeExists && Boolean(derivedWorktree);
|
|
1864
|
+
return {
|
|
1865
|
+
allowFallback,
|
|
1866
|
+
effectiveBranchOnly: isBranchOnly || allowFallback,
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1861
1869
|
function printStateHUD({ id, docMain, isBranchOnly, isDocsOnly, derivedWorktree, STAMPS_DIR }) {
|
|
1862
1870
|
const stampExists = existsSync(path.join(STAMPS_DIR, `${id}.done`)) ? 'yes' : 'no';
|
|
1863
1871
|
const yamlStatus = docMain.status || 'unknown';
|
|
@@ -1869,6 +1877,8 @@ function printStateHUD({ id, docMain, isBranchOnly, isDocsOnly, derivedWorktree,
|
|
|
1869
1877
|
}
|
|
1870
1878
|
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
1871
1879
|
async function main() {
|
|
1880
|
+
// Allow pre-push hook to recognize wu:done automation (WU-1030)
|
|
1881
|
+
process.env.LUMENFLOW_WU_TOOL = 'wu-done';
|
|
1872
1882
|
// Validate CLI arguments and WU ID format (extracted to wu-done-validators.mjs)
|
|
1873
1883
|
const { args, id } = validateInputs(process.argv);
|
|
1874
1884
|
// Detect workspace mode and calculate paths (WU-1215: extracted to validators module)
|
|
@@ -1876,25 +1886,39 @@ async function main() {
|
|
|
1876
1886
|
const { WU_PATH, STATUS_PATH, BACKLOG_PATH, STAMPS_DIR, docMain, isBranchOnly, derivedWorktree, docForValidation: initialDocForValidation, isDocsOnly, } = pathInfo;
|
|
1877
1887
|
// Capture main checkout path once. process.cwd() may drift later during recovery flows.
|
|
1878
1888
|
const mainCheckoutPath = process.cwd();
|
|
1889
|
+
// Resolve worktree path early so we can detect missing worktree before pre-flight checks
|
|
1890
|
+
const resolvedWorktreePath = derivedWorktree && !isBranchOnly
|
|
1891
|
+
? path.isAbsolute(derivedWorktree)
|
|
1892
|
+
? derivedWorktree
|
|
1893
|
+
: path.resolve(mainCheckoutPath, derivedWorktree)
|
|
1894
|
+
: null;
|
|
1895
|
+
const worktreeExists = resolvedWorktreePath ? existsSync(resolvedWorktreePath) : false;
|
|
1896
|
+
const { allowFallback: allowBranchOnlyFallback, effectiveBranchOnly } = computeBranchOnlyFallback({
|
|
1897
|
+
isBranchOnly,
|
|
1898
|
+
branchOnlyRequested: args.branchOnly,
|
|
1899
|
+
worktreeExists,
|
|
1900
|
+
derivedWorktree,
|
|
1901
|
+
});
|
|
1902
|
+
if (allowBranchOnlyFallback) {
|
|
1903
|
+
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Worktree missing (${resolvedWorktreePath}). Proceeding in branch-only mode because --branch-only was provided.`);
|
|
1904
|
+
}
|
|
1905
|
+
const effectiveDerivedWorktree = effectiveBranchOnly ? null : derivedWorktree;
|
|
1906
|
+
const effectiveWorktreePath = effectiveBranchOnly ? null : resolvedWorktreePath;
|
|
1879
1907
|
// Pre-flight checks (WU-1215: extracted to executePreFlightChecks function)
|
|
1880
1908
|
const preFlightResult = await executePreFlightChecks({
|
|
1881
1909
|
id,
|
|
1882
1910
|
args,
|
|
1883
|
-
isBranchOnly,
|
|
1911
|
+
isBranchOnly: effectiveBranchOnly,
|
|
1884
1912
|
isDocsOnly,
|
|
1885
1913
|
docMain,
|
|
1886
1914
|
docForValidation: initialDocForValidation,
|
|
1887
|
-
derivedWorktree,
|
|
1915
|
+
derivedWorktree: effectiveDerivedWorktree,
|
|
1888
1916
|
});
|
|
1889
1917
|
const title = preFlightResult.title;
|
|
1890
1918
|
// Note: docForValidation is returned but not used after pre-flight checks
|
|
1891
1919
|
// The metadata transaction uses docForUpdate instead
|
|
1892
1920
|
// Step 0: Run gates (WU-1215: extracted to executeGates function)
|
|
1893
|
-
const worktreePath =
|
|
1894
|
-
? path.isAbsolute(derivedWorktree)
|
|
1895
|
-
? derivedWorktree
|
|
1896
|
-
: path.resolve(mainCheckoutPath, derivedWorktree)
|
|
1897
|
-
: null;
|
|
1921
|
+
const worktreePath = effectiveWorktreePath;
|
|
1898
1922
|
// WU-1943: Check if any checkpoints exist for this WU session
|
|
1899
1923
|
// Warn (don't block) if no checkpoints - agent should have been checkpointing periodically
|
|
1900
1924
|
try {
|
|
@@ -1908,9 +1932,16 @@ async function main() {
|
|
|
1908
1932
|
catch {
|
|
1909
1933
|
// Non-blocking: checkpoint check failure should not block wu:done
|
|
1910
1934
|
}
|
|
1911
|
-
await executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath });
|
|
1935
|
+
await executeGates({ id, args, isBranchOnly: effectiveBranchOnly, isDocsOnly, worktreePath });
|
|
1912
1936
|
// Print State HUD for visibility (WU-1215: extracted to printStateHUD function)
|
|
1913
|
-
printStateHUD({
|
|
1937
|
+
printStateHUD({
|
|
1938
|
+
id,
|
|
1939
|
+
docMain,
|
|
1940
|
+
isBranchOnly: effectiveBranchOnly,
|
|
1941
|
+
isDocsOnly,
|
|
1942
|
+
derivedWorktree: effectiveDerivedWorktree,
|
|
1943
|
+
STAMPS_DIR,
|
|
1944
|
+
});
|
|
1914
1945
|
// Step 0.5: Pre-flight validation - run ALL pre-commit hooks BEFORE merge
|
|
1915
1946
|
// This prevents partial completion states where merge succeeds but commit fails
|
|
1916
1947
|
// Validates all 8 gates: secrets, file size, ESLint, Prettier, TypeScript, audit, architecture, tasks
|
|
@@ -1947,7 +1978,7 @@ async function main() {
|
|
|
1947
1978
|
validateStagedFiles,
|
|
1948
1979
|
};
|
|
1949
1980
|
try {
|
|
1950
|
-
if (
|
|
1981
|
+
if (effectiveBranchOnly) {
|
|
1951
1982
|
// Branch-Only mode: merge first, then update metadata on main
|
|
1952
1983
|
// NOTE: Branch-only still uses old rollback mechanism
|
|
1953
1984
|
const branchOnlyContext = {
|
|
@@ -2165,7 +2196,7 @@ async function detectChangedDocPaths(worktreePath, baseBranch) {
|
|
|
2165
2196
|
// Get files changed in this branch vs base
|
|
2166
2197
|
const diff = await git.raw(['diff', '--name-only', baseBranch]);
|
|
2167
2198
|
const changedFiles = diff.split('\n').filter(Boolean);
|
|
2168
|
-
// Filter to docs:
|
|
2199
|
+
// Filter to docs: docs/04-operations/_frameworks/lumenflow/agent/onboarding/, docs/, CLAUDE.md, README.md, *.md in root
|
|
2169
2200
|
const docPatterns = [
|
|
2170
2201
|
/^ai\/onboarding\//,
|
|
2171
2202
|
/^docs\//,
|
|
@@ -2183,6 +2214,7 @@ async function detectChangedDocPaths(worktreePath, baseBranch) {
|
|
|
2183
2214
|
// Guard main() execution for testability (WU-1366)
|
|
2184
2215
|
// When imported as a module for testing, main() should not auto-run
|
|
2185
2216
|
import { fileURLToPath } from 'node:url';
|
|
2217
|
+
import { runCLI } from './cli-entry-point.js';
|
|
2186
2218
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
2187
|
-
main
|
|
2219
|
+
runCLI(main);
|
|
2188
2220
|
}
|
package/dist/wu-edit.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* Part of WU-1274: Add wu:edit command for spec-only changes
|
|
27
27
|
* @see {@link tools/lib/micro-worktree.mjs} - Shared micro-worktree logic
|
|
28
28
|
*/
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
29
30
|
import { getGitForCwd, createGitForPath } from '@lumenflow/core/dist/git-adapter.js';
|
|
30
31
|
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
31
32
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -35,7 +36,9 @@ import { join, resolve } from 'node:path';
|
|
|
35
36
|
import { parseYAML, stringifyYAML, readWU } from '@lumenflow/core/dist/wu-yaml.js';
|
|
36
37
|
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
37
38
|
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
38
|
-
import { FILE_SYSTEM, EXIT_CODES, MICRO_WORKTREE_OPERATIONS, LOG_PREFIX, COMMIT_FORMATS, WU_STATUS, CLAIMED_MODES, getLaneBranch, PKG_MANAGER, SCRIPTS, PRETTIER_FLAGS, READINESS_UI,
|
|
39
|
+
import { FILE_SYSTEM, EXIT_CODES, MICRO_WORKTREE_OPERATIONS, LOG_PREFIX, COMMIT_FORMATS, WU_STATUS, CLAIMED_MODES, getLaneBranch, PKG_MANAGER, SCRIPTS, PRETTIER_FLAGS, READINESS_UI,
|
|
40
|
+
// WU-1039: Import exposure values for validation (Library-First, no magic strings)
|
|
41
|
+
WU_EXPOSURE_VALUES, } from '@lumenflow/core/dist/wu-constants.js';
|
|
39
42
|
// WU-1593: Use centralized validateWUIDFormat (DRY)
|
|
40
43
|
import { ensureOnMain, ensureMainUpToDate, validateWUIDFormat, } from '@lumenflow/core/dist/wu-helpers.js';
|
|
41
44
|
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
@@ -57,6 +60,86 @@ import { normalizeWUSchema } from '@lumenflow/core/dist/wu-schema-normalization.
|
|
|
57
60
|
import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
|
|
58
61
|
/* eslint-disable security/detect-object-injection */
|
|
59
62
|
const PREFIX = LOG_PREFIX.EDIT;
|
|
63
|
+
/**
|
|
64
|
+
* WU-1039: Validate which edits are allowed on done WUs
|
|
65
|
+
*
|
|
66
|
+
* Done WUs only allow metadata reassignment: initiative, phase, and exposure.
|
|
67
|
+
* All other edits are blocked to preserve WU immutability after completion.
|
|
68
|
+
*
|
|
69
|
+
* @param opts - Parsed CLI options
|
|
70
|
+
* @returns { valid: boolean, disallowedEdits: string[] }
|
|
71
|
+
*/
|
|
72
|
+
export function validateDoneWUEdits(opts) {
|
|
73
|
+
const disallowedEdits = [];
|
|
74
|
+
// Check for disallowed edits on done WUs
|
|
75
|
+
if (opts.specFile)
|
|
76
|
+
disallowedEdits.push('--spec-file');
|
|
77
|
+
if (opts.description)
|
|
78
|
+
disallowedEdits.push('--description');
|
|
79
|
+
if (opts.acceptance && Array.isArray(opts.acceptance) && opts.acceptance.length > 0) {
|
|
80
|
+
disallowedEdits.push('--acceptance');
|
|
81
|
+
}
|
|
82
|
+
if (opts.notes)
|
|
83
|
+
disallowedEdits.push('--notes');
|
|
84
|
+
if (opts.codePaths && Array.isArray(opts.codePaths) && opts.codePaths.length > 0) {
|
|
85
|
+
disallowedEdits.push('--code-paths');
|
|
86
|
+
}
|
|
87
|
+
if (opts.lane)
|
|
88
|
+
disallowedEdits.push('--lane');
|
|
89
|
+
if (opts.type)
|
|
90
|
+
disallowedEdits.push('--type');
|
|
91
|
+
if (opts.priority)
|
|
92
|
+
disallowedEdits.push('--priority');
|
|
93
|
+
if (opts.testPathsManual &&
|
|
94
|
+
Array.isArray(opts.testPathsManual) &&
|
|
95
|
+
opts.testPathsManual.length > 0) {
|
|
96
|
+
disallowedEdits.push('--test-paths-manual');
|
|
97
|
+
}
|
|
98
|
+
if (opts.testPathsUnit && Array.isArray(opts.testPathsUnit) && opts.testPathsUnit.length > 0) {
|
|
99
|
+
disallowedEdits.push('--test-paths-unit');
|
|
100
|
+
}
|
|
101
|
+
if (opts.testPathsE2e && Array.isArray(opts.testPathsE2e) && opts.testPathsE2e.length > 0) {
|
|
102
|
+
disallowedEdits.push('--test-paths-e2e');
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
valid: disallowedEdits.length === 0,
|
|
106
|
+
disallowedEdits,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* WU-1039: Validate exposure value against schema
|
|
111
|
+
*
|
|
112
|
+
* Uses WU_EXPOSURE_VALUES from core constants (Library-First, no magic strings).
|
|
113
|
+
*
|
|
114
|
+
* @param exposure - Exposure value to validate
|
|
115
|
+
* @returns { valid: boolean, error?: string }
|
|
116
|
+
*/
|
|
117
|
+
export function validateExposureValue(exposure) {
|
|
118
|
+
// WU_EXPOSURE_VALUES is readonly array, need to cast for includes check
|
|
119
|
+
const validValues = WU_EXPOSURE_VALUES;
|
|
120
|
+
if (!validValues.includes(exposure)) {
|
|
121
|
+
return {
|
|
122
|
+
valid: false,
|
|
123
|
+
error: `Invalid exposure value: "${exposure}"\n\nValid values: ${WU_EXPOSURE_VALUES.join(', ')}`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return { valid: true };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* WU-1039: Apply exposure edit to WU object
|
|
130
|
+
*
|
|
131
|
+
* Returns a new WU object with updated exposure (immutable pattern).
|
|
132
|
+
*
|
|
133
|
+
* @param wu - Original WU object
|
|
134
|
+
* @param exposure - New exposure value
|
|
135
|
+
* @returns Updated WU object (does not mutate original)
|
|
136
|
+
*/
|
|
137
|
+
export function applyExposureEdit(wu, exposure) {
|
|
138
|
+
return {
|
|
139
|
+
...wu,
|
|
140
|
+
exposure,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
60
143
|
/**
|
|
61
144
|
* Custom options for wu-edit (not in shared WU_OPTIONS)
|
|
62
145
|
*/
|
|
@@ -244,6 +327,8 @@ function parseArgs() {
|
|
|
244
327
|
// WU-2564: Add blocked_by and dependencies
|
|
245
328
|
EDIT_OPTIONS.blockedBy,
|
|
246
329
|
EDIT_OPTIONS.addDep,
|
|
330
|
+
// WU-1039: Add exposure for done WU metadata updates
|
|
331
|
+
WU_OPTIONS.exposure,
|
|
247
332
|
],
|
|
248
333
|
required: ['id'],
|
|
249
334
|
allowPositionalId: true,
|
|
@@ -629,6 +714,14 @@ function applyEdits(wu, opts) {
|
|
|
629
714
|
.filter(Boolean);
|
|
630
715
|
updated.dependencies = mergeArrayField(wu.dependencies, depIds, opts.append);
|
|
631
716
|
}
|
|
717
|
+
// WU-1039: Handle --exposure flag with validation
|
|
718
|
+
if (opts.exposure) {
|
|
719
|
+
const exposureResult = validateExposureValue(opts.exposure);
|
|
720
|
+
if (!exposureResult.valid) {
|
|
721
|
+
die(exposureResult.error);
|
|
722
|
+
}
|
|
723
|
+
updated.exposure = opts.exposure;
|
|
724
|
+
}
|
|
632
725
|
return updated;
|
|
633
726
|
}
|
|
634
727
|
/**
|
|
@@ -642,38 +735,18 @@ async function main() {
|
|
|
642
735
|
// Validate inputs
|
|
643
736
|
validateWUIDFormat(id);
|
|
644
737
|
const { wu: originalWU, editMode, isDone } = validateWUEditable(id);
|
|
645
|
-
// WU-
|
|
738
|
+
// WU-1039: Done WUs allow initiative/phase/exposure edits only (metadata reassignment)
|
|
739
|
+
// Uses validateDoneWUEdits (DRY - centralized validation logic)
|
|
646
740
|
if (isDone) {
|
|
647
|
-
const
|
|
648
|
-
if (
|
|
649
|
-
disallowedEdits.push('--spec-file');
|
|
650
|
-
if (opts.description)
|
|
651
|
-
disallowedEdits.push('--description');
|
|
652
|
-
if (opts.acceptance && opts.acceptance.length > 0)
|
|
653
|
-
disallowedEdits.push('--acceptance');
|
|
654
|
-
if (opts.notes)
|
|
655
|
-
disallowedEdits.push('--notes');
|
|
656
|
-
if (opts.codePaths && opts.codePaths.length > 0)
|
|
657
|
-
disallowedEdits.push('--code-paths');
|
|
658
|
-
if (opts.lane)
|
|
659
|
-
disallowedEdits.push('--lane');
|
|
660
|
-
if (opts.type)
|
|
661
|
-
disallowedEdits.push('--type');
|
|
662
|
-
if (opts.priority)
|
|
663
|
-
disallowedEdits.push('--priority');
|
|
664
|
-
if (opts.testPathsManual && opts.testPathsManual.length > 0)
|
|
665
|
-
disallowedEdits.push('--test-paths-manual');
|
|
666
|
-
if (opts.testPathsUnit && opts.testPathsUnit.length > 0)
|
|
667
|
-
disallowedEdits.push('--test-paths-unit');
|
|
668
|
-
if (opts.testPathsE2e && opts.testPathsE2e.length > 0)
|
|
669
|
-
disallowedEdits.push('--test-paths-e2e');
|
|
670
|
-
if (disallowedEdits.length > 0) {
|
|
741
|
+
const doneValidation = validateDoneWUEdits(opts);
|
|
742
|
+
if (!doneValidation.valid) {
|
|
671
743
|
die(`Cannot edit WU ${id}: WU is done/immutable.\n\n` +
|
|
672
|
-
`Completed WUs only allow initiative/phase reassignment.\n` +
|
|
673
|
-
`Disallowed edits: ${disallowedEdits.join(', ')}\n\n` +
|
|
744
|
+
`Completed WUs only allow initiative/phase/exposure reassignment.\n` +
|
|
745
|
+
`Disallowed edits: ${doneValidation.disallowedEdits.join(', ')}\n\n` +
|
|
674
746
|
`Allowed for done WUs:\n` +
|
|
675
747
|
` --initiative <initId> Reassign to different initiative\n` +
|
|
676
|
-
` --phase <number> Update phase within initiative`
|
|
748
|
+
` --phase <number> Update phase within initiative\n` +
|
|
749
|
+
` --exposure <type> Update exposure level`);
|
|
677
750
|
}
|
|
678
751
|
}
|
|
679
752
|
// Check we have something to edit
|
|
@@ -698,7 +771,9 @@ async function main() {
|
|
|
698
771
|
opts.phase ||
|
|
699
772
|
// WU-2564: Add blocked_by and add_dep to hasEdits check
|
|
700
773
|
opts.blockedBy ||
|
|
701
|
-
opts.addDep
|
|
774
|
+
opts.addDep ||
|
|
775
|
+
// WU-1039: Add exposure to hasEdits check
|
|
776
|
+
opts.exposure;
|
|
702
777
|
if (!hasEdits) {
|
|
703
778
|
die('No edits specified.\n\n' +
|
|
704
779
|
'Provide one of:\n' +
|
|
@@ -716,7 +791,8 @@ async function main() {
|
|
|
716
791
|
' --test-paths-unit <path> Add unit test paths (repeatable; use --append to add)\n' +
|
|
717
792
|
' --test-paths-e2e <path> Add e2e test paths (repeatable; use --append to add)\n' +
|
|
718
793
|
' --blocked-by <wuIds> WU IDs that block this WU (comma-separated; use --append to add)\n' +
|
|
719
|
-
' --add-dep <wuIds> Add WU IDs to dependencies (comma-separated; use --append to add)'
|
|
794
|
+
' --add-dep <wuIds> Add WU IDs to dependencies (comma-separated; use --append to add)\n' +
|
|
795
|
+
' --exposure <type> Update exposure level (ui, api, backend-only, documentation)');
|
|
720
796
|
}
|
|
721
797
|
// Apply edits to get updated WU
|
|
722
798
|
const updatedWU = applyEdits(originalWU, opts);
|
|
@@ -814,39 +890,54 @@ async function main() {
|
|
|
814
890
|
const oldInitiative = originalWU.initiative;
|
|
815
891
|
const newInitiative = opts.initiative;
|
|
816
892
|
const initiativeChanged = newInitiative && newInitiative !== oldInitiative;
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
893
|
+
const previousWuTool = process.env.LUMENFLOW_WU_TOOL;
|
|
894
|
+
process.env.LUMENFLOW_WU_TOOL = MICRO_WORKTREE_OPERATIONS.WU_EDIT;
|
|
895
|
+
try {
|
|
896
|
+
await withMicroWorktree({
|
|
897
|
+
operation: MICRO_WORKTREE_OPERATIONS.WU_EDIT,
|
|
898
|
+
id: id,
|
|
899
|
+
logPrefix: PREFIX,
|
|
900
|
+
execute: async ({ worktreePath }) => {
|
|
901
|
+
const files = [WU_PATHS.WU(id)];
|
|
902
|
+
// Write updated WU to micro-worktree (WU-1750: use normalized data)
|
|
903
|
+
const wuPath = join(worktreePath, WU_PATHS.WU(id));
|
|
904
|
+
// WU-1442: Normalize dates before dumping to prevent ISO timestamp corruption
|
|
905
|
+
normalizeWUDates(normalizedWU);
|
|
906
|
+
// Emergency fix Session 2: Use centralized stringifyYAML helper
|
|
907
|
+
const yamlContent = stringifyYAML(normalizedWU);
|
|
908
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes WU files
|
|
909
|
+
writeFileSync(wuPath, yamlContent, { encoding: FILE_SYSTEM.ENCODING });
|
|
910
|
+
console.log(`${PREFIX} ✅ Updated ${id}.yaml in micro-worktree`);
|
|
911
|
+
// WU-1929: Handle bidirectional initiative updates
|
|
912
|
+
if (initiativeChanged) {
|
|
913
|
+
const initiativeFiles = updateInitiativeWusArrays(worktreePath, id, oldInitiative, newInitiative);
|
|
914
|
+
files.push(...initiativeFiles);
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
commitMessage: COMMIT_FORMATS.EDIT(id),
|
|
918
|
+
files,
|
|
919
|
+
};
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
finally {
|
|
924
|
+
if (previousWuTool === undefined) {
|
|
925
|
+
delete process.env.LUMENFLOW_WU_TOOL;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
process.env.LUMENFLOW_WU_TOOL = previousWuTool;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
843
931
|
console.log(`${PREFIX} ✅ Successfully edited ${id}`);
|
|
844
932
|
console.log(`${PREFIX} Changes pushed to origin/main`);
|
|
845
933
|
// WU-1620: Display readiness summary
|
|
846
934
|
displayReadinessSummary(id);
|
|
847
935
|
}
|
|
848
936
|
}
|
|
849
|
-
main()
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
});
|
|
937
|
+
// Guard main() execution for testability (WU-1366)
|
|
938
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
939
|
+
main().catch((err) => {
|
|
940
|
+
console.error(`${PREFIX} ❌ ${err.message}`);
|
|
941
|
+
process.exit(EXIT_CODES.ERROR);
|
|
942
|
+
});
|
|
943
|
+
}
|
package/dist/wu-infer-lane.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { readFileSync, existsSync } from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
|
-
import
|
|
19
|
+
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';
|
|
@@ -81,7 +81,7 @@ function loadWuYaml(id) {
|
|
|
81
81
|
` 2. Ensure you have read access to the repository`);
|
|
82
82
|
}
|
|
83
83
|
try {
|
|
84
|
-
const doc =
|
|
84
|
+
const doc = parseYAML(content);
|
|
85
85
|
return doc;
|
|
86
86
|
}
|
|
87
87
|
catch (err) {
|
|
@@ -93,7 +93,7 @@ function loadWuYaml(id) {
|
|
|
93
93
|
` 2. Fix YAML errors manually and retry`);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
function main() {
|
|
96
|
+
async function main() {
|
|
97
97
|
const args = parseArgs(process.argv);
|
|
98
98
|
let codePaths = [];
|
|
99
99
|
let description = '';
|
|
@@ -130,6 +130,7 @@ function main() {
|
|
|
130
130
|
}
|
|
131
131
|
// Guard main() for testability (WU-1366)
|
|
132
132
|
import { fileURLToPath } from 'node:url';
|
|
133
|
+
import { runCLI } from './cli-entry-point.js';
|
|
133
134
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
134
|
-
main
|
|
135
|
+
runCLI(main);
|
|
135
136
|
}
|
package/dist/wu-preflight.js
CHANGED
|
@@ -160,8 +160,9 @@ async function main() {
|
|
|
160
160
|
process.exit(EXIT_CODES.SUCCESS);
|
|
161
161
|
}
|
|
162
162
|
// Guard main() for testability
|
|
163
|
+
import { runCLI } from './cli-entry-point.js';
|
|
163
164
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
164
|
-
main
|
|
165
|
+
runCLI(main);
|
|
165
166
|
}
|
|
166
167
|
// Export for testing
|
|
167
168
|
export { parseArgs, detectWorktreePath };
|
package/dist/wu-prune.js
CHANGED
|
@@ -19,8 +19,8 @@ import { readWUYaml, validateBranchName, extractWUFromBranch, } from '@lumenflow
|
|
|
19
19
|
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
20
20
|
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
21
21
|
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
22
|
-
import { detectOrphanWorktrees, removeOrphanDirectory, } from '@lumenflow/core/dist/orphan-detector.js';
|
|
23
|
-
import { BRANCHES, WU_STATUS, CLI_FLAGS, EXIT_CODES, STRING_LITERALS, EMOJI, LOG_PREFIX, } from '@lumenflow/core/dist/wu-constants.js';
|
|
22
|
+
import { detectOrphanWorktrees, detectMissingTrackedWorktrees, removeOrphanDirectory, } from '@lumenflow/core/dist/orphan-detector.js';
|
|
23
|
+
import { BRANCHES, WU_STATUS, CLI_FLAGS, EXIT_CODES, STRING_LITERALS, EMOJI, LOG_PREFIX, WORKTREE_WARNINGS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
24
24
|
function parseArgs(argv) {
|
|
25
25
|
const args = { dryRun: true }; // Default to dry-run for safety
|
|
26
26
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -145,6 +145,14 @@ This tool:
|
|
|
145
145
|
}
|
|
146
146
|
console.log(`${PREFIX} Worktree Hygiene Check`);
|
|
147
147
|
console.log(`${PREFIX} =====================\n`);
|
|
148
|
+
const missingTracked = await detectMissingTrackedWorktrees(process.cwd());
|
|
149
|
+
if (missingTracked.length > 0) {
|
|
150
|
+
console.warn(`${PREFIX} ${EMOJI.WARNING} ${WORKTREE_WARNINGS.MISSING_TRACKED_HEADER}`);
|
|
151
|
+
for (const missingPath of missingTracked) {
|
|
152
|
+
console.warn(`${PREFIX} ${WORKTREE_WARNINGS.MISSING_TRACKED_LINE(missingPath)}`);
|
|
153
|
+
}
|
|
154
|
+
console.warn('');
|
|
155
|
+
}
|
|
148
156
|
if (args.dryRun) {
|
|
149
157
|
console.log(`${PREFIX} ${EMOJI.INFO} DRY-RUN MODE (use --execute to apply changes)\n`);
|
|
150
158
|
}
|
|
@@ -254,6 +262,7 @@ This tool:
|
|
|
254
262
|
}
|
|
255
263
|
// Guard main() for testability (WU-1366)
|
|
256
264
|
import { fileURLToPath } from 'node:url';
|
|
265
|
+
import { runCLI } from './cli-entry-point.js';
|
|
257
266
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
258
|
-
main
|
|
267
|
+
runCLI(main);
|
|
259
268
|
}
|
package/dist/wu-repair.js
CHANGED
|
@@ -219,8 +219,9 @@ async function main() {
|
|
|
219
219
|
}
|
|
220
220
|
// Guard main() for testability (WU-1366)
|
|
221
221
|
import { fileURLToPath } from 'node:url';
|
|
222
|
+
import { runCLI } from './cli-entry-point.js';
|
|
222
223
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
223
|
-
main
|
|
224
|
+
runCLI(main);
|
|
224
225
|
}
|
|
225
226
|
// Export for testing
|
|
226
227
|
export { normaliseWUId, isValidWUId };
|