@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.
- package/README.md +11 -8
- 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 +207 -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__/templates-sync.test.js +219 -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 +670 -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/orchestrate-init-status.js +37 -9
- package/dist/orchestrate-initiative.js +10 -4
- 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/sync-templates.js +137 -5
- 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-prep.js +131 -8
- package/dist/wu-spawn.js +14 -1
- package/dist/wu-unblock.js +34 -2
- package/dist/wu-validate.js +25 -17
- package/package.json +11 -7
- package/templates/core/.lumenflow/constraints.md.template +61 -3
- package/templates/core/AGENTS.md.template +2 -2
- package/templates/core/LUMENFLOW.md.template +85 -23
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
- package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
- package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
- package/templates/core/ai/onboarding/release-process.md.template +8 -2
- package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
- package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
- package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
- package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
- package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
- package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
- 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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console -- CLI tool requires console output */
|
|
3
|
+
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
4
|
+
/**
|
|
5
|
+
* Plan Promote Command (WU-1313)
|
|
6
|
+
*
|
|
7
|
+
* Promotes a plan from draft to approved status.
|
|
8
|
+
* Validates that required sections are complete before approving.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* pnpm plan:promote --id WU-1313
|
|
12
|
+
* pnpm plan:promote --id INIT-001 --force # Skip validation
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* - Validates plan completeness (non-empty required sections)
|
|
16
|
+
* - Adds approved status and timestamp
|
|
17
|
+
* - Uses micro-worktree isolation for atomic commits
|
|
18
|
+
* - Idempotent: no error if already approved
|
|
19
|
+
*
|
|
20
|
+
* Context: WU-1313 (INIT-013 Plan Tooling)
|
|
21
|
+
*/
|
|
22
|
+
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
23
|
+
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
24
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
27
|
+
import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
|
|
28
|
+
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
29
|
+
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
30
|
+
import { todayISO } from '@lumenflow/core/dist/date-utils.js';
|
|
31
|
+
import { LOG_PREFIX as CORE_LOG_PREFIX } from '@lumenflow/core/dist/wu-constants.js';
|
|
32
|
+
/** Log prefix for console output */
|
|
33
|
+
export const LOG_PREFIX = CORE_LOG_PREFIX.PLAN_PROMOTE ?? '[plan:promote]';
|
|
34
|
+
/** Micro-worktree operation name */
|
|
35
|
+
const OPERATION_NAME = 'plan-promote';
|
|
36
|
+
/** WU ID pattern */
|
|
37
|
+
const WU_ID_PATTERN = /^WU-\d+$/;
|
|
38
|
+
/** Initiative ID pattern */
|
|
39
|
+
const INIT_ID_PATTERN = /^INIT-[A-Z0-9]+$/i;
|
|
40
|
+
/** Required sections that must have content */
|
|
41
|
+
const REQUIRED_SECTIONS = ['Goal', 'Scope', 'Approach'];
|
|
42
|
+
/** Status marker patterns */
|
|
43
|
+
const STATUS_APPROVED_PATTERN = /^Status:\s*approved/im;
|
|
44
|
+
/**
|
|
45
|
+
* Get the path to a plan file from its ID
|
|
46
|
+
*
|
|
47
|
+
* @param id - WU or Initiative ID
|
|
48
|
+
* @returns Path to plan file
|
|
49
|
+
* @throws Error if plan not found
|
|
50
|
+
*/
|
|
51
|
+
export function getPlanPath(id) {
|
|
52
|
+
const plansDir = WU_PATHS.PLANS_DIR();
|
|
53
|
+
const planPath = join(plansDir, `${id}-plan.md`);
|
|
54
|
+
if (!existsSync(planPath)) {
|
|
55
|
+
die(`Plan not found for ${id}\n\n` +
|
|
56
|
+
`Expected path: ${planPath}\n\n` +
|
|
57
|
+
`Create it first with: pnpm plan:create --id ${id} --title "Title"`);
|
|
58
|
+
}
|
|
59
|
+
return planPath;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate that a plan has all required sections with content
|
|
63
|
+
*
|
|
64
|
+
* @param planPath - Path to plan file
|
|
65
|
+
* @returns Validation result with valid flag and errors array
|
|
66
|
+
*/
|
|
67
|
+
export function validatePlanComplete(planPath) {
|
|
68
|
+
if (!existsSync(planPath)) {
|
|
69
|
+
return { valid: false, errors: ['Plan file not found'] };
|
|
70
|
+
}
|
|
71
|
+
const text = readFileSync(planPath, { encoding: 'utf-8' });
|
|
72
|
+
const errors = [];
|
|
73
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
74
|
+
const sectionHeading = `## ${section}`;
|
|
75
|
+
const headingIndex = text.indexOf(sectionHeading);
|
|
76
|
+
if (headingIndex === -1) {
|
|
77
|
+
errors.push(`Missing required section: ${section}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// Find the content between this heading and the next
|
|
81
|
+
const afterHeading = text.substring(headingIndex + sectionHeading.length);
|
|
82
|
+
const nextHeadingIndex = afterHeading.indexOf('\n## ');
|
|
83
|
+
const sectionContent = nextHeadingIndex >= 0 ? afterHeading.substring(0, nextHeadingIndex) : afterHeading;
|
|
84
|
+
// Check if content is just whitespace, empty, or only comments
|
|
85
|
+
const trimmedContent = sectionContent
|
|
86
|
+
.split('\n')
|
|
87
|
+
.filter((line) => !line.trim().startsWith('<!--') && line.trim() !== '-->')
|
|
88
|
+
.join('\n')
|
|
89
|
+
.trim();
|
|
90
|
+
if (trimmedContent === '' || trimmedContent.length < 10) {
|
|
91
|
+
errors.push(`${section} section is empty or too short`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
valid: errors.length === 0,
|
|
96
|
+
errors,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Promote a plan to approved status
|
|
101
|
+
*
|
|
102
|
+
* Adds Status: approved and Approved: date after the Created line.
|
|
103
|
+
*
|
|
104
|
+
* @param planPath - Path to plan file
|
|
105
|
+
* @returns True if changes were made, false if already approved
|
|
106
|
+
*/
|
|
107
|
+
export function promotePlan(planPath) {
|
|
108
|
+
if (!existsSync(planPath)) {
|
|
109
|
+
die(`Plan file not found: ${planPath}`);
|
|
110
|
+
}
|
|
111
|
+
const text = readFileSync(planPath, { encoding: 'utf-8' });
|
|
112
|
+
// Check if already approved
|
|
113
|
+
if (STATUS_APPROVED_PATTERN.test(text)) {
|
|
114
|
+
console.log(`${LOG_PREFIX} Plan already approved (idempotent)`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// Find the Created: line and insert status after it
|
|
118
|
+
// Use specific pattern to avoid backtracking (sonarjs/slow-regex)
|
|
119
|
+
const createdPattern = /^Created:\s*\S.*$/m;
|
|
120
|
+
const createdMatch = createdPattern.exec(text);
|
|
121
|
+
const today = todayISO();
|
|
122
|
+
const statusLines = `Status: approved\nApproved: ${today}`;
|
|
123
|
+
let newText;
|
|
124
|
+
if (createdMatch && createdMatch.index !== undefined) {
|
|
125
|
+
// Insert after Created: line
|
|
126
|
+
const insertPos = createdMatch.index + createdMatch[0].length;
|
|
127
|
+
newText = text.substring(0, insertPos) + '\n' + statusLines + text.substring(insertPos);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// No Created: line found, insert after first heading
|
|
131
|
+
const firstHeadingPattern = /^# .+$/m;
|
|
132
|
+
const firstHeadingMatch = firstHeadingPattern.exec(text);
|
|
133
|
+
if (firstHeadingMatch && firstHeadingMatch.index !== undefined) {
|
|
134
|
+
const insertPos = firstHeadingMatch.index + firstHeadingMatch[0].length;
|
|
135
|
+
newText =
|
|
136
|
+
text.substring(0, insertPos) + '\n\n' + statusLines + '\n' + text.substring(insertPos);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Fallback: prepend to file
|
|
140
|
+
newText = statusLines + '\n\n' + text;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
writeFileSync(planPath, newText, { encoding: 'utf-8' });
|
|
144
|
+
console.log(`${LOG_PREFIX} Plan promoted to approved status`);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Generate commit message for plan promote operation
|
|
149
|
+
*
|
|
150
|
+
* @param id - WU or Initiative ID
|
|
151
|
+
* @returns Commit message
|
|
152
|
+
*/
|
|
153
|
+
export function getCommitMessage(id) {
|
|
154
|
+
const idLower = id.toLowerCase();
|
|
155
|
+
return `docs: promote ${idLower} plan to approved`;
|
|
156
|
+
}
|
|
157
|
+
async function main() {
|
|
158
|
+
const FORCE_OPTION = {
|
|
159
|
+
name: 'force',
|
|
160
|
+
flags: '-f, --force',
|
|
161
|
+
description: 'Skip validation and promote anyway',
|
|
162
|
+
};
|
|
163
|
+
const args = createWUParser({
|
|
164
|
+
name: 'plan-promote',
|
|
165
|
+
description: 'Promote a plan to approved status',
|
|
166
|
+
options: [WU_OPTIONS.id, FORCE_OPTION],
|
|
167
|
+
required: ['id'],
|
|
168
|
+
allowPositionalId: true,
|
|
169
|
+
});
|
|
170
|
+
const id = args.id;
|
|
171
|
+
const force = args.force;
|
|
172
|
+
// Validate ID format
|
|
173
|
+
if (!WU_ID_PATTERN.test(id) && !INIT_ID_PATTERN.test(id)) {
|
|
174
|
+
die(`Invalid ID format: "${id}"\n\n` + `Expected format: WU-XXX or INIT-XXX`);
|
|
175
|
+
}
|
|
176
|
+
console.log(`${LOG_PREFIX} Promoting plan for ${id}...`);
|
|
177
|
+
// Ensure on main for micro-worktree operations
|
|
178
|
+
await ensureOnMain(getGitForCwd());
|
|
179
|
+
try {
|
|
180
|
+
await withMicroWorktree({
|
|
181
|
+
operation: OPERATION_NAME,
|
|
182
|
+
id,
|
|
183
|
+
logPrefix: LOG_PREFIX,
|
|
184
|
+
pushOnly: true,
|
|
185
|
+
execute: async ({ worktreePath }) => {
|
|
186
|
+
const planRelPath = join(WU_PATHS.PLANS_DIR(), `${id}-plan.md`);
|
|
187
|
+
const planAbsPath = join(worktreePath, planRelPath);
|
|
188
|
+
if (!existsSync(planAbsPath)) {
|
|
189
|
+
die(`Plan not found for ${id}\n\n` +
|
|
190
|
+
`Expected path: ${planRelPath}\n\n` +
|
|
191
|
+
`Create it first with: pnpm plan:create --id ${id} --title "Title"`);
|
|
192
|
+
}
|
|
193
|
+
// Validate plan completeness (unless force)
|
|
194
|
+
if (!force) {
|
|
195
|
+
const validation = validatePlanComplete(planAbsPath);
|
|
196
|
+
if (!validation.valid) {
|
|
197
|
+
const errorList = validation.errors.map((e) => ` - ${e}`).join('\n');
|
|
198
|
+
die(`Plan validation failed:\n\n${errorList}\n\n` +
|
|
199
|
+
`Fix these issues or use --force to skip validation.`);
|
|
200
|
+
}
|
|
201
|
+
console.log(`${LOG_PREFIX} Plan validation passed`);
|
|
202
|
+
}
|
|
203
|
+
// Promote the plan
|
|
204
|
+
const changed = promotePlan(planAbsPath);
|
|
205
|
+
if (!changed) {
|
|
206
|
+
console.log(`${LOG_PREFIX} No changes needed (already approved)`);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
commitMessage: getCommitMessage(id),
|
|
210
|
+
files: [planRelPath],
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
console.log(`\n${LOG_PREFIX} Plan promoted successfully!`);
|
|
215
|
+
console.log(`\nPromotion Details:`);
|
|
216
|
+
console.log(` ID: ${id}`);
|
|
217
|
+
console.log(` Status: approved`);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
die(`Plan promotion failed: ${error.message}\n\n` +
|
|
221
|
+
`Micro-worktree cleanup was attempted automatically.\n` +
|
|
222
|
+
`If issue persists, check for orphaned branches: git branch | grep tmp/${OPERATION_NAME}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Guard main() for testability
|
|
226
|
+
import { runCLI } from './cli-entry-point.js';
|
|
227
|
+
if (import.meta.main) {
|
|
228
|
+
void runCLI(main);
|
|
229
|
+
}
|
|
230
|
+
// Export for testing
|
|
231
|
+
export { main };
|
package/dist/sync-templates.js
CHANGED
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
* - Claude skills -> templates/vendors/claude/.claude/skills/
|
|
9
9
|
* - Core docs (LUMENFLOW.md, constraints.md) -> templates/core/
|
|
10
10
|
*/
|
|
11
|
+
/* eslint-disable no-console -- CLI tool requires console output */
|
|
12
|
+
/* eslint-disable security/detect-non-literal-fs-filename -- CLI tool syncs templates from known paths */
|
|
13
|
+
/* eslint-disable security/detect-non-literal-regexp -- Dynamic date pattern for template substitution */
|
|
11
14
|
import * as fs from 'node:fs';
|
|
12
15
|
import * as path from 'node:path';
|
|
13
16
|
import { createWUParser } from '@lumenflow/core';
|
|
17
|
+
// Directory name constants to avoid duplicate strings
|
|
18
|
+
const LUMENFLOW_DIR = '.lumenflow';
|
|
19
|
+
const CLAUDE_DIR = '.claude';
|
|
20
|
+
const SKILLS_DIR = 'skills';
|
|
14
21
|
// Template variable patterns
|
|
15
22
|
const DATE_PATTERN = /\d{4}-\d{2}-\d{2}/g;
|
|
16
23
|
/**
|
|
@@ -29,6 +36,12 @@ const SYNC_TEMPLATES_OPTIONS = {
|
|
|
29
36
|
description: 'Show detailed output',
|
|
30
37
|
default: false,
|
|
31
38
|
},
|
|
39
|
+
checkDrift: {
|
|
40
|
+
name: 'check-drift',
|
|
41
|
+
flags: '--check-drift',
|
|
42
|
+
description: 'Check for template drift without syncing (CI mode)',
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
32
45
|
};
|
|
33
46
|
/**
|
|
34
47
|
* Parse sync-templates command options
|
|
@@ -42,6 +55,7 @@ export function parseSyncTemplatesOptions() {
|
|
|
42
55
|
return {
|
|
43
56
|
dryRun: opts['dry-run'] ?? false,
|
|
44
57
|
verbose: opts.verbose ?? false,
|
|
58
|
+
checkDrift: opts['check-drift'] ?? false,
|
|
45
59
|
};
|
|
46
60
|
}
|
|
47
61
|
/**
|
|
@@ -119,8 +133,8 @@ export async function syncOnboardingDocs(projectRoot, dryRun = false) {
|
|
|
119
133
|
*/
|
|
120
134
|
export async function syncSkillsToTemplates(projectRoot, dryRun = false) {
|
|
121
135
|
const result = { synced: [], errors: [] };
|
|
122
|
-
const sourceDir = path.join(projectRoot,
|
|
123
|
-
const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude',
|
|
136
|
+
const sourceDir = path.join(projectRoot, CLAUDE_DIR, SKILLS_DIR);
|
|
137
|
+
const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude', CLAUDE_DIR, SKILLS_DIR);
|
|
124
138
|
if (!fs.existsSync(sourceDir)) {
|
|
125
139
|
result.errors.push(`Skills source directory not found: ${sourceDir}`);
|
|
126
140
|
return result;
|
|
@@ -153,8 +167,8 @@ export async function syncCoreDocs(projectRoot, dryRun = false) {
|
|
|
153
167
|
const lumenflowTarget = path.join(templatesDir, 'core', 'LUMENFLOW.md.template');
|
|
154
168
|
syncFile(lumenflowSource, lumenflowTarget, projectRoot, result, dryRun);
|
|
155
169
|
// Sync constraints.md
|
|
156
|
-
const constraintsSource = path.join(projectRoot,
|
|
157
|
-
const constraintsTarget = path.join(templatesDir, 'core',
|
|
170
|
+
const constraintsSource = path.join(projectRoot, LUMENFLOW_DIR, 'constraints.md');
|
|
171
|
+
const constraintsTarget = path.join(templatesDir, 'core', LUMENFLOW_DIR, 'constraints.md.template');
|
|
158
172
|
syncFile(constraintsSource, constraintsTarget, projectRoot, result, dryRun);
|
|
159
173
|
return result;
|
|
160
174
|
}
|
|
@@ -167,12 +181,130 @@ export async function syncTemplates(projectRoot, dryRun = false) {
|
|
|
167
181
|
const core = await syncCoreDocs(projectRoot, dryRun);
|
|
168
182
|
return { onboarding, skills, core };
|
|
169
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Compare source file content with template content (ignoring date placeholders)
|
|
186
|
+
*/
|
|
187
|
+
function compareContent(sourceContent, templateContent, projectRoot) {
|
|
188
|
+
// Convert source to template format for comparison
|
|
189
|
+
const convertedSource = convertToTemplate(sourceContent, projectRoot);
|
|
190
|
+
return convertedSource === templateContent;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if a single template file is in sync with its source
|
|
194
|
+
*/
|
|
195
|
+
function checkFileDrift(sourcePath, templatePath, projectRoot) {
|
|
196
|
+
const relativePath = path.relative(projectRoot, templatePath);
|
|
197
|
+
if (!fs.existsSync(sourcePath)) {
|
|
198
|
+
return { isDrifting: false, relativePath }; // Source doesn't exist, can't drift
|
|
199
|
+
}
|
|
200
|
+
if (!fs.existsSync(templatePath)) {
|
|
201
|
+
return { isDrifting: true, relativePath }; // Template missing, definitely drifting
|
|
202
|
+
}
|
|
203
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
|
|
204
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
205
|
+
const isDrifting = !compareContent(sourceContent, templateContent, projectRoot);
|
|
206
|
+
return { isDrifting, relativePath };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check for template drift - compares source docs with templates (WU-1353)
|
|
210
|
+
*
|
|
211
|
+
* This function compares source documents with their template counterparts
|
|
212
|
+
* to detect if templates have drifted out of sync. Used by CI to warn
|
|
213
|
+
* when templates need to be re-synced.
|
|
214
|
+
*/
|
|
215
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- Multi-category drift check requires nested iteration
|
|
216
|
+
export async function checkTemplateDrift(projectRoot) {
|
|
217
|
+
const driftingFiles = [];
|
|
218
|
+
const checkedFiles = [];
|
|
219
|
+
const templatesDir = getTemplatesDir(projectRoot);
|
|
220
|
+
// Check core docs
|
|
221
|
+
const coreChecks = [
|
|
222
|
+
{
|
|
223
|
+
source: path.join(projectRoot, 'LUMENFLOW.md'),
|
|
224
|
+
template: path.join(templatesDir, 'core', 'LUMENFLOW.md.template'),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
source: path.join(projectRoot, LUMENFLOW_DIR, 'constraints.md'),
|
|
228
|
+
template: path.join(templatesDir, 'core', LUMENFLOW_DIR, 'constraints.md.template'),
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
for (const check of coreChecks) {
|
|
232
|
+
const result = checkFileDrift(check.source, check.template, projectRoot);
|
|
233
|
+
checkedFiles.push(result.relativePath);
|
|
234
|
+
if (result.isDrifting) {
|
|
235
|
+
driftingFiles.push(result.relativePath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Check onboarding docs
|
|
239
|
+
const onboardingSourceDir = path.join(projectRoot, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
|
|
240
|
+
const onboardingTargetDir = path.join(templatesDir, 'core', 'ai', 'onboarding');
|
|
241
|
+
if (fs.existsSync(onboardingSourceDir)) {
|
|
242
|
+
const files = fs.readdirSync(onboardingSourceDir).filter((f) => f.endsWith('.md'));
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const sourcePath = path.join(onboardingSourceDir, file);
|
|
245
|
+
const templatePath = path.join(onboardingTargetDir, `${file}.template`);
|
|
246
|
+
const result = checkFileDrift(sourcePath, templatePath, projectRoot);
|
|
247
|
+
checkedFiles.push(result.relativePath);
|
|
248
|
+
if (result.isDrifting) {
|
|
249
|
+
driftingFiles.push(result.relativePath);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Check skills
|
|
254
|
+
const skillsSourceDir = path.join(projectRoot, CLAUDE_DIR, SKILLS_DIR);
|
|
255
|
+
const skillsTargetDir = path.join(templatesDir, 'vendors', 'claude', CLAUDE_DIR, SKILLS_DIR);
|
|
256
|
+
if (fs.existsSync(skillsSourceDir)) {
|
|
257
|
+
const skillDirs = fs
|
|
258
|
+
.readdirSync(skillsSourceDir, { withFileTypes: true })
|
|
259
|
+
.filter((d) => d.isDirectory())
|
|
260
|
+
.map((d) => d.name);
|
|
261
|
+
for (const skillName of skillDirs) {
|
|
262
|
+
const skillFile = path.join(skillsSourceDir, skillName, 'SKILL.md');
|
|
263
|
+
const templatePath = path.join(skillsTargetDir, skillName, 'SKILL.md.template');
|
|
264
|
+
if (fs.existsSync(skillFile)) {
|
|
265
|
+
const result = checkFileDrift(skillFile, templatePath, projectRoot);
|
|
266
|
+
checkedFiles.push(result.relativePath);
|
|
267
|
+
if (result.isDrifting) {
|
|
268
|
+
driftingFiles.push(result.relativePath);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
hasDrift: driftingFiles.length > 0,
|
|
275
|
+
driftingFiles,
|
|
276
|
+
checkedFiles,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
170
279
|
/**
|
|
171
280
|
* CLI entry point
|
|
172
281
|
*/
|
|
282
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- CLI main() handles multiple modes and output formatting
|
|
173
283
|
export async function main() {
|
|
174
284
|
const opts = parseSyncTemplatesOptions();
|
|
175
285
|
const projectRoot = process.cwd();
|
|
286
|
+
// Check-drift mode: verify templates match source without syncing
|
|
287
|
+
if (opts.checkDrift) {
|
|
288
|
+
console.log('[sync-templates] Checking for template drift...');
|
|
289
|
+
const drift = await checkTemplateDrift(projectRoot);
|
|
290
|
+
if (opts.verbose) {
|
|
291
|
+
console.log(` Checked ${drift.checkedFiles.length} files`);
|
|
292
|
+
}
|
|
293
|
+
if (drift.hasDrift) {
|
|
294
|
+
console.log('\n[sync-templates] WARNING: Template drift detected!');
|
|
295
|
+
console.log(' The following templates are out of sync with their source:');
|
|
296
|
+
for (const file of drift.driftingFiles) {
|
|
297
|
+
console.log(` - ${file}`);
|
|
298
|
+
}
|
|
299
|
+
console.log('\n Run `pnpm sync:templates` to update templates.');
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.log('[sync-templates] All templates are in sync.');
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Sync mode: update templates from source
|
|
176
308
|
console.log('[sync-templates] Syncing internal docs to CLI templates...');
|
|
177
309
|
if (opts.dryRun) {
|
|
178
310
|
console.log(' (dry-run mode - no files will be written)');
|
|
@@ -208,5 +340,5 @@ export async function main() {
|
|
|
208
340
|
// CLI entry point
|
|
209
341
|
import { runCLI } from './cli-entry-point.js';
|
|
210
342
|
if (import.meta.main) {
|
|
211
|
-
runCLI(main);
|
|
343
|
+
void runCLI(main);
|
|
212
344
|
}
|
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;
|