@lumenflow/cli 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +147 -57
  2. package/dist/__tests__/agent-log-issue.test.js +56 -0
  3. package/dist/__tests__/cli-entry-point.test.js +66 -17
  4. package/dist/__tests__/cli-subprocess.test.js +25 -0
  5. package/dist/__tests__/init.test.js +298 -0
  6. package/dist/__tests__/initiative-plan.test.js +340 -0
  7. package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
  8. package/dist/__tests__/merge-block.test.js +220 -0
  9. package/dist/__tests__/release.test.js +28 -0
  10. package/dist/__tests__/safe-git.test.js +191 -0
  11. package/dist/__tests__/state-doctor.test.js +274 -0
  12. package/dist/__tests__/wu-done.test.js +36 -0
  13. package/dist/__tests__/wu-edit.test.js +119 -0
  14. package/dist/__tests__/wu-prep.test.js +108 -0
  15. package/dist/agent-issues-query.js +4 -3
  16. package/dist/agent-log-issue.js +25 -4
  17. package/dist/backlog-prune.js +5 -4
  18. package/dist/cli-entry-point.js +11 -1
  19. package/dist/doctor.js +368 -0
  20. package/dist/flow-bottlenecks.js +6 -5
  21. package/dist/flow-report.js +4 -3
  22. package/dist/gates.js +468 -116
  23. package/dist/guard-locked.js +4 -3
  24. package/dist/guard-worktree-commit.js +4 -3
  25. package/dist/init.js +508 -86
  26. package/dist/initiative-add-wu.js +4 -3
  27. package/dist/initiative-bulk-assign-wus.js +8 -5
  28. package/dist/initiative-create.js +73 -37
  29. package/dist/initiative-edit.js +37 -21
  30. package/dist/initiative-list.js +4 -3
  31. package/dist/initiative-plan.js +337 -0
  32. package/dist/initiative-status.js +4 -3
  33. package/dist/lane-health.js +377 -0
  34. package/dist/lane-suggest.js +382 -0
  35. package/dist/mem-checkpoint.js +2 -2
  36. package/dist/mem-cleanup.js +2 -2
  37. package/dist/mem-context.js +306 -0
  38. package/dist/mem-create.js +2 -2
  39. package/dist/mem-delete.js +293 -0
  40. package/dist/mem-inbox.js +2 -2
  41. package/dist/mem-index.js +211 -0
  42. package/dist/mem-init.js +1 -1
  43. package/dist/mem-profile.js +207 -0
  44. package/dist/mem-promote.js +254 -0
  45. package/dist/mem-ready.js +2 -2
  46. package/dist/mem-signal.js +2 -2
  47. package/dist/mem-start.js +2 -2
  48. package/dist/mem-summarize.js +2 -2
  49. package/dist/mem-triage.js +2 -2
  50. package/dist/merge-block.js +222 -0
  51. package/dist/metrics-cli.js +7 -4
  52. package/dist/metrics-snapshot.js +4 -3
  53. package/dist/orchestrate-initiative.js +10 -4
  54. package/dist/orchestrate-monitor.js +379 -31
  55. package/dist/signal-cleanup.js +296 -0
  56. package/dist/spawn-list.js +6 -5
  57. package/dist/state-bootstrap.js +5 -4
  58. package/dist/state-cleanup.js +360 -0
  59. package/dist/state-doctor-fix.js +196 -0
  60. package/dist/state-doctor.js +501 -0
  61. package/dist/validate-agent-skills.js +4 -3
  62. package/dist/validate-agent-sync.js +4 -3
  63. package/dist/validate-backlog-sync.js +7 -84
  64. package/dist/validate-skills-spec.js +4 -3
  65. package/dist/validate.js +7 -107
  66. package/dist/wu-block.js +3 -3
  67. package/dist/wu-claim.js +208 -98
  68. package/dist/wu-cleanup.js +5 -4
  69. package/dist/wu-create.js +71 -46
  70. package/dist/wu-delete.js +88 -60
  71. package/dist/wu-deps.js +6 -5
  72. package/dist/wu-done-check.js +34 -0
  73. package/dist/wu-done.js +60 -24
  74. package/dist/wu-edit.js +63 -28
  75. package/dist/wu-infer-lane.js +7 -6
  76. package/dist/wu-preflight.js +23 -81
  77. package/dist/wu-prep.js +125 -0
  78. package/dist/wu-prune.js +4 -3
  79. package/dist/wu-recover.js +88 -22
  80. package/dist/wu-repair.js +7 -6
  81. package/dist/wu-spawn.js +226 -270
  82. package/dist/wu-status.js +4 -3
  83. package/dist/wu-unblock.js +5 -5
  84. package/dist/wu-unlock-lane.js +4 -3
  85. package/dist/wu-validate.js +5 -4
  86. package/package.json +16 -7
  87. package/templates/core/.lumenflow/constraints.md.template +192 -0
  88. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  89. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  90. package/templates/core/AGENTS.md.template +60 -0
  91. package/templates/core/LUMENFLOW.md.template +255 -0
  92. package/templates/core/UPGRADING.md.template +121 -0
  93. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  94. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  95. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  96. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  97. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  98. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  99. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  100. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  101. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  102. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  103. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  104. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  105. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  106. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  107. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  108. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  109. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  110. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  111. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  112. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  113. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  114. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  115. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  116. package/templates/vendors/cline/.clinerules.template +53 -0
  117. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  118. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  119. 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
+ }