@lumenflow/cli 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/init-config-lanes.test.js +131 -0
- package/dist/__tests__/init-docs-structure.test.js +119 -0
- package/dist/__tests__/init-lane-inference.test.js +125 -0
- package/dist/__tests__/init-onboarding-docs.test.js +132 -0
- package/dist/__tests__/init-quick-ref.test.js +145 -0
- package/dist/__tests__/init-scripts.test.js +96 -0
- package/dist/__tests__/init-template-portability.test.js +97 -0
- package/dist/__tests__/init.test.js +7 -2
- package/dist/__tests__/initiative-add-wu.test.js +420 -0
- package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
- package/dist/__tests__/initiative-remove-wu.test.js +458 -0
- package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
- package/dist/__tests__/path-centralization-cli.test.js +234 -0
- package/dist/__tests__/plan-create.test.js +126 -0
- package/dist/__tests__/plan-edit.test.js +157 -0
- package/dist/__tests__/plan-link.test.js +239 -0
- package/dist/__tests__/plan-promote.test.js +181 -0
- package/dist/__tests__/wu-create-strict.test.js +118 -0
- package/dist/__tests__/wu-edit-strict.test.js +109 -0
- package/dist/__tests__/wu-validate-strict.test.js +113 -0
- package/dist/flow-bottlenecks.js +4 -2
- package/dist/gates.js +22 -0
- package/dist/init.js +580 -87
- package/dist/initiative-add-wu.js +112 -16
- package/dist/initiative-remove-wu.js +248 -0
- package/dist/onboarding-smoke-test.js +400 -0
- package/dist/plan-create.js +199 -0
- package/dist/plan-edit.js +235 -0
- package/dist/plan-link.js +233 -0
- package/dist/plan-promote.js +231 -0
- package/dist/wu-block.js +16 -5
- package/dist/wu-claim.js +15 -9
- package/dist/wu-create.js +50 -2
- package/dist/wu-deps.js +3 -1
- package/dist/wu-done.js +14 -5
- package/dist/wu-edit.js +35 -0
- package/dist/wu-spawn.js +8 -0
- package/dist/wu-unblock.js +34 -2
- package/dist/wu-validate.js +25 -17
- package/package.json +10 -6
- package/templates/core/AGENTS.md.template +2 -2
- package/dist/__tests__/init-plan.test.js +0 -340
- package/dist/agent-issues-query.d.ts +0 -16
- package/dist/agent-log-issue.d.ts +0 -10
- package/dist/agent-session-end.d.ts +0 -10
- package/dist/agent-session.d.ts +0 -10
- package/dist/backlog-prune.d.ts +0 -84
- package/dist/cli-entry-point.d.ts +0 -8
- package/dist/deps-add.d.ts +0 -91
- package/dist/deps-remove.d.ts +0 -17
- package/dist/docs-sync.d.ts +0 -50
- package/dist/file-delete.d.ts +0 -84
- package/dist/file-edit.d.ts +0 -82
- package/dist/file-read.d.ts +0 -92
- package/dist/file-write.d.ts +0 -90
- package/dist/flow-bottlenecks.d.ts +0 -16
- package/dist/flow-report.d.ts +0 -16
- package/dist/gates.d.ts +0 -94
- package/dist/git-branch.d.ts +0 -65
- package/dist/git-diff.d.ts +0 -58
- package/dist/git-log.d.ts +0 -69
- package/dist/git-status.d.ts +0 -58
- package/dist/guard-locked.d.ts +0 -62
- package/dist/guard-main-branch.d.ts +0 -50
- package/dist/guard-worktree-commit.d.ts +0 -59
- package/dist/index.d.ts +0 -10
- package/dist/init-plan.d.ts +0 -80
- package/dist/init-plan.js +0 -337
- package/dist/init.d.ts +0 -46
- package/dist/initiative-add-wu.d.ts +0 -22
- package/dist/initiative-bulk-assign-wus.d.ts +0 -16
- package/dist/initiative-create.d.ts +0 -28
- package/dist/initiative-edit.d.ts +0 -34
- package/dist/initiative-list.d.ts +0 -12
- package/dist/initiative-status.d.ts +0 -11
- package/dist/lumenflow-upgrade.d.ts +0 -103
- package/dist/mem-checkpoint.d.ts +0 -16
- package/dist/mem-cleanup.d.ts +0 -29
- package/dist/mem-create.d.ts +0 -17
- package/dist/mem-export.d.ts +0 -10
- package/dist/mem-inbox.d.ts +0 -35
- package/dist/mem-init.d.ts +0 -15
- package/dist/mem-ready.d.ts +0 -16
- package/dist/mem-signal.d.ts +0 -16
- package/dist/mem-start.d.ts +0 -16
- package/dist/mem-summarize.d.ts +0 -22
- package/dist/mem-triage.d.ts +0 -22
- package/dist/metrics-cli.d.ts +0 -90
- package/dist/metrics-snapshot.d.ts +0 -18
- package/dist/orchestrate-init-status.d.ts +0 -11
- package/dist/orchestrate-initiative.d.ts +0 -12
- package/dist/orchestrate-monitor.d.ts +0 -11
- package/dist/release.d.ts +0 -117
- package/dist/rotate-progress.d.ts +0 -48
- package/dist/session-coordinator.d.ts +0 -74
- package/dist/spawn-list.d.ts +0 -16
- package/dist/state-bootstrap.d.ts +0 -92
- package/dist/sync-templates.d.ts +0 -52
- package/dist/trace-gen.d.ts +0 -84
- package/dist/validate-agent-skills.d.ts +0 -50
- package/dist/validate-agent-sync.d.ts +0 -36
- package/dist/validate-backlog-sync.d.ts +0 -37
- package/dist/validate-skills-spec.d.ts +0 -40
- package/dist/validate.d.ts +0 -60
- package/dist/wu-block.d.ts +0 -16
- package/dist/wu-claim.d.ts +0 -74
- package/dist/wu-cleanup.d.ts +0 -35
- package/dist/wu-create.d.ts +0 -69
- package/dist/wu-delete.d.ts +0 -21
- package/dist/wu-deps.d.ts +0 -13
- package/dist/wu-done.d.ts +0 -225
- package/dist/wu-edit.d.ts +0 -63
- package/dist/wu-infer-lane.d.ts +0 -17
- package/dist/wu-preflight.d.ts +0 -47
- package/dist/wu-prune.d.ts +0 -16
- package/dist/wu-recover.d.ts +0 -37
- package/dist/wu-release.d.ts +0 -19
- package/dist/wu-repair.d.ts +0 -60
- package/dist/wu-spawn-completion.d.ts +0 -10
- package/dist/wu-spawn.d.ts +0 -192
- package/dist/wu-status.d.ts +0 -25
- package/dist/wu-unblock.d.ts +0 -16
- package/dist/wu-unlock-lane.d.ts +0 -19
- package/dist/wu-validate.d.ts +0 -16
package/dist/wu-block.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console -- CLI tool requires console output */
|
|
2
3
|
/**
|
|
3
4
|
* WU Block Helper
|
|
4
5
|
*
|
|
@@ -32,6 +33,8 @@ import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
|
32
33
|
import { WUStateStore } from '@lumenflow/core/dist/wu-state-store.js';
|
|
33
34
|
// WU-1603: Atomic lane locking - release lock when WU is blocked
|
|
34
35
|
import { releaseLaneLock } from '@lumenflow/core/dist/lane-lock.js';
|
|
36
|
+
// WU-1325: Import lock policy getter to determine release behavior
|
|
37
|
+
import { getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
|
|
35
38
|
// ensureOnMain() moved to wu-helpers.ts (WU-1256)
|
|
36
39
|
// ensureStaged() moved to git-staged-validator.ts (WU-1341)
|
|
37
40
|
// defaultWorktreeFrom() moved to wu-paths.ts (WU-1341)
|
|
@@ -215,15 +218,23 @@ async function main() {
|
|
|
215
218
|
await getGitForCwd().push(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
216
219
|
}
|
|
217
220
|
await handleWorktreeRemoval(args, doc);
|
|
218
|
-
// WU-
|
|
219
|
-
//
|
|
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
|
|
224
|
-
if (
|
|
225
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
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
|
|
1633
|
+
` 1. Update ${specConfig.directories.wuDir}/${id}.yaml\n` +
|
|
1625
1634
|
` 2. Fill description with Context/Problem/Solution\n` +
|
|
1626
1635
|
` 3. Replace ${PLACEHOLDER_SENTINEL} text with specific criteria\n` +
|
|
1627
1636
|
` 4. List all modified files in code_paths\n` +
|
package/dist/wu-edit.js
CHANGED
|
@@ -57,6 +57,8 @@ import { readInitiative, writeInitiative } from '@lumenflow/initiatives/dist/ini
|
|
|
57
57
|
import { normalizeWUSchema } from '@lumenflow/core/dist/wu-schema-normalization.js';
|
|
58
58
|
// WU-2253: Import WU spec linter for acceptance/code_paths validation
|
|
59
59
|
import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
|
|
60
|
+
// WU-1329: Import path existence validators for strict validation
|
|
61
|
+
import { validateCodePathsExistence, validateTestPathsExistence, } from '@lumenflow/core/dist/wu-preflight-validators.js';
|
|
60
62
|
/* eslint-disable security/detect-object-injection */
|
|
61
63
|
const PREFIX = LOG_PREFIX.EDIT;
|
|
62
64
|
/**
|
|
@@ -377,6 +379,8 @@ function parseArgs() {
|
|
|
377
379
|
EDIT_OPTIONS.replaceDependencies,
|
|
378
380
|
// WU-1039: Add exposure for done WU metadata updates
|
|
379
381
|
WU_OPTIONS.exposure,
|
|
382
|
+
// WU-1329: Strict validation is default, --no-strict bypasses
|
|
383
|
+
WU_OPTIONS.noStrict,
|
|
380
384
|
],
|
|
381
385
|
required: ['id'],
|
|
382
386
|
allowPositionalId: true,
|
|
@@ -921,6 +925,37 @@ async function main() {
|
|
|
921
925
|
// WU-1750: CRITICAL - Use transformed data for all subsequent operations
|
|
922
926
|
// This ensures embedded newlines are normalized before YAML output
|
|
923
927
|
const normalizedWU = validationResult.data;
|
|
928
|
+
// WU-1329: Strict validation of path existence (default behavior)
|
|
929
|
+
// --no-strict bypasses these checks
|
|
930
|
+
const strict = !opts.noStrict;
|
|
931
|
+
if (!strict) {
|
|
932
|
+
console.warn(`${PREFIX} WARNING: strict validation bypassed (--no-strict). Path existence checks skipped.`);
|
|
933
|
+
}
|
|
934
|
+
if (strict) {
|
|
935
|
+
const rootDir = process.cwd();
|
|
936
|
+
const strictErrors = [];
|
|
937
|
+
// Validate code_paths exist
|
|
938
|
+
if (normalizedWU.code_paths && normalizedWU.code_paths.length > 0) {
|
|
939
|
+
const codePathsResult = validateCodePathsExistence(normalizedWU.code_paths, rootDir);
|
|
940
|
+
if (!codePathsResult.valid) {
|
|
941
|
+
strictErrors.push(...codePathsResult.errors);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
// Validate test_paths exist (unit, e2e - not manual)
|
|
945
|
+
if (normalizedWU.tests) {
|
|
946
|
+
const testPathsResult = validateTestPathsExistence(normalizedWU.tests, rootDir);
|
|
947
|
+
if (!testPathsResult.valid) {
|
|
948
|
+
strictErrors.push(...testPathsResult.errors);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (strictErrors.length > 0) {
|
|
952
|
+
const errorList = strictErrors.map((e) => ` • ${e}`).join('\n');
|
|
953
|
+
die(`${PREFIX} ❌ Strict validation failed:\n\n${errorList}\n\n` +
|
|
954
|
+
`Options:\n` +
|
|
955
|
+
` 1. Fix the paths in the WU spec to match actual files\n` +
|
|
956
|
+
` 2. Use --no-strict to bypass path existence checks (not recommended)`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
924
959
|
// Validate lane format if present (WU-923: block parent-only lanes with taxonomy)
|
|
925
960
|
if (normalizedWU.lane) {
|
|
926
961
|
validateLaneFormat(normalizedWU.lane);
|
package/dist/wu-spawn.js
CHANGED
|
@@ -36,7 +36,9 @@ import { parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
|
|
|
36
36
|
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
37
37
|
import { WU_STATUS, PATTERNS, FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
38
38
|
// WU-1603: Check lane lock status before spawning
|
|
39
|
+
// WU-1325: Import lock policy getter for lane availability check
|
|
39
40
|
import { checkLaneLock } from '@lumenflow/core/dist/lane-lock.js';
|
|
41
|
+
import { getLockPolicyForLane } from '@lumenflow/core/dist/lane-checker.js';
|
|
40
42
|
import { minimatch } from 'minimatch';
|
|
41
43
|
// WU-2252: Import invariants loader for spawn output injection
|
|
42
44
|
import { loadInvariants, INVARIANT_TYPES } from '@lumenflow/core/dist/invariants-runner.js';
|
|
@@ -1146,11 +1148,17 @@ ${SPAWN_END_SENTINEL}
|
|
|
1146
1148
|
}
|
|
1147
1149
|
/**
|
|
1148
1150
|
* WU-1603: Check if a lane is currently occupied by another WU
|
|
1151
|
+
* WU-1325: Now considers lock_policy - lanes with policy=none are never occupied
|
|
1149
1152
|
*
|
|
1150
1153
|
* @param {string} lane - Lane name (e.g., "Operations: Tooling")
|
|
1151
1154
|
* @returns {import('@lumenflow/core/dist/lane-lock.js').LockMetadata|null} Lock metadata if occupied, null otherwise
|
|
1152
1155
|
*/
|
|
1153
1156
|
export function checkLaneOccupation(lane) {
|
|
1157
|
+
// WU-1325: Lanes with lock_policy=none never report as occupied
|
|
1158
|
+
const lockPolicy = getLockPolicyForLane(lane);
|
|
1159
|
+
if (lockPolicy === 'none') {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1154
1162
|
const lockStatus = checkLaneLock(lane);
|
|
1155
1163
|
if (lockStatus.locked && lockStatus.metadata) {
|
|
1156
1164
|
return lockStatus.metadata;
|
package/dist/wu-unblock.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 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
|
-
|
|
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)
|
package/dist/wu-validate.js
CHANGED
|
@@ -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
|
-
*
|
|
7
|
-
*
|
|
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
|
|
11
|
-
* pnpm wu:validate --all # Validate all WUs
|
|
12
|
-
* pnpm wu:validate --all --strict
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Command-line interface for LumenFlow workflow framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -75,6 +75,10 @@
|
|
|
75
75
|
"initiative-add-wu": "./dist/initiative-add-wu.js",
|
|
76
76
|
"initiative-plan": "./dist/initiative-plan.js",
|
|
77
77
|
"init-plan": "./dist/init-plan.js",
|
|
78
|
+
"plan-create": "./dist/plan-create.js",
|
|
79
|
+
"plan-link": "./dist/plan-link.js",
|
|
80
|
+
"plan-edit": "./dist/plan-edit.js",
|
|
81
|
+
"plan-promote": "./dist/plan-promote.js",
|
|
78
82
|
"agent-session": "./dist/agent-session.js",
|
|
79
83
|
"agent-session-end": "./dist/agent-session-end.js",
|
|
80
84
|
"agent-log-issue": "./dist/agent-log-issue.js",
|
|
@@ -144,11 +148,11 @@
|
|
|
144
148
|
"pretty-ms": "^9.2.0",
|
|
145
149
|
"simple-git": "^3.30.0",
|
|
146
150
|
"yaml": "^2.8.2",
|
|
147
|
-
"@lumenflow/core": "2.
|
|
148
|
-
"@lumenflow/
|
|
149
|
-
"@lumenflow/memory": "2.
|
|
150
|
-
"@lumenflow/
|
|
151
|
-
"@lumenflow/
|
|
151
|
+
"@lumenflow/core": "2.5.0",
|
|
152
|
+
"@lumenflow/initiatives": "2.5.0",
|
|
153
|
+
"@lumenflow/memory": "2.5.0",
|
|
154
|
+
"@lumenflow/agent": "2.5.0",
|
|
155
|
+
"@lumenflow/metrics": "2.5.0"
|
|
152
156
|
},
|
|
153
157
|
"devDependencies": {
|
|
154
158
|
"@vitest/coverage-v8": "^4.0.17",
|
|
@@ -17,11 +17,11 @@ cd worktrees/<lane>-wu-xxxx
|
|
|
17
17
|
pnpm gates
|
|
18
18
|
|
|
19
19
|
# 3. Complete (ALWAYS run this!)
|
|
20
|
-
cd
|
|
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](
|
|
24
|
+
> **Complete CLI reference:** See [quick-ref-commands.md]({{QUICK_REF_LINK}})
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|