@lumenflow/core 1.5.0 → 1.6.0
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 +325 -1
- package/dist/adapters/context-adapters.d.ts +90 -0
- package/dist/adapters/context-adapters.js +99 -0
- package/dist/adapters/index.d.ts +14 -0
- package/dist/adapters/index.js +18 -0
- package/dist/adapters/recovery-adapters.d.ts +40 -0
- package/dist/adapters/recovery-adapters.js +43 -0
- package/dist/adapters/validation-adapters.d.ts +52 -0
- package/dist/adapters/validation-adapters.js +59 -0
- package/dist/context/context-computer.d.ts +46 -0
- package/dist/context/context-computer.js +125 -0
- package/dist/context/git-state-reader.d.ts +51 -0
- package/dist/context/git-state-reader.js +61 -0
- package/dist/context/index.d.ts +17 -0
- package/dist/context/index.js +17 -0
- package/dist/context/location-resolver.d.ts +48 -0
- package/dist/context/location-resolver.js +175 -0
- package/dist/context/wu-state-reader.d.ts +37 -0
- package/dist/context/wu-state-reader.js +76 -0
- package/dist/context-di.d.ts +184 -0
- package/dist/context-di.js +178 -0
- package/dist/context-validation-integration.d.ts +77 -0
- package/dist/context-validation-integration.js +157 -0
- package/dist/domain/context.schemas.d.ts +147 -0
- package/dist/domain/context.schemas.js +126 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/recovery.schemas.d.ts +115 -0
- package/dist/domain/recovery.schemas.js +83 -0
- package/dist/domain/validation.schemas.d.ts +146 -0
- package/dist/domain/validation.schemas.js +114 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +43 -0
- package/dist/lumenflow-config-schema.d.ts +41 -0
- package/dist/lumenflow-config-schema.js +37 -0
- package/dist/ports/context.ports.d.ts +135 -0
- package/dist/ports/context.ports.js +21 -0
- package/dist/ports/index.d.ts +14 -0
- package/dist/ports/index.js +14 -0
- package/dist/ports/recovery.ports.d.ts +58 -0
- package/dist/ports/recovery.ports.js +17 -0
- package/dist/ports/validation.ports.d.ts +74 -0
- package/dist/ports/validation.ports.js +17 -0
- package/dist/recovery/index.d.ts +11 -0
- package/dist/recovery/index.js +11 -0
- package/dist/recovery/recovery-analyzer.d.ts +66 -0
- package/dist/recovery/recovery-analyzer.js +129 -0
- package/dist/usecases/analyze-recovery.usecase.d.ts +42 -0
- package/dist/usecases/analyze-recovery.usecase.js +45 -0
- package/dist/usecases/compute-context.usecase.d.ts +62 -0
- package/dist/usecases/compute-context.usecase.js +101 -0
- package/dist/usecases/index.d.ts +14 -0
- package/dist/usecases/index.js +18 -0
- package/dist/usecases/validate-command.usecase.d.ts +55 -0
- package/dist/usecases/validate-command.usecase.js +154 -0
- package/dist/validation/command-registry.d.ts +38 -0
- package/dist/validation/command-registry.js +229 -0
- package/dist/validation/index.d.ts +15 -0
- package/dist/validation/index.js +15 -0
- package/dist/validation/types.d.ts +135 -0
- package/dist/validation/types.js +11 -0
- package/dist/validation/validate-command.d.ts +27 -0
- package/dist/validation/validate-command.js +160 -0
- package/dist/wu-constants.d.ts +136 -0
- package/dist/wu-constants.js +124 -0
- package/dist/wu-helpers.d.ts +5 -1
- package/dist/wu-helpers.js +12 -1
- package/package.json +2 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery Analyzer for WU State Issues
|
|
3
|
+
*
|
|
4
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
5
|
+
*
|
|
6
|
+
* Analyzes WU context to detect state inconsistencies and suggests
|
|
7
|
+
* appropriate recovery actions.
|
|
8
|
+
*
|
|
9
|
+
* Issue types detected:
|
|
10
|
+
* - Partial claim: worktree exists but status is ready
|
|
11
|
+
* - Orphan claim: status is in_progress but worktree missing
|
|
12
|
+
* - Inconsistent state: YAML and state store disagree
|
|
13
|
+
* - Orphan branch: branch exists but worktree missing
|
|
14
|
+
* - Stale lock: lock file from old session
|
|
15
|
+
* - Leftover worktree: WU is done but worktree exists
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { type RecoveryActionType, type RecoveryIssueCode } from '../wu-constants.js';
|
|
20
|
+
import type { WuContext } from '../validation/types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Issue detected during recovery analysis.
|
|
23
|
+
*/
|
|
24
|
+
export interface RecoveryIssue {
|
|
25
|
+
/** Issue code from RECOVERY_ISSUES */
|
|
26
|
+
code: RecoveryIssueCode;
|
|
27
|
+
/** Human-readable description */
|
|
28
|
+
description: string;
|
|
29
|
+
/** Additional context for the issue */
|
|
30
|
+
context?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Suggested recovery action.
|
|
34
|
+
*/
|
|
35
|
+
export interface WuRecoveryAction {
|
|
36
|
+
/** Action type from RECOVERY_ACTIONS */
|
|
37
|
+
type: RecoveryActionType;
|
|
38
|
+
/** Human-readable description of what this action does */
|
|
39
|
+
description: string;
|
|
40
|
+
/** Command to execute (copy-paste ready) */
|
|
41
|
+
command: string;
|
|
42
|
+
/** Whether this action requires --force flag */
|
|
43
|
+
requiresForce: boolean;
|
|
44
|
+
/** Warning message if any */
|
|
45
|
+
warning?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Result of recovery analysis.
|
|
49
|
+
*/
|
|
50
|
+
export interface RecoveryAnalysis {
|
|
51
|
+
/** Whether any issues were found */
|
|
52
|
+
hasIssues: boolean;
|
|
53
|
+
/** List of detected issues */
|
|
54
|
+
issues: RecoveryIssue[];
|
|
55
|
+
/** Suggested recovery actions */
|
|
56
|
+
actions: WuRecoveryAction[];
|
|
57
|
+
/** WU ID analyzed */
|
|
58
|
+
wuId: string | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Analyze context for recovery issues.
|
|
62
|
+
*
|
|
63
|
+
* @param context - Current WU context
|
|
64
|
+
* @returns Recovery analysis with issues and suggested actions
|
|
65
|
+
*/
|
|
66
|
+
export declare function analyzeRecovery(context: WuContext): Promise<RecoveryAnalysis>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery Analyzer for WU State Issues
|
|
3
|
+
*
|
|
4
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
5
|
+
*
|
|
6
|
+
* Analyzes WU context to detect state inconsistencies and suggests
|
|
7
|
+
* appropriate recovery actions.
|
|
8
|
+
*
|
|
9
|
+
* Issue types detected:
|
|
10
|
+
* - Partial claim: worktree exists but status is ready
|
|
11
|
+
* - Orphan claim: status is in_progress but worktree missing
|
|
12
|
+
* - Inconsistent state: YAML and state store disagree
|
|
13
|
+
* - Orphan branch: branch exists but worktree missing
|
|
14
|
+
* - Stale lock: lock file from old session
|
|
15
|
+
* - Leftover worktree: WU is done but worktree exists
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { CONTEXT_VALIDATION, WU_STATUS, DEFAULTS, toKebab, } from '../wu-constants.js';
|
|
22
|
+
const { RECOVERY_ISSUES, RECOVERY_ACTIONS } = CONTEXT_VALIDATION;
|
|
23
|
+
/**
|
|
24
|
+
* Get expected worktree path for a WU.
|
|
25
|
+
*/
|
|
26
|
+
function getExpectedWorktreePath(mainCheckout, lane, wuId) {
|
|
27
|
+
const laneKebab = toKebab(lane);
|
|
28
|
+
const wuIdLower = wuId.toLowerCase();
|
|
29
|
+
return join(mainCheckout, DEFAULTS.WORKTREES_DIR, `${laneKebab}-${wuIdLower}`);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if worktree exists for a WU.
|
|
33
|
+
*/
|
|
34
|
+
function worktreeExists(mainCheckout, lane, wuId) {
|
|
35
|
+
const worktreePath = getExpectedWorktreePath(mainCheckout, lane, wuId);
|
|
36
|
+
return existsSync(worktreePath);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Analyze context for recovery issues.
|
|
40
|
+
*
|
|
41
|
+
* @param context - Current WU context
|
|
42
|
+
* @returns Recovery analysis with issues and suggested actions
|
|
43
|
+
*/
|
|
44
|
+
export async function analyzeRecovery(context) {
|
|
45
|
+
const issues = [];
|
|
46
|
+
const actions = [];
|
|
47
|
+
// No WU context means nothing to analyze
|
|
48
|
+
if (!context.wu) {
|
|
49
|
+
return {
|
|
50
|
+
hasIssues: false,
|
|
51
|
+
issues: [],
|
|
52
|
+
actions: [],
|
|
53
|
+
wuId: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const { wu } = context;
|
|
57
|
+
const mainCheckout = context.location.mainCheckout;
|
|
58
|
+
const hasWorktree = worktreeExists(mainCheckout, wu.lane, wu.id);
|
|
59
|
+
const worktreePath = getExpectedWorktreePath(mainCheckout, wu.lane, wu.id);
|
|
60
|
+
// Check for partial claim: worktree exists but status is ready
|
|
61
|
+
if (hasWorktree && wu.status === WU_STATUS.READY) {
|
|
62
|
+
issues.push({
|
|
63
|
+
code: RECOVERY_ISSUES.PARTIAL_CLAIM,
|
|
64
|
+
description: `Worktree exists for ${wu.id} but status is 'ready'. The claim may have been interrupted.`,
|
|
65
|
+
context: { worktreePath, status: wu.status },
|
|
66
|
+
});
|
|
67
|
+
actions.push({
|
|
68
|
+
type: RECOVERY_ACTIONS.RESUME,
|
|
69
|
+
description: 'Reconcile state and continue working (preserves work)',
|
|
70
|
+
command: `pnpm wu:recover --id ${wu.id} --action resume`,
|
|
71
|
+
requiresForce: false,
|
|
72
|
+
});
|
|
73
|
+
actions.push({
|
|
74
|
+
type: RECOVERY_ACTIONS.RESET,
|
|
75
|
+
description: 'Discard worktree and reset WU to ready',
|
|
76
|
+
command: `pnpm wu:recover --id ${wu.id} --action reset`,
|
|
77
|
+
requiresForce: false,
|
|
78
|
+
warning: 'This will discard any uncommitted work in the worktree',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Check for orphan claim: status is in_progress but worktree missing
|
|
82
|
+
if (!hasWorktree && wu.status === WU_STATUS.IN_PROGRESS) {
|
|
83
|
+
issues.push({
|
|
84
|
+
code: RECOVERY_ISSUES.ORPHAN_CLAIM,
|
|
85
|
+
description: `${wu.id} is 'in_progress' but worktree is missing. The worktree may have been manually deleted.`,
|
|
86
|
+
context: { expectedPath: worktreePath, status: wu.status },
|
|
87
|
+
});
|
|
88
|
+
actions.push({
|
|
89
|
+
type: RECOVERY_ACTIONS.RESET,
|
|
90
|
+
description: 'Reset WU status back to ready for re-claiming',
|
|
91
|
+
command: `pnpm wu:recover --id ${wu.id} --action reset`,
|
|
92
|
+
requiresForce: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Check for leftover worktree: WU is done but worktree exists
|
|
96
|
+
if (hasWorktree && wu.status === WU_STATUS.DONE) {
|
|
97
|
+
issues.push({
|
|
98
|
+
code: RECOVERY_ISSUES.LEFTOVER_WORKTREE,
|
|
99
|
+
description: `${wu.id} is 'done' but worktree still exists. The cleanup may have failed.`,
|
|
100
|
+
context: { worktreePath, status: wu.status },
|
|
101
|
+
});
|
|
102
|
+
actions.push({
|
|
103
|
+
type: RECOVERY_ACTIONS.CLEANUP,
|
|
104
|
+
description: 'Remove leftover worktree for completed WU',
|
|
105
|
+
command: `pnpm wu:recover --id ${wu.id} --action cleanup`,
|
|
106
|
+
requiresForce: false,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Check for inconsistent state
|
|
110
|
+
if (!wu.isConsistent && wu.inconsistencyReason) {
|
|
111
|
+
issues.push({
|
|
112
|
+
code: RECOVERY_ISSUES.INCONSISTENT_STATE,
|
|
113
|
+
description: wu.inconsistencyReason,
|
|
114
|
+
context: { wuId: wu.id },
|
|
115
|
+
});
|
|
116
|
+
actions.push({
|
|
117
|
+
type: RECOVERY_ACTIONS.RESET,
|
|
118
|
+
description: 'Reconcile YAML and state store',
|
|
119
|
+
command: `pnpm wu:recover --id ${wu.id} --action reset`,
|
|
120
|
+
requiresForce: false,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
hasIssues: issues.length > 0,
|
|
125
|
+
issues,
|
|
126
|
+
actions,
|
|
127
|
+
wuId: wu.id,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnalyzeRecoveryUseCase
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Use case for analyzing WU state and suggesting recovery actions.
|
|
7
|
+
* Uses constructor injection for recovery analyzer dependency.
|
|
8
|
+
*
|
|
9
|
+
* Hexagonal Architecture - Application Layer
|
|
10
|
+
* - Depends on port interface (IRecoveryAnalyzer)
|
|
11
|
+
* - Does NOT import from infrastructure layer
|
|
12
|
+
*
|
|
13
|
+
* @module usecases/analyze-recovery.usecase
|
|
14
|
+
*/
|
|
15
|
+
import type { IRecoveryAnalyzer, WuContext, RecoveryAnalysis } from '../ports/recovery.ports.js';
|
|
16
|
+
/**
|
|
17
|
+
* AnalyzeRecoveryUseCase
|
|
18
|
+
*
|
|
19
|
+
* Analyzes WU context to detect state inconsistencies and suggests
|
|
20
|
+
* recovery actions.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Using default analyzer via DI factory
|
|
24
|
+
* const useCase = createAnalyzeRecoveryUseCase();
|
|
25
|
+
* const analysis = await useCase.execute(context);
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Using custom analyzer for testing
|
|
29
|
+
* const useCase = new AnalyzeRecoveryUseCase(mockAnalyzer);
|
|
30
|
+
* const analysis = await useCase.execute(context);
|
|
31
|
+
*/
|
|
32
|
+
export declare class AnalyzeRecoveryUseCase {
|
|
33
|
+
private readonly recoveryAnalyzer;
|
|
34
|
+
constructor(recoveryAnalyzer: IRecoveryAnalyzer);
|
|
35
|
+
/**
|
|
36
|
+
* Execute the use case to analyze recovery issues.
|
|
37
|
+
*
|
|
38
|
+
* @param context - Current WU context
|
|
39
|
+
* @returns Promise<RecoveryAnalysis> - Analysis with issues and suggested actions
|
|
40
|
+
*/
|
|
41
|
+
execute(context: WuContext): Promise<RecoveryAnalysis>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnalyzeRecoveryUseCase
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Use case for analyzing WU state and suggesting recovery actions.
|
|
7
|
+
* Uses constructor injection for recovery analyzer dependency.
|
|
8
|
+
*
|
|
9
|
+
* Hexagonal Architecture - Application Layer
|
|
10
|
+
* - Depends on port interface (IRecoveryAnalyzer)
|
|
11
|
+
* - Does NOT import from infrastructure layer
|
|
12
|
+
*
|
|
13
|
+
* @module usecases/analyze-recovery.usecase
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* AnalyzeRecoveryUseCase
|
|
17
|
+
*
|
|
18
|
+
* Analyzes WU context to detect state inconsistencies and suggests
|
|
19
|
+
* recovery actions.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Using default analyzer via DI factory
|
|
23
|
+
* const useCase = createAnalyzeRecoveryUseCase();
|
|
24
|
+
* const analysis = await useCase.execute(context);
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Using custom analyzer for testing
|
|
28
|
+
* const useCase = new AnalyzeRecoveryUseCase(mockAnalyzer);
|
|
29
|
+
* const analysis = await useCase.execute(context);
|
|
30
|
+
*/
|
|
31
|
+
export class AnalyzeRecoveryUseCase {
|
|
32
|
+
recoveryAnalyzer;
|
|
33
|
+
constructor(recoveryAnalyzer) {
|
|
34
|
+
this.recoveryAnalyzer = recoveryAnalyzer;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Execute the use case to analyze recovery issues.
|
|
38
|
+
*
|
|
39
|
+
* @param context - Current WU context
|
|
40
|
+
* @returns Promise<RecoveryAnalysis> - Analysis with issues and suggested actions
|
|
41
|
+
*/
|
|
42
|
+
async execute(context) {
|
|
43
|
+
return this.recoveryAnalyzer.analyzeRecovery(context);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComputeContextUseCase
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Use case for computing WU context by orchestrating multiple adapters.
|
|
7
|
+
* Uses constructor injection for all dependencies.
|
|
8
|
+
*
|
|
9
|
+
* Hexagonal Architecture - Application Layer
|
|
10
|
+
* - Depends on port interfaces (ILocationResolver, IGitStateReader, IWuStateReader)
|
|
11
|
+
* - Does NOT import from infrastructure layer
|
|
12
|
+
*
|
|
13
|
+
* @module usecases/compute-context.usecase
|
|
14
|
+
*/
|
|
15
|
+
import type { ILocationResolver, IGitStateReader, IWuStateReader } from '../ports/context.ports.js';
|
|
16
|
+
import type { WuContext } from '../validation/types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Options for computing WU context.
|
|
19
|
+
*/
|
|
20
|
+
export interface ComputeContextOptions {
|
|
21
|
+
/** WU ID to look up (optional - will detect from worktree if not provided) */
|
|
22
|
+
wuId?: string;
|
|
23
|
+
/** Current working directory (defaults to process.cwd()) */
|
|
24
|
+
cwd?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* ComputeContextUseCase
|
|
28
|
+
*
|
|
29
|
+
* Orchestrates the computation of WU context by calling multiple adapters
|
|
30
|
+
* and assembling the unified context model.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Using default adapters via DI factory
|
|
34
|
+
* const useCase = createComputeContextUseCase();
|
|
35
|
+
* const context = await useCase.execute({ wuId: 'WU-1094' });
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Using custom adapters for testing
|
|
39
|
+
* const useCase = new ComputeContextUseCase(
|
|
40
|
+
* mockLocationResolver,
|
|
41
|
+
* mockGitStateReader,
|
|
42
|
+
* mockWuStateReader,
|
|
43
|
+
* );
|
|
44
|
+
* const context = await useCase.execute({});
|
|
45
|
+
*/
|
|
46
|
+
export declare class ComputeContextUseCase {
|
|
47
|
+
private readonly locationResolver;
|
|
48
|
+
private readonly gitStateReader;
|
|
49
|
+
private readonly wuStateReader;
|
|
50
|
+
constructor(locationResolver: ILocationResolver, gitStateReader: IGitStateReader, wuStateReader: IWuStateReader);
|
|
51
|
+
/**
|
|
52
|
+
* Execute the use case to compute WU context.
|
|
53
|
+
*
|
|
54
|
+
* @param options - Options including optional wuId and cwd
|
|
55
|
+
* @returns Promise<WuContext> - Computed WU context
|
|
56
|
+
*/
|
|
57
|
+
execute(options?: ComputeContextOptions): Promise<WuContext>;
|
|
58
|
+
/**
|
|
59
|
+
* Get expected worktree path for a WU.
|
|
60
|
+
*/
|
|
61
|
+
private getWorktreePath;
|
|
62
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComputeContextUseCase
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Use case for computing WU context by orchestrating multiple adapters.
|
|
7
|
+
* Uses constructor injection for all dependencies.
|
|
8
|
+
*
|
|
9
|
+
* Hexagonal Architecture - Application Layer
|
|
10
|
+
* - Depends on port interfaces (ILocationResolver, IGitStateReader, IWuStateReader)
|
|
11
|
+
* - Does NOT import from infrastructure layer
|
|
12
|
+
*
|
|
13
|
+
* @module usecases/compute-context.usecase
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* ComputeContextUseCase
|
|
17
|
+
*
|
|
18
|
+
* Orchestrates the computation of WU context by calling multiple adapters
|
|
19
|
+
* and assembling the unified context model.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Using default adapters via DI factory
|
|
23
|
+
* const useCase = createComputeContextUseCase();
|
|
24
|
+
* const context = await useCase.execute({ wuId: 'WU-1094' });
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Using custom adapters for testing
|
|
28
|
+
* const useCase = new ComputeContextUseCase(
|
|
29
|
+
* mockLocationResolver,
|
|
30
|
+
* mockGitStateReader,
|
|
31
|
+
* mockWuStateReader,
|
|
32
|
+
* );
|
|
33
|
+
* const context = await useCase.execute({});
|
|
34
|
+
*/
|
|
35
|
+
export class ComputeContextUseCase {
|
|
36
|
+
locationResolver;
|
|
37
|
+
gitStateReader;
|
|
38
|
+
wuStateReader;
|
|
39
|
+
constructor(locationResolver, gitStateReader, wuStateReader) {
|
|
40
|
+
this.locationResolver = locationResolver;
|
|
41
|
+
this.gitStateReader = gitStateReader;
|
|
42
|
+
this.wuStateReader = wuStateReader;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Execute the use case to compute WU context.
|
|
46
|
+
*
|
|
47
|
+
* @param options - Options including optional wuId and cwd
|
|
48
|
+
* @returns Promise<WuContext> - Computed WU context
|
|
49
|
+
*/
|
|
50
|
+
async execute(options = {}) {
|
|
51
|
+
const { wuId, cwd } = options;
|
|
52
|
+
// Step 1: Resolve location context
|
|
53
|
+
const location = await this.locationResolver.resolveLocation(cwd);
|
|
54
|
+
// Step 2: Read git state for current directory
|
|
55
|
+
const git = await this.gitStateReader.readGitState(cwd);
|
|
56
|
+
// Step 3: Determine WU ID (explicit or from worktree)
|
|
57
|
+
const effectiveWuId = wuId ?? location.worktreeWuId;
|
|
58
|
+
// Step 4: Read WU state if we have a WU ID
|
|
59
|
+
let wu = null;
|
|
60
|
+
let worktreeGit;
|
|
61
|
+
if (effectiveWuId) {
|
|
62
|
+
const wuStateResult = await this.wuStateReader.readWuState(effectiveWuId, location.mainCheckout);
|
|
63
|
+
if (wuStateResult) {
|
|
64
|
+
wu = {
|
|
65
|
+
id: wuStateResult.id,
|
|
66
|
+
status: wuStateResult.status,
|
|
67
|
+
lane: wuStateResult.lane,
|
|
68
|
+
title: wuStateResult.title,
|
|
69
|
+
yamlPath: wuStateResult.yamlPath,
|
|
70
|
+
isConsistent: wuStateResult.isConsistent,
|
|
71
|
+
inconsistencyReason: wuStateResult.inconsistencyReason,
|
|
72
|
+
};
|
|
73
|
+
// Step 5: If running from main and WU is in_progress, read worktree git state
|
|
74
|
+
if (location.type === 'main' && wu.status === 'in_progress') {
|
|
75
|
+
const worktreePath = this.getWorktreePath(location.mainCheckout, wu.lane, wu.id);
|
|
76
|
+
worktreeGit = await this.gitStateReader.readGitState(worktreePath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Step 6: Create session state (inactive by default)
|
|
81
|
+
const session = {
|
|
82
|
+
isActive: false,
|
|
83
|
+
sessionId: null,
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
location,
|
|
87
|
+
git,
|
|
88
|
+
wu,
|
|
89
|
+
session,
|
|
90
|
+
worktreeGit,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get expected worktree path for a WU.
|
|
95
|
+
*/
|
|
96
|
+
getWorktreePath(mainCheckout, lane, wuId) {
|
|
97
|
+
const laneKebab = lane.toLowerCase().replace(/[: ]+/g, '-');
|
|
98
|
+
const wuIdLower = wuId.toLowerCase();
|
|
99
|
+
return `${mainCheckout}/worktrees/${laneKebab}-${wuIdLower}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use Cases Index
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Re-exports all use case classes.
|
|
7
|
+
*
|
|
8
|
+
* @module usecases
|
|
9
|
+
*/
|
|
10
|
+
export { ComputeContextUseCase, type ComputeContextOptions } from './compute-context.usecase.js';
|
|
11
|
+
export { ValidateCommandUseCase } from './validate-command.usecase.js';
|
|
12
|
+
export { AnalyzeRecoveryUseCase } from './analyze-recovery.usecase.js';
|
|
13
|
+
export { GetDashboardDataUseCase, type GetDashboardDataOptions, } from './get-dashboard-data.usecase.js';
|
|
14
|
+
export { GetSuggestionsUseCase, type GetSuggestionsOptions } from './get-suggestions.usecase.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use Cases Index
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Re-exports all use case classes.
|
|
7
|
+
*
|
|
8
|
+
* @module usecases
|
|
9
|
+
*/
|
|
10
|
+
// Context use cases
|
|
11
|
+
export { ComputeContextUseCase } from './compute-context.usecase.js';
|
|
12
|
+
// Validation use cases
|
|
13
|
+
export { ValidateCommandUseCase } from './validate-command.usecase.js';
|
|
14
|
+
// Recovery use cases
|
|
15
|
+
export { AnalyzeRecoveryUseCase } from './analyze-recovery.usecase.js';
|
|
16
|
+
// Existing use cases (pre-WU-1094)
|
|
17
|
+
export { GetDashboardDataUseCase, } from './get-dashboard-data.usecase.js';
|
|
18
|
+
export { GetSuggestionsUseCase } from './get-suggestions.usecase.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidateCommandUseCase
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Use case for validating commands against WU context.
|
|
7
|
+
* Uses constructor injection for command registry dependency.
|
|
8
|
+
*
|
|
9
|
+
* Hexagonal Architecture - Application Layer
|
|
10
|
+
* - Depends on port interface (ICommandRegistry)
|
|
11
|
+
* - Does NOT import from infrastructure layer
|
|
12
|
+
*
|
|
13
|
+
* @module usecases/validate-command.usecase
|
|
14
|
+
*/
|
|
15
|
+
import type { ICommandRegistry, CommandDefinition, WuContext } from '../ports/validation.ports.js';
|
|
16
|
+
import type { ValidationResult } from '../validation/types.js';
|
|
17
|
+
/**
|
|
18
|
+
* ValidateCommandUseCase
|
|
19
|
+
*
|
|
20
|
+
* Validates a command against the current WU context, returning
|
|
21
|
+
* validation errors and warnings with fix suggestions.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Using default registry via DI factory
|
|
25
|
+
* const useCase = createValidateCommandUseCase();
|
|
26
|
+
* const result = await useCase.execute('wu:done', context);
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Using custom registry for testing
|
|
30
|
+
* const useCase = new ValidateCommandUseCase(mockRegistry);
|
|
31
|
+
* const result = await useCase.execute('wu:claim', context);
|
|
32
|
+
*/
|
|
33
|
+
export declare class ValidateCommandUseCase {
|
|
34
|
+
private readonly commandRegistry;
|
|
35
|
+
constructor(commandRegistry: ICommandRegistry);
|
|
36
|
+
/**
|
|
37
|
+
* Execute the use case to validate a command.
|
|
38
|
+
*
|
|
39
|
+
* @param command - Command name (e.g., 'wu:done')
|
|
40
|
+
* @param context - Current WU context
|
|
41
|
+
* @returns Promise<ValidationResult> - Validation result with errors/warnings
|
|
42
|
+
*/
|
|
43
|
+
execute(command: string, context: WuContext): Promise<ValidationResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Get valid commands for the current context.
|
|
46
|
+
*
|
|
47
|
+
* @param context - Current WU context
|
|
48
|
+
* @returns Promise<CommandDefinition[]> - Array of valid commands
|
|
49
|
+
*/
|
|
50
|
+
getValidCommands(context: WuContext): Promise<CommandDefinition[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Generate fix command for location errors.
|
|
53
|
+
*/
|
|
54
|
+
private getLocationFixCommand;
|
|
55
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidateCommandUseCase
|
|
3
|
+
*
|
|
4
|
+
* WU-1094: INIT-002 Phase 2 - Implement adapters and dependency injection
|
|
5
|
+
*
|
|
6
|
+
* Use case for validating commands against WU context.
|
|
7
|
+
* Uses constructor injection for command registry dependency.
|
|
8
|
+
*
|
|
9
|
+
* Hexagonal Architecture - Application Layer
|
|
10
|
+
* - Depends on port interface (ICommandRegistry)
|
|
11
|
+
* - Does NOT import from infrastructure layer
|
|
12
|
+
*
|
|
13
|
+
* @module usecases/validate-command.usecase
|
|
14
|
+
*/
|
|
15
|
+
import { CONTEXT_VALIDATION } from '../wu-constants.js';
|
|
16
|
+
const { ERROR_CODES, SEVERITY } = CONTEXT_VALIDATION;
|
|
17
|
+
/**
|
|
18
|
+
* ValidateCommandUseCase
|
|
19
|
+
*
|
|
20
|
+
* Validates a command against the current WU context, returning
|
|
21
|
+
* validation errors and warnings with fix suggestions.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Using default registry via DI factory
|
|
25
|
+
* const useCase = createValidateCommandUseCase();
|
|
26
|
+
* const result = await useCase.execute('wu:done', context);
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Using custom registry for testing
|
|
30
|
+
* const useCase = new ValidateCommandUseCase(mockRegistry);
|
|
31
|
+
* const result = await useCase.execute('wu:claim', context);
|
|
32
|
+
*/
|
|
33
|
+
export class ValidateCommandUseCase {
|
|
34
|
+
commandRegistry;
|
|
35
|
+
constructor(commandRegistry) {
|
|
36
|
+
this.commandRegistry = commandRegistry;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute the use case to validate a command.
|
|
40
|
+
*
|
|
41
|
+
* @param command - Command name (e.g., 'wu:done')
|
|
42
|
+
* @param context - Current WU context
|
|
43
|
+
* @returns Promise<ValidationResult> - Validation result with errors/warnings
|
|
44
|
+
*/
|
|
45
|
+
async execute(command, context) {
|
|
46
|
+
const errors = [];
|
|
47
|
+
const warnings = [];
|
|
48
|
+
// Step 1: Look up command definition
|
|
49
|
+
const commandDef = this.commandRegistry.getCommandDefinition(command);
|
|
50
|
+
if (!commandDef) {
|
|
51
|
+
errors.push({
|
|
52
|
+
// Use WU_NOT_FOUND as a general "not found" error code
|
|
53
|
+
// The message clarifies it's an unknown command
|
|
54
|
+
code: ERROR_CODES.WU_NOT_FOUND,
|
|
55
|
+
message: `Unknown command: ${command}`,
|
|
56
|
+
fixCommand: null,
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
errors,
|
|
61
|
+
warnings,
|
|
62
|
+
context,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Step 2: Check location requirement
|
|
66
|
+
if (commandDef.requiredLocation !== null) {
|
|
67
|
+
if (context.location.type !== commandDef.requiredLocation) {
|
|
68
|
+
const fixCommand = this.getLocationFixCommand(commandDef.requiredLocation, context, command);
|
|
69
|
+
errors.push({
|
|
70
|
+
code: ERROR_CODES.WRONG_LOCATION,
|
|
71
|
+
message: `${command} must be run from ${commandDef.requiredLocation} checkout`,
|
|
72
|
+
fixCommand,
|
|
73
|
+
context: {
|
|
74
|
+
required: commandDef.requiredLocation,
|
|
75
|
+
actual: context.location.type,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Step 3: Check WU status requirement
|
|
81
|
+
if (commandDef.requiredWuStatus !== null) {
|
|
82
|
+
const actualStatus = context.wu?.status ?? null;
|
|
83
|
+
if (actualStatus !== commandDef.requiredWuStatus) {
|
|
84
|
+
errors.push({
|
|
85
|
+
code: ERROR_CODES.WRONG_WU_STATUS,
|
|
86
|
+
message: `${command} requires WU status '${commandDef.requiredWuStatus}' but got '${actualStatus ?? 'no WU'}'`,
|
|
87
|
+
fixCommand: null,
|
|
88
|
+
context: {
|
|
89
|
+
required: commandDef.requiredWuStatus,
|
|
90
|
+
actual: actualStatus,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Step 4: Check predicates
|
|
96
|
+
if (commandDef.predicates && commandDef.predicates.length > 0) {
|
|
97
|
+
for (const predicate of commandDef.predicates) {
|
|
98
|
+
const passed = predicate.check(context);
|
|
99
|
+
if (!passed) {
|
|
100
|
+
const message = predicate.getFixMessage
|
|
101
|
+
? predicate.getFixMessage(context)
|
|
102
|
+
: predicate.description;
|
|
103
|
+
if (predicate.severity === SEVERITY.ERROR) {
|
|
104
|
+
errors.push({
|
|
105
|
+
// Use GATES_NOT_PASSED for predicate failures (e.g., dirty worktree)
|
|
106
|
+
code: ERROR_CODES.GATES_NOT_PASSED,
|
|
107
|
+
message,
|
|
108
|
+
fixCommand: null,
|
|
109
|
+
context: { predicateId: predicate.id },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
warnings.push({
|
|
114
|
+
id: predicate.id,
|
|
115
|
+
message,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
valid: errors.length === 0,
|
|
123
|
+
errors,
|
|
124
|
+
warnings,
|
|
125
|
+
context,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get valid commands for the current context.
|
|
130
|
+
*
|
|
131
|
+
* @param context - Current WU context
|
|
132
|
+
* @returns Promise<CommandDefinition[]> - Array of valid commands
|
|
133
|
+
*/
|
|
134
|
+
async getValidCommands(context) {
|
|
135
|
+
return this.commandRegistry.getValidCommandsForContext(context);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Generate fix command for location errors.
|
|
139
|
+
*/
|
|
140
|
+
getLocationFixCommand(requiredLocation, context, command) {
|
|
141
|
+
if (requiredLocation === 'main') {
|
|
142
|
+
// Need to cd to main checkout
|
|
143
|
+
const wuIdParam = context.wu?.id ? ` --id ${context.wu.id}` : '';
|
|
144
|
+
return `cd ${context.location.mainCheckout} && pnpm ${command}${wuIdParam}`;
|
|
145
|
+
}
|
|
146
|
+
else if (requiredLocation === 'worktree') {
|
|
147
|
+
// Need to cd to worktree
|
|
148
|
+
if (context.location.worktreeName) {
|
|
149
|
+
return `cd ${context.location.mainCheckout}/worktrees/${context.location.worktreeName}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
}
|