@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,121 +0,0 @@
1
- /**
2
- * @file wu-create.test.ts
3
- * Tests for wu:create helpers and warnings (WU-1429)
4
- */
5
- import { describe, it, expect } from 'vitest';
6
- import { buildWUContent, collectInitiativeWarnings, validateCreateSpec } from '../wu-create.js';
7
- const BASE_WU = {
8
- id: 'WU-1429',
9
- lane: 'Framework: CLI',
10
- title: 'Test WU',
11
- priority: 'P2',
12
- type: 'feature',
13
- created: '2026-02-04',
14
- opts: {
15
- description: 'Context: test context.\nProblem: test problem.\nSolution: test solution that exceeds minimum.',
16
- acceptance: ['Acceptance criterion'],
17
- exposure: 'backend-only',
18
- codePaths: ['packages/@lumenflow/cli/src/wu-create.ts'],
19
- testPathsUnit: ['packages/@lumenflow/cli/src/__tests__/wu-create.test.ts'],
20
- specRefs: ['lumenflow://plans/WU-1429-plan.md'],
21
- },
22
- };
23
- describe('wu:create helpers (WU-1429)', () => {
24
- it('should default notes to non-empty placeholder when not provided', () => {
25
- const wu = buildWUContent({
26
- ...BASE_WU,
27
- opts: {
28
- ...BASE_WU.opts,
29
- // Intentionally omit notes
30
- notes: undefined,
31
- },
32
- });
33
- expect(typeof wu.notes).toBe('string');
34
- expect(wu.notes.trim().length).toBeGreaterThan(0);
35
- expect(wu.notes).toContain('(auto)');
36
- });
37
- it('should persist notes when provided', () => {
38
- const wu = buildWUContent({
39
- ...BASE_WU,
40
- opts: {
41
- ...BASE_WU.opts,
42
- notes: 'Implementation notes for test',
43
- },
44
- });
45
- expect(wu.notes).toBe('Implementation notes for test');
46
- });
47
- it('should allow creating a plan-first WU without explicit test flags when code_paths are non-code', () => {
48
- const validation = validateCreateSpec({
49
- id: 'WU-2000',
50
- lane: 'Framework: CLI',
51
- title: 'Plan-only spec creation',
52
- priority: 'P2',
53
- type: 'feature',
54
- opts: {
55
- description: 'Context: test context.\nProblem: test problem.\nSolution: test solution that exceeds minimum.',
56
- acceptance: ['Acceptance criterion'],
57
- exposure: 'backend-only',
58
- // Non-code file path: manual-only tests are acceptable.
59
- codePaths: ['docs/README.md'],
60
- // No testPathsManual/unit/e2e provided - should auto-default manual stub.
61
- specRefs: ['lumenflow://plans/WU-2000-plan.md'],
62
- strict: false,
63
- },
64
- });
65
- expect(validation.valid).toBe(true);
66
- const wu = buildWUContent({
67
- id: 'WU-2000',
68
- lane: 'Framework: CLI',
69
- title: 'Plan-only spec creation',
70
- priority: 'P2',
71
- type: 'feature',
72
- created: '2026-02-05',
73
- opts: {
74
- description: 'Context: test context.\nProblem: test problem.\nSolution: test solution that exceeds minimum.',
75
- acceptance: ['Acceptance criterion'],
76
- exposure: 'backend-only',
77
- codePaths: ['docs/README.md'],
78
- specRefs: ['lumenflow://plans/WU-2000-plan.md'],
79
- },
80
- });
81
- expect(wu.tests?.manual?.length).toBeGreaterThan(0);
82
- });
83
- it('should warn when initiative has phases but no --phase is provided', () => {
84
- const warnings = collectInitiativeWarnings({
85
- initiativeId: 'INIT-TEST',
86
- initiativeDoc: {
87
- phases: [{ id: 1, title: 'Phase 1' }],
88
- },
89
- phase: undefined,
90
- specRefs: ['lumenflow://plans/WU-1429-plan.md'],
91
- });
92
- expect(warnings).toEqual(expect.arrayContaining([
93
- 'Initiative INIT-TEST has phases defined. Consider adding --phase to link this WU to a phase.',
94
- ]));
95
- });
96
- it('should warn when initiative has related_plan but no spec_refs', () => {
97
- const warnings = collectInitiativeWarnings({
98
- initiativeId: 'INIT-TEST',
99
- initiativeDoc: {
100
- related_plan: 'lumenflow://plans/INIT-TEST-plan.md',
101
- },
102
- phase: '1',
103
- specRefs: [],
104
- });
105
- expect(warnings).toEqual(expect.arrayContaining([
106
- 'Initiative INIT-TEST has related_plan (lumenflow://plans/INIT-TEST-plan.md). Consider adding --spec-refs to link this WU to the plan.',
107
- ]));
108
- });
109
- it('should not warn when phase and spec_refs are provided', () => {
110
- const warnings = collectInitiativeWarnings({
111
- initiativeId: 'INIT-TEST',
112
- initiativeDoc: {
113
- phases: [{ id: 1, title: 'Phase 1' }],
114
- related_plan: 'lumenflow://plans/INIT-TEST-plan.md',
115
- },
116
- phase: '1',
117
- specRefs: ['lumenflow://plans/WU-1429-plan.md'],
118
- });
119
- expect(warnings).toEqual([]);
120
- });
121
- });
@@ -1,135 +0,0 @@
1
- /**
2
- * @file wu-done-auto-cleanup.test.ts
3
- * Test suite for wu:done auto cleanup on success (WU-1366)
4
- *
5
- * WU-1366: State cleanup runs automatically after wu:done success (non-fatal)
6
- *
7
- * Tests:
8
- * - shouldRunAutoCleanup respects config.cleanup.trigger setting
9
- * - runAutoCleanupAfterDone is non-fatal (logs errors but doesn't throw)
10
- */
11
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
12
- /** Common mock path for lumenflow config module */
13
- const CONFIG_MODULE_PATH = '@lumenflow/core/dist/lumenflow-config.js';
14
- // Test the exported functions directly with minimal mocking
15
- describe('wu:done auto cleanup (WU-1366)', () => {
16
- let consoleLogSpy;
17
- let consoleWarnSpy;
18
- beforeEach(() => {
19
- consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
20
- consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
21
- });
22
- afterEach(() => {
23
- consoleLogSpy.mockRestore();
24
- consoleWarnSpy.mockRestore();
25
- vi.resetModules();
26
- });
27
- describe('shouldRunAutoCleanup', () => {
28
- it('should return true when config.cleanup.trigger is on_done', async () => {
29
- // Mock getConfig to return on_done trigger
30
- vi.doMock(CONFIG_MODULE_PATH, () => ({
31
- getConfig: vi.fn().mockReturnValue({
32
- cleanup: { trigger: 'on_done' },
33
- }),
34
- }));
35
- const { shouldRunAutoCleanup } = await import('../wu-done-auto-cleanup.js');
36
- const result = shouldRunAutoCleanup();
37
- expect(result).toBe(true);
38
- });
39
- it('should return false when config.cleanup.trigger is manual', async () => {
40
- vi.doMock(CONFIG_MODULE_PATH, () => ({
41
- getConfig: vi.fn().mockReturnValue({
42
- cleanup: { trigger: 'manual' },
43
- }),
44
- }));
45
- const { shouldRunAutoCleanup } = await import('../wu-done-auto-cleanup.js');
46
- const result = shouldRunAutoCleanup();
47
- expect(result).toBe(false);
48
- });
49
- it('should return false when config.cleanup.trigger is on_init', async () => {
50
- vi.doMock(CONFIG_MODULE_PATH, () => ({
51
- getConfig: vi.fn().mockReturnValue({
52
- cleanup: { trigger: 'on_init' },
53
- }),
54
- }));
55
- const { shouldRunAutoCleanup } = await import('../wu-done-auto-cleanup.js');
56
- const result = shouldRunAutoCleanup();
57
- expect(result).toBe(false);
58
- });
59
- it('should return true when cleanup config is missing (default behavior)', async () => {
60
- vi.doMock(CONFIG_MODULE_PATH, () => ({
61
- getConfig: vi.fn().mockReturnValue({}),
62
- }));
63
- const { shouldRunAutoCleanup } = await import('../wu-done-auto-cleanup.js');
64
- const result = shouldRunAutoCleanup();
65
- expect(result).toBe(true);
66
- });
67
- });
68
- describe('runAutoCleanupAfterDone non-fatal behavior', () => {
69
- it('should not throw when cleanup throws an error', async () => {
70
- // Mock config to enable cleanup
71
- vi.doMock(CONFIG_MODULE_PATH, () => ({
72
- getConfig: vi.fn().mockReturnValue({
73
- cleanup: { trigger: 'on_done' },
74
- directories: { wuDir: 'docs/tasks/wu' },
75
- }),
76
- }));
77
- // Mock cleanupState to throw
78
- vi.doMock('@lumenflow/core/dist/state-cleanup-core.js', () => ({
79
- cleanupState: vi.fn().mockRejectedValue(new Error('Cleanup failed')),
80
- }));
81
- // Mock the memory functions to avoid actual file operations
82
- vi.doMock('@lumenflow/memory/dist/signal-cleanup-core.js', () => ({
83
- cleanupSignals: vi.fn().mockResolvedValue({
84
- success: true,
85
- removedIds: [],
86
- retainedIds: [],
87
- bytesFreed: 0,
88
- compactionRatio: 0,
89
- breakdown: {},
90
- }),
91
- }));
92
- vi.doMock('@lumenflow/memory/dist/mem-cleanup-core.js', () => ({
93
- cleanupMemory: vi.fn().mockResolvedValue({
94
- success: true,
95
- removedIds: [],
96
- retainedIds: [],
97
- bytesFreed: 0,
98
- compactionRatio: 0,
99
- breakdown: {},
100
- }),
101
- }));
102
- vi.doMock('@lumenflow/core/dist/wu-events-cleanup.js', () => ({
103
- archiveWuEvents: vi.fn().mockResolvedValue({
104
- success: true,
105
- archivedWuIds: [],
106
- retainedWuIds: [],
107
- bytesArchived: 0,
108
- archivedEventCount: 0,
109
- retainedEventCount: 0,
110
- breakdown: {},
111
- }),
112
- }));
113
- const { runAutoCleanupAfterDone } = await import('../wu-done-auto-cleanup.js');
114
- // Should not throw - cleanup errors are non-fatal
115
- await expect(runAutoCleanupAfterDone('/test/dir')).resolves.not.toThrow();
116
- // Should log warning about the error
117
- expect(consoleWarnSpy).toHaveBeenCalled();
118
- });
119
- it('should skip cleanup when trigger is manual', async () => {
120
- vi.doMock(CONFIG_MODULE_PATH, () => ({
121
- getConfig: vi.fn().mockReturnValue({
122
- cleanup: { trigger: 'manual' },
123
- }),
124
- }));
125
- const mockCleanupState = vi.fn();
126
- vi.doMock('@lumenflow/core/dist/state-cleanup-core.js', () => ({
127
- cleanupState: mockCleanupState,
128
- }));
129
- const { runAutoCleanupAfterDone } = await import('../wu-done-auto-cleanup.js');
130
- await runAutoCleanupAfterDone('/test/dir');
131
- // Cleanup should not be called when trigger is manual
132
- expect(mockCleanupState).not.toHaveBeenCalled();
133
- });
134
- });
135
- });
@@ -1,20 +0,0 @@
1
- /**
2
- * @file wu-done-docs-only-policy.test.ts
3
- * Guardrail test: docs-only eligibility checks must not use raw type/exposure string literals (WU-1446).
4
- *
5
- * This keeps CLI policy logic DRY and aligned with core constants/helpers.
6
- */
7
- import { describe, it, expect } from 'vitest';
8
- import { readFileSync } from 'node:fs';
9
- import path from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
- describe('wu:done docs-only policy (WU-1446)', () => {
12
- it('should not use raw documentation string comparisons for type/exposure checks', () => {
13
- const thisDir = path.dirname(fileURLToPath(import.meta.url));
14
- const filePath = path.join(thisDir, '..', 'wu-done.ts');
15
- const content = readFileSync(filePath, 'utf-8');
16
- // These comparisons should use core constants/helpers instead.
17
- expect(content).not.toContain("exposure === 'documentation'");
18
- expect(content).not.toContain("type === 'documentation'");
19
- });
20
- });
@@ -1,35 +0,0 @@
1
- /**
2
- * @file Tests for wu-done staging whitelist patterns
3
- * @see WU-1072: Fix staging whitelist for auto-generated docs
4
- */
5
- import { describe, it, expect } from 'vitest';
6
- /**
7
- * Pattern matching logic extracted from validateStagedFiles in wu-done.ts
8
- * This tests the MDX whitelist pattern added in WU-1072
9
- */
10
- function isWhitelistedAutoGeneratedDoc(file) {
11
- return file.startsWith('apps/docs/') && file.endsWith('.mdx');
12
- }
13
- describe('wu-done staging whitelist', () => {
14
- describe('auto-generated docs pattern (WU-1072)', () => {
15
- it('should whitelist MDX files in apps/docs/', () => {
16
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/api/wu-create.mdx')).toBe(true);
17
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/commands/gates.mdx')).toBe(true);
18
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/nested/deep/file.mdx')).toBe(true);
19
- });
20
- it('should not whitelist MDX files outside apps/docs/', () => {
21
- expect(isWhitelistedAutoGeneratedDoc('docs/something.mdx')).toBe(false);
22
- expect(isWhitelistedAutoGeneratedDoc('packages/cli/README.mdx')).toBe(false);
23
- expect(isWhitelistedAutoGeneratedDoc('src/docs/file.mdx')).toBe(false);
24
- });
25
- it('should not whitelist non-MDX files in apps/docs/', () => {
26
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/config.ts')).toBe(false);
27
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/package.json')).toBe(false);
28
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/styles.css')).toBe(false);
29
- });
30
- it('should not whitelist files with mdx in the path but not as extension', () => {
31
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/mdx-utils/helper.ts')).toBe(false);
32
- expect(isWhitelistedAutoGeneratedDoc('apps/docs/mdx')).toBe(false);
33
- });
34
- });
35
- });
@@ -1,36 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { ensureCleanWorktree } from '../wu-done-check.js';
3
- import * as gitAdapter from '@lumenflow/core/git-adapter';
4
- import * as errorHandler from '@lumenflow/core/error-handler';
5
- // Mock dependencies
6
- vi.mock('@lumenflow/core/git-adapter');
7
- vi.mock('@lumenflow/core/error-handler');
8
- describe('wu-done', () => {
9
- describe('ensureCleanWorktree', () => {
10
- let mockGit;
11
- beforeEach(() => {
12
- vi.resetAllMocks();
13
- mockGit = {
14
- getStatus: vi.fn(),
15
- };
16
- vi.mocked(gitAdapter.createGitForPath).mockReturnValue(mockGit);
17
- });
18
- it('should pass if worktree is clean', async () => {
19
- mockGit.getStatus.mockResolvedValue(''); // Clean status
20
- await ensureCleanWorktree('/path/to/worktree');
21
- expect(mockGit.getStatus).toHaveBeenCalled();
22
- expect(errorHandler.die).not.toHaveBeenCalled();
23
- });
24
- it('should die if worktree has uncommitted changes', async () => {
25
- mockGit.getStatus.mockResolvedValue('M file.ts\n?? new-file.ts'); // Dirty status
26
- await ensureCleanWorktree('/path/to/worktree');
27
- expect(mockGit.getStatus).toHaveBeenCalled();
28
- expect(errorHandler.die).toHaveBeenCalledWith(expect.stringContaining('Worktree has uncommitted changes'));
29
- });
30
- it('should use the correct worktree path', async () => {
31
- mockGit.getStatus.mockResolvedValue('');
32
- await ensureCleanWorktree('/custom/worktree/path');
33
- expect(gitAdapter.createGitForPath).toHaveBeenCalledWith('/custom/worktree/path');
34
- });
35
- });
36
- });
@@ -1,109 +0,0 @@
1
- /**
2
- * @file wu-edit-strict.test.ts
3
- * Test suite for wu:edit strict validation behavior (WU-1329)
4
- *
5
- * WU-1329: Make wu:edit run strict validation by default
6
- *
7
- * Tests:
8
- * - applyEdits function works correctly (unit test for edit logic)
9
- * - validateDoneWUEdits properly validates done WU edits
10
- * - The main() function validates paths in strict mode (covered by e2e tests)
11
- */
12
- import { describe, it, expect } from 'vitest';
13
- // Import test utilities (WU-1329)
14
- import { applyEdits, validateDoneWUEdits, validateExposureValue } from '../wu-edit.js';
15
- // WU-1329: Constants for test file paths
16
- const TEST_NEW_FILE_PATH = 'new/file.ts';
17
- const TEST_NEW_TEST_PATH = 'new/test.test.ts';
18
- describe('wu:edit strict validation (WU-1329)', () => {
19
- describe('applyEdits code_paths handling', () => {
20
- const baseWU = {
21
- id: 'WU-9999',
22
- title: 'Test WU',
23
- lane: 'Framework: CLI',
24
- type: 'feature',
25
- status: 'ready',
26
- priority: 'P2',
27
- description: 'Context: test context.\nProblem: test problem.\nSolution: test solution that exceeds minimum length.',
28
- acceptance: ['Existing criterion'],
29
- code_paths: ['packages/@lumenflow/cli/src/wu-edit.ts'],
30
- tests: {
31
- unit: ['packages/@lumenflow/cli/src/__tests__/wu-edit-strict.test.ts'],
32
- manual: [],
33
- e2e: [],
34
- },
35
- };
36
- // WU-1329: applyEdits transforms WU object - path validation is done separately
37
- it('should apply code_paths edits correctly', () => {
38
- const result = applyEdits(baseWU, {
39
- codePaths: [TEST_NEW_FILE_PATH],
40
- replaceCodePaths: true,
41
- });
42
- // applyEdits transforms the WU, validation happens later in main()
43
- expect(result.code_paths).toContain(TEST_NEW_FILE_PATH);
44
- expect(result.code_paths).toHaveLength(1);
45
- });
46
- it('should append code_paths by default', () => {
47
- const result = applyEdits(baseWU, {
48
- codePaths: [TEST_NEW_FILE_PATH],
49
- });
50
- expect(result.code_paths).toContain('packages/@lumenflow/cli/src/wu-edit.ts');
51
- expect(result.code_paths).toContain(TEST_NEW_FILE_PATH);
52
- expect(result.code_paths).toHaveLength(2);
53
- });
54
- it('should apply test_paths edits correctly', () => {
55
- const result = applyEdits(baseWU, {
56
- testPathsUnit: [TEST_NEW_TEST_PATH],
57
- });
58
- // Should append test paths
59
- expect(result.tests.unit).toContain('packages/@lumenflow/cli/src/__tests__/wu-edit-strict.test.ts');
60
- expect(result.tests.unit).toContain(TEST_NEW_TEST_PATH);
61
- });
62
- });
63
- describe('validateDoneWUEdits', () => {
64
- // WU-1329: Done WUs only allow initiative/phase/exposure edits
65
- it('should allow exposure edits on done WUs', () => {
66
- const result = validateDoneWUEdits({ exposure: 'backend-only' });
67
- expect(result.valid).toBe(true);
68
- expect(result.disallowedEdits).toHaveLength(0);
69
- });
70
- it('should disallow code_paths edits on done WUs', () => {
71
- const result = validateDoneWUEdits({ codePaths: [TEST_NEW_FILE_PATH] });
72
- expect(result.valid).toBe(false);
73
- expect(result.disallowedEdits).toContain('--code-paths');
74
- });
75
- it('should disallow description edits on done WUs', () => {
76
- const result = validateDoneWUEdits({ description: 'new description' });
77
- expect(result.valid).toBe(false);
78
- expect(result.disallowedEdits).toContain('--description');
79
- });
80
- });
81
- describe('validateExposureValue', () => {
82
- it('should accept valid exposure values', () => {
83
- expect(validateExposureValue('ui').valid).toBe(true);
84
- expect(validateExposureValue('api').valid).toBe(true);
85
- expect(validateExposureValue('backend-only').valid).toBe(true);
86
- expect(validateExposureValue('documentation').valid).toBe(true);
87
- });
88
- it('should reject invalid exposure values', () => {
89
- const result = validateExposureValue('invalid-exposure');
90
- expect(result.valid).toBe(false);
91
- expect(result.error).toContain('Invalid exposure value');
92
- });
93
- });
94
- describe('noStrict option support', () => {
95
- // WU-1329: Verify CLI option parsing pattern
96
- it('should support noStrict option in CLI argument pattern', () => {
97
- // CLI uses --no-strict which Commander.js parses as noStrict
98
- // The main() function converts this to strict: !noStrict
99
- const cliArgs = { noStrict: true };
100
- const strict = !cliArgs.noStrict;
101
- expect(strict).toBe(false);
102
- });
103
- it('should default to strict=true when noStrict is undefined', () => {
104
- const cliArgs = { noStrict: undefined };
105
- const strict = !cliArgs.noStrict;
106
- expect(strict).toBe(true);
107
- });
108
- });
109
- });
@@ -1,119 +0,0 @@
1
- /**
2
- * WU-1225: Tests for wu-edit append-by-default behavior
3
- *
4
- * Validates that array fields (code_paths, risks, acceptance, etc.)
5
- * now append by default instead of replacing, making behavior consistent
6
- * across all array options.
7
- */
8
- import { describe, it, expect } from 'vitest';
9
- import { applyEdits, mergeStringField } from '../wu-edit.js';
10
- describe('wu-edit applyEdits', () => {
11
- describe('WU-1225: code_paths append-by-default', () => {
12
- const baseWU = {
13
- id: 'WU-1225',
14
- status: 'ready',
15
- code_paths: ['existing/path.ts'],
16
- };
17
- it('appends code_paths by default (no flags)', () => {
18
- const opts = { codePaths: ['new/path.ts'] };
19
- const result = applyEdits(baseWU, opts);
20
- expect(result.code_paths).toEqual(['existing/path.ts', 'new/path.ts']);
21
- });
22
- it('appends code_paths when --append is set (backwards compat)', () => {
23
- const opts = { codePaths: ['new/path.ts'], append: true };
24
- const result = applyEdits(baseWU, opts);
25
- expect(result.code_paths).toEqual(['existing/path.ts', 'new/path.ts']);
26
- });
27
- it('replaces code_paths when --replace-code-paths is set', () => {
28
- const opts = { codePaths: ['new/path.ts'], replaceCodePaths: true };
29
- const result = applyEdits(baseWU, opts);
30
- expect(result.code_paths).toEqual(['new/path.ts']);
31
- });
32
- });
33
- describe('WU-1225: risks append-by-default', () => {
34
- const baseWU = {
35
- id: 'WU-1225',
36
- status: 'ready',
37
- risks: ['existing risk'],
38
- };
39
- it('appends risks by default', () => {
40
- const opts = { risks: ['new risk'] };
41
- const result = applyEdits(baseWU, opts);
42
- expect(result.risks).toEqual(['existing risk', 'new risk']);
43
- });
44
- it('replaces risks when --replace-risks is set', () => {
45
- const opts = { risks: ['new risk'], replaceRisks: true };
46
- const result = applyEdits(baseWU, opts);
47
- expect(result.risks).toEqual(['new risk']);
48
- });
49
- });
50
- describe('WU-1225: blocked_by append-by-default', () => {
51
- const baseWU = {
52
- id: 'WU-1225',
53
- status: 'ready',
54
- blocked_by: ['WU-100'],
55
- };
56
- it('appends blocked_by by default', () => {
57
- const opts = { blockedBy: 'WU-200' };
58
- const result = applyEdits(baseWU, opts);
59
- expect(result.blocked_by).toEqual(['WU-100', 'WU-200']);
60
- });
61
- it('replaces blocked_by when --replace-blocked-by is set', () => {
62
- const opts = { blockedBy: 'WU-200', replaceBlockedBy: true };
63
- const result = applyEdits(baseWU, opts);
64
- expect(result.blocked_by).toEqual(['WU-200']);
65
- });
66
- });
67
- describe('WU-1225: dependencies append-by-default', () => {
68
- const baseWU = {
69
- id: 'WU-1225',
70
- status: 'ready',
71
- dependencies: ['WU-50'],
72
- };
73
- it('appends dependencies by default', () => {
74
- const opts = { addDep: 'WU-60' };
75
- const result = applyEdits(baseWU, opts);
76
- expect(result.dependencies).toEqual(['WU-50', 'WU-60']);
77
- });
78
- it('replaces dependencies when --replace-dependencies is set', () => {
79
- const opts = { addDep: 'WU-60', replaceDependencies: true };
80
- const result = applyEdits(baseWU, opts);
81
- expect(result.dependencies).toEqual(['WU-60']);
82
- });
83
- });
84
- describe('WU-1144: acceptance already appends by default', () => {
85
- const baseWU = {
86
- id: 'WU-1225',
87
- status: 'ready',
88
- acceptance: ['existing criterion'],
89
- };
90
- it('appends acceptance by default', () => {
91
- const opts = { acceptance: ['new criterion'] };
92
- const result = applyEdits(baseWU, opts);
93
- expect(result.acceptance).toEqual(['existing criterion', 'new criterion']);
94
- });
95
- it('replaces acceptance when --replace-acceptance is set', () => {
96
- const opts = { acceptance: ['new criterion'], replaceAcceptance: true };
97
- const result = applyEdits(baseWU, opts);
98
- expect(result.acceptance).toEqual(['new criterion']);
99
- });
100
- });
101
- });
102
- describe('wu-edit mergeStringField', () => {
103
- it('appends by default', () => {
104
- const result = mergeStringField('existing', 'new', false);
105
- expect(result).toBe('existing\n\nnew');
106
- });
107
- it('replaces when shouldReplace is true', () => {
108
- const result = mergeStringField('existing', 'new', true);
109
- expect(result).toBe('new');
110
- });
111
- it('returns new value if existing is empty', () => {
112
- const result = mergeStringField('', 'new', false);
113
- expect(result).toBe('new');
114
- });
115
- it('returns new value if existing is undefined', () => {
116
- const result = mergeStringField(undefined, 'new', false);
117
- expect(result).toBe('new');
118
- });
119
- });