@lumenflow/cli 2.20.1 → 2.21.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 (111) hide show
  1. package/README.md +8 -4
  2. package/dist/hooks/enforcement-checks.js +120 -0
  3. package/dist/hooks/enforcement-checks.js.map +1 -1
  4. package/dist/init-lane-validation.js +141 -0
  5. package/dist/init-lane-validation.js.map +1 -0
  6. package/dist/init-templates.js +36 -8
  7. package/dist/init-templates.js.map +1 -1
  8. package/dist/init.js +27 -58
  9. package/dist/init.js.map +1 -1
  10. package/dist/initiative-create.js +35 -4
  11. package/dist/initiative-create.js.map +1 -1
  12. package/dist/lane-lifecycle-process.js +364 -0
  13. package/dist/lane-lifecycle-process.js.map +1 -0
  14. package/dist/lane-lock.js +41 -0
  15. package/dist/lane-lock.js.map +1 -0
  16. package/dist/lane-setup.js +55 -0
  17. package/dist/lane-setup.js.map +1 -0
  18. package/dist/lane-status.js +38 -0
  19. package/dist/lane-status.js.map +1 -0
  20. package/dist/lane-validate.js +43 -0
  21. package/dist/lane-validate.js.map +1 -0
  22. package/dist/onboarding-smoke-test.js +17 -0
  23. package/dist/onboarding-smoke-test.js.map +1 -1
  24. package/dist/public-manifest.js +28 -0
  25. package/dist/public-manifest.js.map +1 -1
  26. package/dist/wu-claim-cloud.js +16 -0
  27. package/dist/wu-claim-cloud.js.map +1 -1
  28. package/dist/wu-claim.js +12 -2
  29. package/dist/wu-claim.js.map +1 -1
  30. package/dist/wu-create-content.js +8 -2
  31. package/dist/wu-create-content.js.map +1 -1
  32. package/dist/wu-create-validation.js +5 -3
  33. package/dist/wu-create-validation.js.map +1 -1
  34. package/dist/wu-create.js +21 -1
  35. package/dist/wu-create.js.map +1 -1
  36. package/dist/wu-done.js +57 -8
  37. package/dist/wu-done.js.map +1 -1
  38. package/dist/wu-prep.js +22 -0
  39. package/dist/wu-prep.js.map +1 -1
  40. package/package.json +15 -11
  41. package/dist/__tests__/agent-log-issue.test.js +0 -56
  42. package/dist/__tests__/agent-spawn-coordination.test.js +0 -451
  43. package/dist/__tests__/backlog-prune.test.js +0 -478
  44. package/dist/__tests__/cli-entry-point.test.js +0 -160
  45. package/dist/__tests__/cli-subprocess.test.js +0 -89
  46. package/dist/__tests__/commands/integrate.test.js +0 -165
  47. package/dist/__tests__/commands.test.js +0 -271
  48. package/dist/__tests__/deps-operations.test.js +0 -206
  49. package/dist/__tests__/doctor.test.js +0 -510
  50. package/dist/__tests__/file-operations.test.js +0 -906
  51. package/dist/__tests__/flow-report.test.js +0 -24
  52. package/dist/__tests__/gates-config.test.js +0 -303
  53. package/dist/__tests__/gates-integration-tests.test.js +0 -112
  54. package/dist/__tests__/git-operations.test.js +0 -668
  55. package/dist/__tests__/guard-main-branch.test.js +0 -79
  56. package/dist/__tests__/guards-validation.test.js +0 -416
  57. package/dist/__tests__/hooks/enforcement.test.js +0 -279
  58. package/dist/__tests__/init-config-lanes.test.js +0 -131
  59. package/dist/__tests__/init-docs-structure.test.js +0 -152
  60. package/dist/__tests__/init-greenfield.test.js +0 -247
  61. package/dist/__tests__/init-lane-inference.test.js +0 -125
  62. package/dist/__tests__/init-onboarding-docs.test.js +0 -132
  63. package/dist/__tests__/init-quick-ref.test.js +0 -144
  64. package/dist/__tests__/init-scripts.test.js +0 -207
  65. package/dist/__tests__/init-template-portability.test.js +0 -96
  66. package/dist/__tests__/init.test.js +0 -968
  67. package/dist/__tests__/initiative-add-wu.test.js +0 -490
  68. package/dist/__tests__/initiative-e2e.test.js +0 -442
  69. package/dist/__tests__/initiative-plan-replacement.test.js +0 -161
  70. package/dist/__tests__/initiative-plan.test.js +0 -340
  71. package/dist/__tests__/initiative-remove-wu.test.js +0 -458
  72. package/dist/__tests__/lumenflow-upgrade.test.js +0 -260
  73. package/dist/__tests__/mem-cleanup-execution.test.js +0 -19
  74. package/dist/__tests__/memory-integration.test.js +0 -333
  75. package/dist/__tests__/merge-block.test.js +0 -220
  76. package/dist/__tests__/metrics-cli.test.js +0 -619
  77. package/dist/__tests__/metrics-snapshot.test.js +0 -24
  78. package/dist/__tests__/no-beacon-references-docs.test.js +0 -30
  79. package/dist/__tests__/no-beacon-references.test.js +0 -39
  80. package/dist/__tests__/onboarding-smoke-test.test.js +0 -211
  81. package/dist/__tests__/path-centralization-cli.test.js +0 -234
  82. package/dist/__tests__/plan-create.test.js +0 -126
  83. package/dist/__tests__/plan-edit.test.js +0 -157
  84. package/dist/__tests__/plan-link.test.js +0 -239
  85. package/dist/__tests__/plan-promote.test.js +0 -181
  86. package/dist/__tests__/release.test.js +0 -372
  87. package/dist/__tests__/rotate-progress.test.js +0 -127
  88. package/dist/__tests__/safe-git.test.js +0 -190
  89. package/dist/__tests__/session-coordinator.test.js +0 -109
  90. package/dist/__tests__/state-bootstrap.test.js +0 -432
  91. package/dist/__tests__/state-doctor.test.js +0 -328
  92. package/dist/__tests__/sync-templates.test.js +0 -255
  93. package/dist/__tests__/templates-sync.test.js +0 -219
  94. package/dist/__tests__/trace-gen.test.js +0 -115
  95. package/dist/__tests__/wu-create-required-fields.test.js +0 -143
  96. package/dist/__tests__/wu-create-strict.test.js +0 -118
  97. package/dist/__tests__/wu-create.test.js +0 -121
  98. package/dist/__tests__/wu-done-auto-cleanup.test.js +0 -135
  99. package/dist/__tests__/wu-done-docs-only-policy.test.js +0 -20
  100. package/dist/__tests__/wu-done-staging-whitelist.test.js +0 -35
  101. package/dist/__tests__/wu-done.test.js +0 -36
  102. package/dist/__tests__/wu-edit-strict.test.js +0 -109
  103. package/dist/__tests__/wu-edit.test.js +0 -119
  104. package/dist/__tests__/wu-lifecycle-integration.test.js +0 -388
  105. package/dist/__tests__/wu-prep-default-exec.test.js +0 -35
  106. package/dist/__tests__/wu-prep.test.js +0 -140
  107. package/dist/__tests__/wu-proto.test.js +0 -97
  108. package/dist/__tests__/wu-validate-strict.test.js +0 -113
  109. package/dist/__tests__/wu-validate.test.js +0 -36
  110. package/dist/spawn-list.js +0 -143
  111. package/dist/spawn-list.js.map +0 -1
