@lumenflow/cli 2.2.2 → 2.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/README.md +147 -57
- package/dist/__tests__/agent-log-issue.test.js +56 -0
- package/dist/__tests__/cli-entry-point.test.js +66 -17
- package/dist/__tests__/cli-subprocess.test.js +25 -0
- package/dist/__tests__/init.test.js +298 -0
- package/dist/__tests__/initiative-plan.test.js +340 -0
- package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
- package/dist/__tests__/merge-block.test.js +220 -0
- package/dist/__tests__/release.test.js +61 -0
- package/dist/__tests__/safe-git.test.js +191 -0
- package/dist/__tests__/state-doctor.test.js +274 -0
- package/dist/__tests__/wu-done.test.js +36 -0
- package/dist/__tests__/wu-edit.test.js +119 -0
- package/dist/__tests__/wu-prep.test.js +108 -0
- package/dist/agent-issues-query.js +4 -3
- package/dist/agent-log-issue.js +25 -4
- package/dist/backlog-prune.js +5 -4
- package/dist/cli-entry-point.js +11 -1
- package/dist/doctor.js +368 -0
- package/dist/flow-bottlenecks.js +6 -5
- package/dist/flow-report.js +4 -3
- package/dist/gates.js +356 -101
- package/dist/guard-locked.js +4 -3
- package/dist/guard-worktree-commit.js +4 -3
- package/dist/init.js +517 -86
- package/dist/initiative-add-wu.js +4 -3
- package/dist/initiative-bulk-assign-wus.js +8 -5
- package/dist/initiative-create.js +73 -37
- package/dist/initiative-edit.js +37 -21
- package/dist/initiative-list.js +4 -3
- package/dist/initiative-plan.js +337 -0
- package/dist/initiative-status.js +4 -3
- package/dist/lane-health.js +377 -0
- package/dist/lane-suggest.js +382 -0
- package/dist/mem-checkpoint.js +2 -2
- package/dist/mem-cleanup.js +2 -2
- package/dist/mem-context.js +306 -0
- package/dist/mem-create.js +2 -2
- package/dist/mem-delete.js +293 -0
- package/dist/mem-inbox.js +2 -2
- package/dist/mem-index.js +211 -0
- package/dist/mem-init.js +1 -1
- package/dist/mem-profile.js +207 -0
- package/dist/mem-promote.js +254 -0
- package/dist/mem-ready.js +2 -2
- package/dist/mem-signal.js +2 -2
- package/dist/mem-start.js +2 -2
- package/dist/mem-summarize.js +2 -2
- package/dist/mem-triage.js +2 -2
- package/dist/merge-block.js +222 -0
- package/dist/metrics-cli.js +7 -4
- package/dist/metrics-snapshot.js +4 -3
- package/dist/orchestrate-initiative.js +10 -4
- package/dist/orchestrate-monitor.js +379 -31
- package/dist/release.js +69 -29
- package/dist/signal-cleanup.js +296 -0
- package/dist/spawn-list.js +6 -5
- package/dist/state-bootstrap.js +5 -4
- package/dist/state-cleanup.js +360 -0
- package/dist/state-doctor-fix.js +196 -0
- package/dist/state-doctor.js +501 -0
- package/dist/validate-agent-skills.js +4 -3
- package/dist/validate-agent-sync.js +4 -3
- package/dist/validate-backlog-sync.js +4 -3
- package/dist/validate-skills-spec.js +4 -3
- package/dist/validate.js +4 -3
- package/dist/wu-block.js +3 -3
- package/dist/wu-claim.js +208 -98
- package/dist/wu-cleanup.js +5 -4
- package/dist/wu-create.js +71 -46
- package/dist/wu-delete.js +88 -60
- package/dist/wu-deps.js +6 -5
- package/dist/wu-done-check.js +34 -0
- package/dist/wu-done.js +39 -12
- package/dist/wu-edit.js +63 -28
- package/dist/wu-infer-lane.js +7 -6
- package/dist/wu-preflight.js +23 -81
- package/dist/wu-prep.js +125 -0
- package/dist/wu-prune.js +4 -3
- package/dist/wu-recover.js +88 -22
- package/dist/wu-repair.js +7 -6
- package/dist/wu-spawn.js +226 -270
- package/dist/wu-status.js +4 -3
- package/dist/wu-unblock.js +5 -5
- package/dist/wu-unlock-lane.js +4 -3
- package/dist/wu-validate.js +5 -4
- package/package.json +16 -7
- package/templates/core/.lumenflow/constraints.md.template +192 -0
- package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
- package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
- package/templates/core/AGENTS.md.template +60 -0
- package/templates/core/LUMENFLOW.md.template +255 -0
- package/templates/core/UPGRADING.md.template +121 -0
- package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
- package/templates/core/ai/onboarding/release-process.md.template +362 -0
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
- package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
- package/templates/vendors/aider/.aider.conf.yml.template +27 -0
- package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
- package/templates/vendors/claude/.claude/settings.json.template +49 -0
- package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
- package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
- package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
- package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
- package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
- package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
- package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
- package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
- package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
- package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
- package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
- package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
- package/templates/vendors/cline/.clinerules.template +53 -0
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
- package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lane Health Command
|
|
4
|
+
*
|
|
5
|
+
* WU-1188: CLI command to diagnose lane configuration issues:
|
|
6
|
+
* - Overlap detection between lane code_paths
|
|
7
|
+
* - Coverage gaps (files not covered by any lane)
|
|
8
|
+
* - Exit code 0 for healthy, 1 for issues
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* pnpm lane:health # Run health check
|
|
12
|
+
* pnpm lane:health --json # Output as JSON
|
|
13
|
+
* pnpm lane:health --verbose # Show all checked files
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import fg from 'fast-glob';
|
|
18
|
+
import { minimatch } from 'minimatch';
|
|
19
|
+
import { parse as parseYAML } from 'yaml';
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
|
|
22
|
+
import { findProjectRoot } from '@lumenflow/core/dist/lumenflow-config.js';
|
|
23
|
+
import { runCLI } from './cli-entry-point.js';
|
|
24
|
+
/** Constants */
|
|
25
|
+
const LOG_PREFIX = '[lane:health]';
|
|
26
|
+
const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
|
|
27
|
+
const MAX_DISPLAY_FILES = 5;
|
|
28
|
+
const MAX_DISPLAY_GAPS = 10;
|
|
29
|
+
/** Default exclude patterns for coverage gap detection */
|
|
30
|
+
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
31
|
+
'node_modules/**',
|
|
32
|
+
'.git/**',
|
|
33
|
+
'dist/**',
|
|
34
|
+
'build/**',
|
|
35
|
+
'coverage/**',
|
|
36
|
+
'.turbo/**',
|
|
37
|
+
'*.lock',
|
|
38
|
+
'pnpm-lock.yaml',
|
|
39
|
+
'package-lock.json',
|
|
40
|
+
'yarn.lock',
|
|
41
|
+
'.lumenflow/**',
|
|
42
|
+
'worktrees/**',
|
|
43
|
+
];
|
|
44
|
+
/** File extensions to check for coverage (code files only) */
|
|
45
|
+
const CODE_FILE_EXTENSIONS = [
|
|
46
|
+
'.ts',
|
|
47
|
+
'.tsx',
|
|
48
|
+
'.js',
|
|
49
|
+
'.jsx',
|
|
50
|
+
'.mjs',
|
|
51
|
+
'.cjs',
|
|
52
|
+
'.vue',
|
|
53
|
+
'.svelte',
|
|
54
|
+
'.py',
|
|
55
|
+
'.go',
|
|
56
|
+
'.rs',
|
|
57
|
+
'.java',
|
|
58
|
+
'.kt',
|
|
59
|
+
'.swift',
|
|
60
|
+
'.c',
|
|
61
|
+
'.cpp',
|
|
62
|
+
'.h',
|
|
63
|
+
'.hpp',
|
|
64
|
+
'.cs',
|
|
65
|
+
'.rb',
|
|
66
|
+
'.php',
|
|
67
|
+
];
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Lane Loading
|
|
70
|
+
// ============================================================================
|
|
71
|
+
/**
|
|
72
|
+
* Parse lane definition from raw object
|
|
73
|
+
*/
|
|
74
|
+
function parseLaneDefinition(lane) {
|
|
75
|
+
if (typeof lane !== 'object' || lane === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const laneObj = lane;
|
|
79
|
+
if (typeof laneObj.name !== 'string' || !Array.isArray(laneObj.code_paths)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
name: laneObj.name,
|
|
84
|
+
code_paths: laneObj.code_paths.filter((p) => typeof p === 'string'),
|
|
85
|
+
wip_limit: typeof laneObj.wip_limit === 'number' ? laneObj.wip_limit : undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Load lane definitions from .lumenflow.config.yaml
|
|
90
|
+
*
|
|
91
|
+
* @param projectRoot - Project root directory
|
|
92
|
+
* @returns Array of lane definitions
|
|
93
|
+
*/
|
|
94
|
+
export function loadLaneDefinitions(projectRoot) {
|
|
95
|
+
const configPath = path.join(projectRoot, CONFIG_FILE_NAME);
|
|
96
|
+
if (!existsSync(configPath)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const content = readFileSync(configPath, 'utf8');
|
|
101
|
+
const config = parseYAML(content);
|
|
102
|
+
const lanesConfig = config.lanes;
|
|
103
|
+
if (!lanesConfig) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const definitions = (lanesConfig.definitions || lanesConfig);
|
|
107
|
+
if (!Array.isArray(definitions)) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
return definitions
|
|
111
|
+
.map(parseLaneDefinition)
|
|
112
|
+
.filter((lane) => lane !== null);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Overlap Detection
|
|
120
|
+
// ============================================================================
|
|
121
|
+
/**
|
|
122
|
+
* Check if two glob patterns can potentially overlap
|
|
123
|
+
*/
|
|
124
|
+
function patternsCanOverlap(patternA, patternB) {
|
|
125
|
+
const testPathA = patternA.replace(/\*\*/g, 'test/nested').replace(/\*/g, 'testfile');
|
|
126
|
+
const testPathB = patternB.replace(/\*\*/g, 'test/nested').replace(/\*/g, 'testfile');
|
|
127
|
+
return minimatch(testPathB, patternA) || minimatch(testPathA, patternB);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Find concrete file intersection between two glob patterns
|
|
131
|
+
*/
|
|
132
|
+
function findOverlappingFiles(patternA, patternB) {
|
|
133
|
+
const globOptions = {
|
|
134
|
+
dot: true,
|
|
135
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/worktrees/**'],
|
|
136
|
+
followSymbolicLinks: false,
|
|
137
|
+
suppressErrors: true,
|
|
138
|
+
};
|
|
139
|
+
const filesA = new Set(fg.sync(patternA, globOptions));
|
|
140
|
+
const filesB = new Set(fg.sync(patternB, globOptions));
|
|
141
|
+
return [...filesA].filter((file) => filesB.has(file));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check overlap between two lanes' code paths
|
|
145
|
+
*/
|
|
146
|
+
function checkLanePairOverlap(laneA, laneB) {
|
|
147
|
+
const overlaps = [];
|
|
148
|
+
for (const pathA of laneA.code_paths) {
|
|
149
|
+
for (const pathB of laneB.code_paths) {
|
|
150
|
+
if (patternsCanOverlap(pathA, pathB)) {
|
|
151
|
+
let files = [];
|
|
152
|
+
try {
|
|
153
|
+
files = findOverlappingFiles(pathA, pathB);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Ignore filesystem errors
|
|
157
|
+
}
|
|
158
|
+
overlaps.push({
|
|
159
|
+
lanes: [laneA.name, laneB.name],
|
|
160
|
+
pattern: `${pathA} <-> ${pathB}`,
|
|
161
|
+
files,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return overlaps;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Detect overlapping code_paths between lane definitions
|
|
170
|
+
*/
|
|
171
|
+
export function detectLaneOverlaps(lanes) {
|
|
172
|
+
const overlaps = [];
|
|
173
|
+
for (let i = 0; i < lanes.length; i++) {
|
|
174
|
+
for (let j = i + 1; j < lanes.length; j++) {
|
|
175
|
+
const pairOverlaps = checkLanePairOverlap(lanes[i], lanes[j]);
|
|
176
|
+
overlaps.push(...pairOverlaps);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
hasOverlaps: overlaps.length > 0,
|
|
181
|
+
overlaps,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// Coverage Gap Detection
|
|
186
|
+
// ============================================================================
|
|
187
|
+
/**
|
|
188
|
+
* Build pattern for code files
|
|
189
|
+
*/
|
|
190
|
+
function buildCodeFilesPattern() {
|
|
191
|
+
const extensions = CODE_FILE_EXTENSIONS.map((ext) => ext.replace('.', '')).join(',');
|
|
192
|
+
return `**/*.{${extensions}}`;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Detect files not covered by any lane
|
|
196
|
+
*/
|
|
197
|
+
export function detectCoverageGaps(lanes, options) {
|
|
198
|
+
const { projectRoot, excludePatterns = DEFAULT_EXCLUDE_PATTERNS, codeOnly = true } = options;
|
|
199
|
+
const allFilesPattern = codeOnly ? buildCodeFilesPattern() : '**/*';
|
|
200
|
+
const globOptions = {
|
|
201
|
+
cwd: projectRoot,
|
|
202
|
+
dot: true,
|
|
203
|
+
ignore: excludePatterns,
|
|
204
|
+
onlyFiles: true,
|
|
205
|
+
followSymbolicLinks: false,
|
|
206
|
+
suppressErrors: true,
|
|
207
|
+
};
|
|
208
|
+
const allFiles = fg.sync(allFilesPattern, globOptions);
|
|
209
|
+
const coveredFiles = new Set();
|
|
210
|
+
for (const lane of lanes) {
|
|
211
|
+
for (const pattern of lane.code_paths) {
|
|
212
|
+
const matchedFiles = fg.sync(pattern, {
|
|
213
|
+
...globOptions,
|
|
214
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/worktrees/**'],
|
|
215
|
+
});
|
|
216
|
+
matchedFiles.forEach((file) => coveredFiles.add(file));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const uncoveredFiles = allFiles.filter((file) => !coveredFiles.has(file));
|
|
220
|
+
return {
|
|
221
|
+
hasGaps: uncoveredFiles.length > 0,
|
|
222
|
+
uncoveredFiles,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Report Formatting
|
|
227
|
+
// ============================================================================
|
|
228
|
+
/**
|
|
229
|
+
* Get exit code based on report health
|
|
230
|
+
*/
|
|
231
|
+
export function getExitCode(report) {
|
|
232
|
+
return report.healthy ? 0 : 1;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Format overlap section
|
|
236
|
+
*/
|
|
237
|
+
function formatOverlapSection(overlap) {
|
|
238
|
+
const lines = [];
|
|
239
|
+
lines.push(` ${chalk.cyan(overlap.lanes[0])} <-> ${chalk.cyan(overlap.lanes[1])}`);
|
|
240
|
+
lines.push(` Pattern: ${overlap.pattern}`);
|
|
241
|
+
lines.push(` Files (${overlap.files.length}):`);
|
|
242
|
+
const displayFiles = overlap.files.slice(0, MAX_DISPLAY_FILES);
|
|
243
|
+
displayFiles.forEach((file) => lines.push(` - ${file}`));
|
|
244
|
+
if (overlap.files.length > MAX_DISPLAY_FILES) {
|
|
245
|
+
lines.push(` ... and ${overlap.files.length - MAX_DISPLAY_FILES} more`);
|
|
246
|
+
}
|
|
247
|
+
lines.push('');
|
|
248
|
+
return lines;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Format lane health report as human-readable text
|
|
252
|
+
*/
|
|
253
|
+
export function formatLaneHealthReport(report) {
|
|
254
|
+
const lines = [];
|
|
255
|
+
// Header
|
|
256
|
+
lines.push('');
|
|
257
|
+
lines.push(chalk.bold('='.repeat(60)));
|
|
258
|
+
lines.push(chalk.bold.cyan(' Lane Health Report'));
|
|
259
|
+
lines.push(chalk.bold('='.repeat(60)));
|
|
260
|
+
lines.push('');
|
|
261
|
+
// Status
|
|
262
|
+
if (report.healthy) {
|
|
263
|
+
lines.push(chalk.green.bold(' Status: healthy'));
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push(' All lane configurations are valid:');
|
|
266
|
+
lines.push(' - No overlapping code_paths detected');
|
|
267
|
+
lines.push(' - All code files covered by lanes');
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
lines.push(chalk.red.bold(' Status: Issues detected'));
|
|
271
|
+
}
|
|
272
|
+
lines.push('');
|
|
273
|
+
// Overlaps
|
|
274
|
+
if (report.overlaps.hasOverlaps) {
|
|
275
|
+
lines.push(chalk.yellow.bold(' Overlapping Code Paths'));
|
|
276
|
+
lines.push(' ' + '-'.repeat(40));
|
|
277
|
+
lines.push('');
|
|
278
|
+
report.overlaps.overlaps.forEach((overlap) => {
|
|
279
|
+
lines.push(...formatOverlapSection(overlap));
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Coverage gaps
|
|
283
|
+
if (report.gaps.hasGaps) {
|
|
284
|
+
lines.push(chalk.yellow.bold(' Coverage Gaps'));
|
|
285
|
+
lines.push(' ' + '-'.repeat(40));
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push(` ${report.gaps.uncoveredFiles.length} files not covered by any lane:`);
|
|
288
|
+
lines.push('');
|
|
289
|
+
const displayFiles = report.gaps.uncoveredFiles.slice(0, MAX_DISPLAY_GAPS);
|
|
290
|
+
displayFiles.forEach((file) => lines.push(` - ${file}`));
|
|
291
|
+
if (report.gaps.uncoveredFiles.length > MAX_DISPLAY_GAPS) {
|
|
292
|
+
lines.push(` ... and ${report.gaps.uncoveredFiles.length - MAX_DISPLAY_GAPS} more`);
|
|
293
|
+
}
|
|
294
|
+
lines.push('');
|
|
295
|
+
}
|
|
296
|
+
lines.push(chalk.bold('='.repeat(60)));
|
|
297
|
+
lines.push('');
|
|
298
|
+
return lines.join('\n');
|
|
299
|
+
}
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// CLI Entry Point
|
|
302
|
+
// ============================================================================
|
|
303
|
+
/** Logger for CLI output */
|
|
304
|
+
// eslint-disable-next-line no-console
|
|
305
|
+
const log = console.log.bind(console);
|
|
306
|
+
// eslint-disable-next-line no-console
|
|
307
|
+
const warn = console.warn.bind(console);
|
|
308
|
+
/**
|
|
309
|
+
* Run lane health check
|
|
310
|
+
*/
|
|
311
|
+
export function runLaneHealthCheck(options) {
|
|
312
|
+
const { projectRoot = findProjectRoot(), checkCoverage = true, excludePatterns } = options;
|
|
313
|
+
const lanes = loadLaneDefinitions(projectRoot);
|
|
314
|
+
if (lanes.length === 0) {
|
|
315
|
+
warn(`${LOG_PREFIX} No lane definitions found in ${CONFIG_FILE_NAME}`);
|
|
316
|
+
return {
|
|
317
|
+
overlaps: { hasOverlaps: false, overlaps: [] },
|
|
318
|
+
gaps: { hasGaps: false, uncoveredFiles: [] },
|
|
319
|
+
healthy: true,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const overlaps = detectLaneOverlaps(lanes);
|
|
323
|
+
let gaps = { hasGaps: false, uncoveredFiles: [] };
|
|
324
|
+
if (checkCoverage) {
|
|
325
|
+
gaps = detectCoverageGaps(lanes, { projectRoot, excludePatterns });
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
overlaps,
|
|
329
|
+
gaps,
|
|
330
|
+
healthy: !overlaps.hasOverlaps && !gaps.hasGaps,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Main entry point
|
|
335
|
+
*/
|
|
336
|
+
async function main() {
|
|
337
|
+
const args = createWUParser({
|
|
338
|
+
name: 'lane-health',
|
|
339
|
+
description: 'Check lane configuration health (WU-1188)',
|
|
340
|
+
options: [
|
|
341
|
+
{ name: 'json', flags: '-j, --json', type: 'boolean', description: 'Output as JSON' },
|
|
342
|
+
{
|
|
343
|
+
name: 'verbose',
|
|
344
|
+
flags: '-v, --verbose',
|
|
345
|
+
type: 'boolean',
|
|
346
|
+
description: 'Show verbose output',
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: 'no-coverage',
|
|
350
|
+
flags: '--no-coverage',
|
|
351
|
+
type: 'boolean',
|
|
352
|
+
description: 'Skip coverage gap detection',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
required: [],
|
|
356
|
+
});
|
|
357
|
+
const { json, verbose, 'no-coverage': noCoverage, } = args;
|
|
358
|
+
const projectRoot = findProjectRoot();
|
|
359
|
+
if (verbose) {
|
|
360
|
+
log(`${LOG_PREFIX} Checking lane health in: ${projectRoot}`);
|
|
361
|
+
}
|
|
362
|
+
const report = runLaneHealthCheck({
|
|
363
|
+
projectRoot,
|
|
364
|
+
checkCoverage: !noCoverage,
|
|
365
|
+
});
|
|
366
|
+
if (json) {
|
|
367
|
+
log(JSON.stringify(report, null, 2));
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
log(formatLaneHealthReport(report));
|
|
371
|
+
}
|
|
372
|
+
process.exit(getExitCode(report));
|
|
373
|
+
}
|
|
374
|
+
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
|
|
375
|
+
if (import.meta.main) {
|
|
376
|
+
void runCLI(main);
|
|
377
|
+
}
|