@@ -1,619 +0,0 @@
1
- /**
2
- * @file metrics-cli.test.ts
3
- * @description Tests for unified metrics CLI with subcommands (WU-1110)
4
- *
5
- * TDD: RED phase - Write tests before implementation.
6
- *
7
- * Acceptance criteria:
8
- * - metrics-cli.ts exists with subcommands (lanes, dora, flow)
9
- * - All metrics/application modules migrated
10
- * - Existing tests ported to Vitest
11
- * - >80% coverage
12
- */
13
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
14
- import { existsSync } from 'node:fs';
15
- import { join } from 'node:path';
16
- import { fileURLToPath } from 'node:url';
17
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
18
- describe('metrics-cli module', () => {
19
- it('should have the CLI source file', () => {
20
- const srcPath = join(__dirname, '../metrics-cli.ts');
21
- expect(existsSync(srcPath)).toBe(true);
22
- });
23
- it('should be buildable (dist file exists after build)', () => {
24
- const distPath = join(__dirname, '../../dist/metrics-cli.js');
25
- expect(existsSync(distPath)).toBe(true);
26
- });
27
- });
28
- describe('metrics-cli subcommands', () => {
29
- // Mock console output
30
- let consoleSpy;
31
- let consoleErrorSpy;
32
- beforeEach(() => {
33
- consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
34
- consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
35
- });
36
- afterEach(() => {
37
- consoleSpy.mockRestore();
38
- consoleErrorSpy.mockRestore();
39
- vi.resetModules();
40
- });
41
- describe('parseCommand', () => {
42
- it('should export parseCommand function', async () => {
43
- const { parseCommand } = await import('../metrics-cli.js');
44
- expect(typeof parseCommand).toBe('function');
45
- });
46
- it('should parse "lanes" subcommand', async () => {
47
- const { parseCommand } = await import('../metrics-cli.js');
48
- const result = parseCommand(['node', 'metrics', 'lanes']);
49
- expect(result.subcommand).toBe('lanes');
50
- });
51
- it('should parse "dora" subcommand', async () => {
52
- const { parseCommand } = await import('../metrics-cli.js');
53
- const result = parseCommand(['node', 'metrics', 'dora']);
54
- expect(result.subcommand).toBe('dora');
55
- });
56
- it('should parse "flow" subcommand', async () => {
57
- const { parseCommand } = await import('../metrics-cli.js');
58
- const result = parseCommand(['node', 'metrics', 'flow']);
59
- expect(result.subcommand).toBe('flow');
60
- });
61
- it('should default to "all" when no subcommand given', async () => {
62
- const { parseCommand } = await import('../metrics-cli.js');
63
- const result = parseCommand(['node', 'metrics']);
64
- expect(result.subcommand).toBe('all');
65
- });
66
- it('should parse --days option', async () => {
67
- const { parseCommand } = await import('../metrics-cli.js');
68
- const result = parseCommand(['node', 'metrics', 'dora', '--days', '30']);
69
- expect(result.days).toBe(30);
70
- });
71
- it('should parse --format option', async () => {
72
- const { parseCommand } = await import('../metrics-cli.js');
73
- const result = parseCommand(['node', 'metrics', 'lanes', '--format', 'table']);
74
- expect(result.format).toBe('table');
75
- });
76
- it('should parse --output option', async () => {
77
- const { parseCommand } = await import('../metrics-cli.js');
78
- const result = parseCommand(['node', 'metrics', '--output', 'custom.json']);
79
- expect(result.output).toBe('custom.json');
80
- });
81
- it('should parse --dry-run flag', async () => {
82
- const { parseCommand } = await import('../metrics-cli.js');
83
- const result = parseCommand(['node', 'metrics', '--dry-run']);
84
- expect(result.dryRun).toBe(true);
85
- });
86
- });
87
- describe('MetricsCommandResult type', () => {
88
- it('should have proper subcommand type', async () => {
89
- const { parseCommand } = await import('../metrics-cli.js');
90
- const result = parseCommand(['node', 'metrics', 'dora']);
91
- // Type check: subcommand should be one of 'lanes' | 'dora' | 'flow' | 'all'
92
- expect(['lanes', 'dora', 'flow', 'all']).toContain(result.subcommand);
93
- });
94
- });
95
- describe('runLanesSubcommand', () => {
96
- it('should export runLanesSubcommand function', async () => {
97
- const { runLanesSubcommand } = await import('../metrics-cli.js');
98
- expect(typeof runLanesSubcommand).toBe('function');
99
- });
100
- });
101
- describe('runDoraSubcommand', () => {
102
- it('should export runDoraSubcommand function', async () => {
103
- const { runDoraSubcommand } = await import('../metrics-cli.js');
104
- expect(typeof runDoraSubcommand).toBe('function');
105
- });
106
- });
107
- describe('runFlowSubcommand', () => {
108
- it('should export runFlowSubcommand function', async () => {
109
- const { runFlowSubcommand } = await import('../metrics-cli.js');
110
- expect(typeof runFlowSubcommand).toBe('function');
111
- });
112
- });
113
- describe('runAllSubcommand', () => {
114
- it('should export runAllSubcommand function', async () => {
115
- const { runAllSubcommand } = await import('../metrics-cli.js');
116
- expect(typeof runAllSubcommand).toBe('function');
117
- });
118
- });
119
- });
120
- describe('metrics-cli integration', () => {
121
- describe('lanes subcommand', () => {
122
- it('should calculate lane health from WU data', async () => {
123
- const { calculateLaneHealthFromWUs } = await import('../metrics-cli.js');
124
- const wuMetrics = [
125
- { id: 'WU-1', title: 'A', lane: 'Framework: Core', status: 'done' },
126
- { id: 'WU-2', title: 'B', lane: 'Framework: Core', status: 'in_progress' },
127
- { id: 'WU-3', title: 'C', lane: 'Framework: CLI', status: 'blocked' },
128
- ];
129
- const result = calculateLaneHealthFromWUs(wuMetrics);
130
- expect(result.lanes).toBeDefined();
131
- expect(result.lanes.length).toBeGreaterThan(0);
132
- expect(result.totalActive).toBeGreaterThanOrEqual(0);
133
- expect(result.totalBlocked).toBe(1);
134
- });
135
- });
136
- describe('dora subcommand', () => {
137
- it('should calculate DORA metrics from commits and WUs', async () => {
138
- const { calculateDoraFromData } = await import('../metrics-cli.js');
139
- const commits = [
140
- { hash: 'a1', timestamp: new Date('2026-01-02'), message: 'feat: add feature' },
141
- { hash: 'a2', timestamp: new Date('2026-01-03'), message: 'fix: bug fix' },
142
- ];
143
- const wuMetrics = [
144
- {
145
- id: 'WU-1',
146
- title: 'A',
147
- lane: 'Ops',
148
- status: 'done',
149
- cycleTimeHours: 12,
150
- },
151
- ];
152
- const skipGatesEntries = [];
153
- const weekStart = new Date('2026-01-01');
154
- const weekEnd = new Date('2026-01-07');
155
- const result = calculateDoraFromData({
156
- commits,
157
- wuMetrics,
158
- skipGatesEntries,
159
- weekStart,
160
- weekEnd,
161
- });
162
- expect(result.deploymentFrequency).toBeDefined();
163
- expect(result.leadTimeForChanges).toBeDefined();
164
- expect(result.changeFailureRate).toBeDefined();
165
- expect(result.meanTimeToRecovery).toBeDefined();
166
- });
167
- });
168
- describe('flow subcommand', () => {
169
- it('should calculate flow state from WU data', async () => {
170
- const { calculateFlowFromWUs } = await import('../metrics-cli.js');
171
- const wuMetrics = [
172
- { id: 'WU-1', title: 'A', lane: 'Ops', status: 'ready' },
173
- { id: 'WU-2', title: 'B', lane: 'Ops', status: 'in_progress' },
174
- { id: 'WU-3', title: 'C', lane: 'Ops', status: 'blocked' },
175
- { id: 'WU-4', title: 'D', lane: 'Ops', status: 'done' },
176
- ];
177
- const result = calculateFlowFromWUs(wuMetrics);
178
- expect(result.ready).toBe(1);
179
- expect(result.inProgress).toBe(1);
180
- expect(result.blocked).toBe(1);
181
- expect(result.done).toBe(1);
182
- expect(result.totalActive).toBe(3); // ready + in_progress + blocked
183
- });
184
- });
185
- });
186
- describe('metrics-cli formatters', () => {
187
- describe('formatLanesOutput', () => {
188
- it('should export formatLanesOutput function', async () => {
189
- const { formatLanesOutput } = await import('../metrics-cli.js');
190
- expect(typeof formatLanesOutput).toBe('function');
191
- });
192
- it('should format lanes as JSON by default', async () => {
193
- const { formatLanesOutput } = await import('../metrics-cli.js');
194
- const lanes = {
195
- lanes: [
196
- {
197
- lane: 'Framework: Core',
198
- wusCompleted: 5,
199
- wusInProgress: 2,
200
- wusBlocked: 0,
201
- averageCycleTimeHours: 24,
202
- medianCycleTimeHours: 20,
203
- status: 'healthy',
204
- },
205
- ],
206
- totalActive: 7,
207
- totalBlocked: 0,
208
- totalCompleted: 5,
209
- };
210
- const result = formatLanesOutput(lanes, 'json');
211
- expect(() => JSON.parse(result)).not.toThrow();
212
- });
213
- it('should format lanes as table', async () => {
214
- const { formatLanesOutput } = await import('../metrics-cli.js');
215
- const lanes = {
216
- lanes: [
217
- {
218
- lane: 'Framework: Core',
219
- wusCompleted: 5,
220
- wusInProgress: 2,
221
- wusBlocked: 0,
222
- averageCycleTimeHours: 24,
223
- medianCycleTimeHours: 20,
224
- status: 'healthy',
225
- },
226
- ],
227
- totalActive: 7,
228
- totalBlocked: 0,
229
- totalCompleted: 5,
230
- };
231
- const result = formatLanesOutput(lanes, 'table');
232
- expect(result).toContain('Framework: Core');
233
- expect(result).toContain('[ok]'); // [ok] = healthy, [!] = at-risk, [x] = blocked
234
- });
235
- });
236
- describe('formatDoraOutput', () => {
237
- it('should export formatDoraOutput function', async () => {
238
- const { formatDoraOutput } = await import('../metrics-cli.js');
239
- expect(typeof formatDoraOutput).toBe('function');
240
- });
241
- it('should format DORA as JSON', async () => {
242
- const { formatDoraOutput } = await import('../metrics-cli.js');
243
- const dora = {
244
- deploymentFrequency: { deploysPerWeek: 5, status: 'elite' },
245
- leadTimeForChanges: {
246
- averageHours: 12,
247
- medianHours: 10,
248
- p90Hours: 20,
249
- status: 'elite',
250
- },
251
- changeFailureRate: {
252
- failurePercentage: 5,
253
- totalDeployments: 100,
254
- failures: 5,
255
- status: 'elite',
256
- },
257
- meanTimeToRecovery: { averageHours: 1, incidents: 2, status: 'elite' },
258
- };
259
- const result = formatDoraOutput(dora, 'json');
260
- expect(() => JSON.parse(result)).not.toThrow();
261
- const parsed = JSON.parse(result);
262
- expect(parsed.deploymentFrequency.deploysPerWeek).toBe(5);
263
- });
264
- it('should format DORA as table', async () => {
265
- const { formatDoraOutput } = await import('../metrics-cli.js');
266
- const dora = {
267
- deploymentFrequency: { deploysPerWeek: 5, status: 'elite' },
268
- leadTimeForChanges: {
269
- averageHours: 12,
270
- medianHours: 10,
271
- p90Hours: 20,
272
- status: 'elite',
273
- },
274
- changeFailureRate: {
275
- failurePercentage: 5,
276
- totalDeployments: 100,
277
- failures: 5,
278
- status: 'elite',
279
- },
280
- meanTimeToRecovery: { averageHours: 1, incidents: 2, status: 'elite' },
281
- };
282
- const result = formatDoraOutput(dora, 'table');
283
- expect(result).toContain('DORA METRICS');
284
- expect(result).toContain('Deployment Frequency');
285
- expect(result).toContain('elite');
286
- expect(result).toContain('Lead Time');
287
- expect(result).toContain('MTTR');
288
- });
289
- });
290
- describe('formatFlowOutput', () => {
291
- it('should export formatFlowOutput function', async () => {
292
- const { formatFlowOutput } = await import('../metrics-cli.js');
293
- expect(typeof formatFlowOutput).toBe('function');
294
- });
295
- it('should format flow as JSON', async () => {
296
- const { formatFlowOutput } = await import('../metrics-cli.js');
297
- const flow = {
298
- ready: 5,
299
- inProgress: 3,
300
- blocked: 1,
301
- waiting: 0,
302
- done: 10,
303
- totalActive: 9,
304
- };
305
- const result = formatFlowOutput(flow, 'json');
306
- expect(() => JSON.parse(result)).not.toThrow();
307
- const parsed = JSON.parse(result);
308
- expect(parsed.ready).toBe(5);
309
- expect(parsed.totalActive).toBe(9);
310
- });
311
- it('should format flow as table', async () => {
312
- const { formatFlowOutput } = await import('../metrics-cli.js');
313
- const flow = {
314
- ready: 5,
315
- inProgress: 3,
316
- blocked: 1,
317
- waiting: 0,
318
- done: 10,
319
- totalActive: 9,
320
- };
321
- const result = formatFlowOutput(flow, 'table');
322
- expect(result).toContain('FLOW STATE');
323
- expect(result).toContain('Ready: 5');
324
- expect(result).toContain('In Progress: 3');
325
- expect(result).toContain('Blocked: 1');
326
- expect(result).toContain('Done: 10');
327
- });
328
- });
329
- describe('formatLanesOutput edge cases', () => {
330
- it('should handle at-risk status', async () => {
331
- const { formatLanesOutput } = await import('../metrics-cli.js');
332
- const lanes = {
333
- lanes: [
334
- {
335
- lane: 'Framework: CLI',
336
- wusCompleted: 2,
337
- wusInProgress: 1,
338
- wusBlocked: 1,
339
- averageCycleTimeHours: 48,
340
- medianCycleTimeHours: 40,
341
- status: 'at-risk',
342
- },
343
- ],
344
- totalActive: 4,
345
- totalBlocked: 1,
346
- totalCompleted: 2,
347
- };
348
- const result = formatLanesOutput(lanes, 'table');
349
- expect(result).toContain('[!]'); // at-risk indicator
350
- });
351
- it('should handle blocked status', async () => {
352
- const { formatLanesOutput } = await import('../metrics-cli.js');
353
- const lanes = {
354
- lanes: [
355
- {
356
- lane: 'Operations',
357
- wusCompleted: 0,
358
- wusInProgress: 0,
359
- wusBlocked: 3,
360
- averageCycleTimeHours: 0,
361
- medianCycleTimeHours: 0,
362
- status: 'blocked',
363
- },
364
- ],
365
- totalActive: 3,
366
- totalBlocked: 3,
367
- totalCompleted: 0,
368
- };
369
- const result = formatLanesOutput(lanes, 'table');
370
- expect(result).toContain('[x]'); // blocked indicator
371
- });
372
- it('should handle empty lanes array', async () => {
373
- const { formatLanesOutput } = await import('../metrics-cli.js');
374
- const lanes = {
375
- lanes: [],
376
- totalActive: 0,
377
- totalBlocked: 0,
378
- totalCompleted: 0,
379
- };
380
- const result = formatLanesOutput(lanes, 'table');
381
- expect(result).toContain('LANE HEALTH');
382
- expect(result).toContain('Total Active: 0');
383
- });
384
- });
385
- });
386
- describe('metrics-cli parseCommand edge cases', () => {
387
- it('should handle invalid subcommand gracefully', async () => {
388
- const { parseCommand } = await import('../metrics-cli.js');
389
- const result = parseCommand(['node', 'metrics', 'invalid']);
390
- // Invalid subcommand should default to 'all'
391
- expect(result.subcommand).toBe('all');
392
- });
393
- it('should handle explicit "all" subcommand', async () => {
394
- const { parseCommand } = await import('../metrics-cli.js');
395
- const result = parseCommand(['node', 'metrics', 'all']);
396
- expect(result.subcommand).toBe('all');
397
- });
398
- it('should use default days when not specified', async () => {
399
- const { parseCommand } = await import('../metrics-cli.js');
400
- const result = parseCommand(['node', 'metrics']);
401
- expect(result.days).toBe(7);
402
- });
403
- it('should use default format when not specified', async () => {
404
- const { parseCommand } = await import('../metrics-cli.js');
405
- const result = parseCommand(['node', 'metrics']);
406
- expect(result.format).toBe('json');
407
- });
408
- it('should use default output path when not specified', async () => {
409
- const { parseCommand } = await import('../metrics-cli.js');
410
- const result = parseCommand(['node', 'metrics']);
411
- expect(result.output).toContain('.lumenflow/snapshots/metrics-latest.json');
412
- });
413
- it('should handle dryRun false by default', async () => {
414
- const { parseCommand } = await import('../metrics-cli.js');
415
- const result = parseCommand(['node', 'metrics']);
416
- expect(result.dryRun).toBe(false);
417
- });
418
- });
419
- describe('metrics-cli calculation functions', () => {
420
- describe('calculateLaneHealthFromWUs', () => {
421
- it('should handle empty WU list', async () => {
422
- const { calculateLaneHealthFromWUs } = await import('../metrics-cli.js');
423
- const result = calculateLaneHealthFromWUs([]);
424
- expect(result.lanes).toEqual([]);
425
- expect(result.totalActive).toBe(0);
426
- });
427
- it('should correctly count ready WUs as active', async () => {
428
- const { calculateLaneHealthFromWUs } = await import('../metrics-cli.js');
429
- const wuMetrics = [{ id: 'WU-1', title: 'A', lane: 'Test', status: 'ready' }];
430
- const result = calculateLaneHealthFromWUs(wuMetrics);
431
- expect(result.totalActive).toBe(1);
432
- });
433
- it('should correctly count waiting WUs as active', async () => {
434
- const { calculateLaneHealthFromWUs } = await import('../metrics-cli.js');
435
- const wuMetrics = [{ id: 'WU-1', title: 'A', lane: 'Test', status: 'waiting' }];
436
- const result = calculateLaneHealthFromWUs(wuMetrics);
437
- expect(result.totalActive).toBe(1);
438
- });
439
- });
440
- describe('calculateDoraFromData', () => {
441
- it('should handle empty commit list', async () => {
442
- const { calculateDoraFromData } = await import('../metrics-cli.js');
443
- const result = calculateDoraFromData({
444
- commits: [],
445
- wuMetrics: [],
446
- skipGatesEntries: [],
447
- weekStart: new Date('2026-01-01'),
448
- weekEnd: new Date('2026-01-07'),
449
- });
450
- expect(result.deploymentFrequency.deploysPerWeek).toBe(0);
451
- });
452
- it('should handle skip gates entries', async () => {
453
- const { calculateDoraFromData } = await import('../metrics-cli.js');
454
- const commits = [
455
- { hash: 'a1', timestamp: new Date('2026-01-02'), message: 'feat: a' },
456
- { hash: 'a2', timestamp: new Date('2026-01-03'), message: 'feat: b' },
457
- ];
458
- const skipGatesEntries = [
459
- { timestamp: new Date(), wuId: 'WU-1', reason: 'test', gate: 'lint' },
460
- ];
461
- const result = calculateDoraFromData({
462
- commits,
463
- wuMetrics: [],
464
- skipGatesEntries,
465
- weekStart: new Date('2026-01-01'),
466
- weekEnd: new Date('2026-01-07'),
467
- });
468
- expect(result.changeFailureRate.failures).toBe(1);
469
- });
470
- });
471
- describe('calculateFlowFromWUs', () => {
472
- it('should handle all WU statuses', async () => {
473
- const { calculateFlowFromWUs } = await import('../metrics-cli.js');
474
- const wuMetrics = [
475
- { id: 'WU-1', title: 'A', lane: 'Ops', status: 'ready' },
476
- { id: 'WU-2', title: 'B', lane: 'Ops', status: 'in_progress' },
477
- { id: 'WU-3', title: 'C', lane: 'Ops', status: 'blocked' },
478
- { id: 'WU-4', title: 'D', lane: 'Ops', status: 'waiting' },
479
- { id: 'WU-5', title: 'E', lane: 'Ops', status: 'done' },
480
- ];
481
- const result = calculateFlowFromWUs(wuMetrics);
482
- expect(result.ready).toBe(1);
483
- expect(result.inProgress).toBe(1);
484
- expect(result.blocked).toBe(1);
485
- expect(result.waiting).toBe(1);
486
- expect(result.done).toBe(1);
487
- expect(result.totalActive).toBe(4); // ready + in_progress + blocked + waiting
488
- });
489
- it('should handle empty WU list', async () => {
490
- const { calculateFlowFromWUs } = await import('../metrics-cli.js');
491
- const result = calculateFlowFromWUs([]);
492
- expect(result.ready).toBe(0);
493
- expect(result.totalActive).toBe(0);
494
- });
495
- });
496
- });
497
- describe('metrics-cli run* subcommand functions', () => {
498
- let consoleSpy;
499
- let consoleWarnSpy;
500
- beforeEach(() => {
501
- consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
502
- consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
503
- });
504
- afterEach(() => {
505
- consoleSpy.mockRestore();
506
- consoleWarnSpy.mockRestore();
507
- });
508
- describe('runLanesSubcommand', () => {
509
- it('should run lanes subcommand with dry-run', async () => {
510
- const { runLanesSubcommand } = await import('../metrics-cli.js');
511
- // Run with dry-run to avoid file writes
512
- await runLanesSubcommand({
513
- subcommand: 'lanes',
514
- days: 7,
515
- format: 'json',
516
- output: '.lumenflow/snapshots/test.json',
517
- dryRun: true,
518
- });
519
- // Verify console output was called
520
- expect(consoleSpy).toHaveBeenCalled();
521
- });
522
- it('should run lanes subcommand with table format', async () => {
523
- const { runLanesSubcommand } = await import('../metrics-cli.js');
524
- await runLanesSubcommand({
525
- subcommand: 'lanes',
526
- days: 7,
527
- format: 'table',
528
- output: '.lumenflow/snapshots/test.json',
529
- dryRun: true,
530
- });
531
- // Verify table format was used (contains LANE HEALTH header)
532
- const calls = consoleSpy.mock.calls;
533
- const output = calls.map((c) => String(c[0])).join('\n');
534
- expect(output).toContain('LANE HEALTH');
535
- });
536
- });
537
- describe('runDoraSubcommand', () => {
538
- it('should run dora subcommand with dry-run', async () => {
539
- const { runDoraSubcommand } = await import('../metrics-cli.js');
540
- await runDoraSubcommand({
541
- subcommand: 'dora',
542
- days: 7,
543
- format: 'json',
544
- output: '.lumenflow/snapshots/test.json',
545
- dryRun: true,
546
- });
547
- expect(consoleSpy).toHaveBeenCalled();
548
- });
549
- it('should run dora subcommand with custom days', async () => {
550
- const { runDoraSubcommand } = await import('../metrics-cli.js');
551
- await runDoraSubcommand({
552
- subcommand: 'dora',
553
- days: 30,
554
- format: 'table',
555
- output: '.lumenflow/snapshots/test.json',
556
- dryRun: true,
557
- });
558
- // Verify output includes DORA METRICS header
559
- const calls = consoleSpy.mock.calls;
560
- const output = calls.map((c) => String(c[0])).join('\n');
561
- expect(output).toContain('DORA METRICS');
562
- });
563
- });
564
- describe('runFlowSubcommand', () => {
565
- it('should run flow subcommand with dry-run', async () => {
566
- const { runFlowSubcommand } = await import('../metrics-cli.js');
567
- await runFlowSubcommand({
568
- subcommand: 'flow',
569
- days: 7,
570
- format: 'json',
571
- output: '.lumenflow/snapshots/test.json',
572
- dryRun: true,
573
- });
574
- expect(consoleSpy).toHaveBeenCalled();
575
- });
576
- it('should run flow subcommand with table format', async () => {
577
- const { runFlowSubcommand } = await import('../metrics-cli.js');
578
- await runFlowSubcommand({
579
- subcommand: 'flow',
580
- days: 7,
581
- format: 'table',
582
- output: '.lumenflow/snapshots/test.json',
583
- dryRun: true,
584
- });
585
- const calls = consoleSpy.mock.calls;
586
- const output = calls.map((c) => String(c[0])).join('\n');
587
- expect(output).toContain('FLOW STATE');
588
- });
589
- });
590
- describe('runAllSubcommand', () => {
591
- it('should run all subcommand with dry-run', async () => {
592
- const { runAllSubcommand } = await import('../metrics-cli.js');
593
- await runAllSubcommand({
594
- subcommand: 'all',
595
- days: 7,
596
- format: 'json',
597
- output: '.lumenflow/snapshots/test.json',
598
- dryRun: true,
599
- });
600
- expect(consoleSpy).toHaveBeenCalled();
601
- });
602
- it('should run all subcommand with table format', async () => {
603
- const { runAllSubcommand } = await import('../metrics-cli.js');
604
- await runAllSubcommand({
605
- subcommand: 'all',
606
- days: 7,
607
- format: 'table',
608
- output: '.lumenflow/snapshots/test.json',
609
- dryRun: true,
610
- });
611
- const calls = consoleSpy.mock.calls;
612
- const output = calls.map((c) => String(c[0])).join('\n');
613
- // Should contain all three sections
614
- expect(output).toContain('DORA METRICS');
615
- expect(output).toContain('LANE HEALTH');
616
- expect(output).toContain('FLOW STATE');
617
- });
618
- });
619
- });
@@ -1,24 +0,0 @@
1
- /**
2
- * @file metrics-snapshot.test.ts
3
- * @description Tests for metrics-snapshot CLI command (WU-1020)
4
- *
5
- * These are smoke tests to verify the CLI module can be imported and
6
- * that the TypeScript compilation succeeds (verifying the readonly array fix).
7
- */
8
- import { describe, it, expect } from 'vitest';
9
- import { existsSync } from 'node:fs';
10
- import { join } from 'node:path';
11
- import { fileURLToPath } from 'node:url';
12
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
13
- describe('metrics-snapshot CLI', () => {
14
- it('should have the CLI source file', () => {
15
- const srcPath = join(__dirname, '../metrics-snapshot.ts');
16
- expect(existsSync(srcPath)).toBe(true);
17
- });
18
- it('should be buildable (dist file exists after build)', () => {
19
- // This test verifies that tsc compiled the file successfully
20
- // WU-1020: The readonly array cast fix allows this file to compile
21
- const distPath = join(__dirname, '../../dist/metrics-snapshot.js');
22
- expect(existsSync(distPath)).toBe(true);
23
- });
24
- });
@@ -1,30 +0,0 @@
1
- /**
2
- * @file no-beacon-references-docs.test.ts
3
- * Guardrail test: public + onboarding docs must not reference legacy `.beacon` paths (WU-1450).
4
- */
5
- import { describe, it, expect } from 'vitest';
6
- import { readFileSync } from 'node:fs';
7
- import path from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- function repoRootFromThisFile() {
10
- // packages/@lumenflow/cli/src/__tests__/no-beacon-references-docs.test.ts -> repo root
11
- const thisDir = path.dirname(fileURLToPath(import.meta.url));
12
- return path.resolve(thisDir, '..', '..', '..', '..', '..');
13
- }
14
- describe('no legacy .beacon references in docs (WU-1450)', () => {
15
- it('should not contain .beacon references in onboarding/public docs', () => {
16
- const repoRoot = repoRootFromThisFile();
17
- const files = [
18
- 'docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-safety-card.md',
19
- 'docs/04-operations/_frameworks/lumenflow/agent/onboarding/lumenflow-force-usage.md',
20
- 'apps/docs/src/content/docs/getting-started/upgrade.mdx',
21
- 'apps/docs/src/content/docs/reference/changelog.mdx',
22
- 'apps/docs/src/content/docs/reference/compatibility.mdx',
23
- ];
24
- for (const relPath of files) {
25
- const absPath = path.join(repoRoot, relPath);
26
- const content = readFileSync(absPath, 'utf-8');
27
- expect(content, `${relPath} should not reference .beacon`).not.toContain('.beacon');
28
- }
29
- });
30
- });