@lumenflow/core 1.4.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 +362 -7
- 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/agent-patterns-registry.d.ts +100 -2
- package/dist/agent-patterns-registry.js +124 -0
- package/dist/arg-parser.js +7 -0
- package/dist/branch-check.d.ts +32 -3
- package/dist/branch-check.js +81 -15
- package/dist/color-support.d.ts +32 -0
- package/dist/color-support.js +64 -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/cycle-detector.d.ts +51 -0
- package/dist/cycle-detector.js +89 -0
- package/dist/dependency-graph.js +1 -11
- 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 +17 -0
- package/dist/index.js +47 -0
- package/dist/lumenflow-config-schema.d.ts +47 -0
- package/dist/lumenflow-config-schema.js +84 -4
- 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-done-preflight.js +8 -1
- package/dist/wu-helpers.d.ts +5 -1
- package/dist/wu-helpers.js +12 -1
- package/package.json +3 -6
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Registry for WU Lifecycle Commands
|
|
3
|
+
*
|
|
4
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
5
|
+
*
|
|
6
|
+
* Declarative definitions for all wu:* commands specifying:
|
|
7
|
+
* - Required location (main vs worktree)
|
|
8
|
+
* - Required WU status
|
|
9
|
+
* - Custom validation predicates
|
|
10
|
+
* - Success next steps
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import type { CommandDefinition, CommandPredicate, WuContext } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Command registry mapping command names to definitions.
|
|
17
|
+
*/
|
|
18
|
+
export declare const COMMAND_REGISTRY: Map<string, CommandDefinition>;
|
|
19
|
+
/**
|
|
20
|
+
* Get command definition by name.
|
|
21
|
+
*
|
|
22
|
+
* @param command - Command name (e.g., 'wu:create')
|
|
23
|
+
* @returns CommandDefinition or null if not found
|
|
24
|
+
*/
|
|
25
|
+
export declare function getCommandDefinition(command: string): CommandDefinition | null;
|
|
26
|
+
/**
|
|
27
|
+
* Get all commands valid for the current context.
|
|
28
|
+
*
|
|
29
|
+
* A command is valid if:
|
|
30
|
+
* - Location requirement is satisfied (or null = any)
|
|
31
|
+
* - WU status requirement is satisfied (or null = no WU required)
|
|
32
|
+
* - All error-severity predicates pass
|
|
33
|
+
*
|
|
34
|
+
* @param context - Current WU context
|
|
35
|
+
* @returns Array of valid CommandDefinitions
|
|
36
|
+
*/
|
|
37
|
+
export declare function getValidCommandsForContext(context: WuContext): CommandDefinition[];
|
|
38
|
+
export type { CommandDefinition, CommandPredicate, WuContext };
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Registry for WU Lifecycle Commands
|
|
3
|
+
*
|
|
4
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
5
|
+
*
|
|
6
|
+
* Declarative definitions for all wu:* commands specifying:
|
|
7
|
+
* - Required location (main vs worktree)
|
|
8
|
+
* - Required WU status
|
|
9
|
+
* - Custom validation predicates
|
|
10
|
+
* - Success next steps
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { CONTEXT_VALIDATION, WU_STATUS } from '../wu-constants.js';
|
|
15
|
+
const { LOCATION_TYPES, COMMANDS, SEVERITY } = CONTEXT_VALIDATION;
|
|
16
|
+
/**
|
|
17
|
+
* Predicate: Check if worktree is clean (no uncommitted changes).
|
|
18
|
+
*
|
|
19
|
+
* WU-1092: Checks worktreeGit.isDirty when available (running from main),
|
|
20
|
+
* falls back to git.isDirty otherwise (running from worktree itself).
|
|
21
|
+
*/
|
|
22
|
+
const worktreeCleanPredicate = {
|
|
23
|
+
id: 'worktree-clean',
|
|
24
|
+
description: 'Worktree must not have uncommitted changes',
|
|
25
|
+
severity: SEVERITY.ERROR,
|
|
26
|
+
check: (context) => {
|
|
27
|
+
// WU-1092: Prefer worktreeGit when available (running wu:done from main)
|
|
28
|
+
if (context.worktreeGit !== undefined) {
|
|
29
|
+
return !context.worktreeGit.isDirty;
|
|
30
|
+
}
|
|
31
|
+
// Fallback: check current git state (running from worktree itself)
|
|
32
|
+
return !context.git.isDirty;
|
|
33
|
+
},
|
|
34
|
+
getFixMessage: (context) => {
|
|
35
|
+
const worktreePath = context.location.worktreeName
|
|
36
|
+
? `worktrees/${context.location.worktreeName}`
|
|
37
|
+
: 'worktree';
|
|
38
|
+
return `Commit or stash changes in ${worktreePath} before running wu:done`;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Predicate: Check if branch has commits to push.
|
|
43
|
+
*/
|
|
44
|
+
const hasCommitsPredicate = {
|
|
45
|
+
id: 'has-commits',
|
|
46
|
+
description: 'Branch must have commits ahead of tracking branch',
|
|
47
|
+
severity: SEVERITY.WARNING,
|
|
48
|
+
check: (context) => context.git.ahead > 0,
|
|
49
|
+
getFixMessage: () => 'No new commits to merge. Did you forget to commit your changes?',
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Predicate: Check if WU state is consistent between YAML and state store.
|
|
53
|
+
*/
|
|
54
|
+
const stateConsistentPredicate = {
|
|
55
|
+
id: 'state-consistent',
|
|
56
|
+
description: 'WU state must be consistent between YAML and state store',
|
|
57
|
+
severity: SEVERITY.WARNING,
|
|
58
|
+
check: (context) => context.wu?.isConsistent ?? true,
|
|
59
|
+
getFixMessage: (context) => context.wu?.inconsistencyReason || 'State store and YAML are inconsistent',
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Command definition for wu:create.
|
|
63
|
+
*/
|
|
64
|
+
const wuCreate = {
|
|
65
|
+
name: COMMANDS.WU_CREATE,
|
|
66
|
+
description: 'Create a new WU YAML spec',
|
|
67
|
+
requiredLocation: LOCATION_TYPES.MAIN,
|
|
68
|
+
requiredWuStatus: null, // Creates new WU, no existing status required
|
|
69
|
+
predicates: [],
|
|
70
|
+
getNextSteps: (context) => [
|
|
71
|
+
`1. Edit the WU spec with acceptance criteria`,
|
|
72
|
+
`2. Run: pnpm wu:claim --id ${context.wu?.id || 'WU-XXX'} --lane "<lane>"`,
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Command definition for wu:claim.
|
|
77
|
+
*/
|
|
78
|
+
const wuClaim = {
|
|
79
|
+
name: COMMANDS.WU_CLAIM,
|
|
80
|
+
description: 'Claim a WU and create worktree',
|
|
81
|
+
requiredLocation: LOCATION_TYPES.MAIN,
|
|
82
|
+
requiredWuStatus: WU_STATUS.READY,
|
|
83
|
+
predicates: [],
|
|
84
|
+
getNextSteps: (context) => {
|
|
85
|
+
const wuId = context.wu?.id?.toLowerCase() || 'wu-xxx';
|
|
86
|
+
const lane = context.wu?.lane?.toLowerCase().replace(/[: ]+/g, '-') || 'lane';
|
|
87
|
+
return [
|
|
88
|
+
`1. cd worktrees/${lane}-${wuId}`,
|
|
89
|
+
'2. Implement changes per acceptance criteria',
|
|
90
|
+
'3. Run: pnpm gates',
|
|
91
|
+
`4. Return to main and run: pnpm wu:done --id ${context.wu?.id || 'WU-XXX'}`,
|
|
92
|
+
];
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Command definition for wu:done.
|
|
97
|
+
*/
|
|
98
|
+
const wuDone = {
|
|
99
|
+
name: COMMANDS.WU_DONE,
|
|
100
|
+
description: 'Complete WU (merge, stamp, cleanup)',
|
|
101
|
+
requiredLocation: LOCATION_TYPES.MAIN,
|
|
102
|
+
requiredWuStatus: WU_STATUS.IN_PROGRESS,
|
|
103
|
+
predicates: [worktreeCleanPredicate, hasCommitsPredicate, stateConsistentPredicate],
|
|
104
|
+
getNextSteps: () => [
|
|
105
|
+
'WU completed successfully!',
|
|
106
|
+
'Check backlog.md for your next task or run: pnpm wu:status',
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Command definition for wu:block.
|
|
111
|
+
*/
|
|
112
|
+
const wuBlock = {
|
|
113
|
+
name: COMMANDS.WU_BLOCK,
|
|
114
|
+
description: 'Block WU due to external dependency',
|
|
115
|
+
requiredLocation: null, // Can run from main or worktree
|
|
116
|
+
requiredWuStatus: WU_STATUS.IN_PROGRESS,
|
|
117
|
+
predicates: [],
|
|
118
|
+
getNextSteps: () => [
|
|
119
|
+
'WU blocked. The lane is now available for other work.',
|
|
120
|
+
'When blocker is resolved, run: pnpm wu:unblock --id WU-XXX',
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Command definition for wu:unblock.
|
|
125
|
+
*/
|
|
126
|
+
const wuUnblock = {
|
|
127
|
+
name: COMMANDS.WU_UNBLOCK,
|
|
128
|
+
description: 'Unblock a blocked WU',
|
|
129
|
+
requiredLocation: null, // Can run from main or worktree
|
|
130
|
+
requiredWuStatus: WU_STATUS.BLOCKED,
|
|
131
|
+
predicates: [],
|
|
132
|
+
getNextSteps: (context) => [
|
|
133
|
+
'WU unblocked and returned to in_progress.',
|
|
134
|
+
`Continue working in the worktree or run: pnpm wu:done --id ${context.wu?.id || 'WU-XXX'}`,
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Command definition for wu:status.
|
|
139
|
+
*/
|
|
140
|
+
const wuStatus = {
|
|
141
|
+
name: COMMANDS.WU_STATUS,
|
|
142
|
+
description: 'Show WU status, location, and valid commands',
|
|
143
|
+
requiredLocation: null, // Informational, works anywhere
|
|
144
|
+
requiredWuStatus: null, // Works without WU context too
|
|
145
|
+
predicates: [],
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Command definition for wu:recover.
|
|
149
|
+
*/
|
|
150
|
+
const wuRecover = {
|
|
151
|
+
name: COMMANDS.WU_RECOVER,
|
|
152
|
+
description: 'Analyze and fix WU state inconsistencies',
|
|
153
|
+
requiredLocation: LOCATION_TYPES.MAIN,
|
|
154
|
+
requiredWuStatus: null, // Handles any state
|
|
155
|
+
predicates: [],
|
|
156
|
+
getNextSteps: () => ['Review recovery actions and confirm to proceed.'],
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Command registry mapping command names to definitions.
|
|
160
|
+
*/
|
|
161
|
+
export const COMMAND_REGISTRY = new Map([
|
|
162
|
+
[COMMANDS.WU_CREATE, wuCreate],
|
|
163
|
+
[COMMANDS.WU_CLAIM, wuClaim],
|
|
164
|
+
[COMMANDS.WU_DONE, wuDone],
|
|
165
|
+
[COMMANDS.WU_BLOCK, wuBlock],
|
|
166
|
+
[COMMANDS.WU_UNBLOCK, wuUnblock],
|
|
167
|
+
[COMMANDS.WU_STATUS, wuStatus],
|
|
168
|
+
[COMMANDS.WU_RECOVER, wuRecover],
|
|
169
|
+
]);
|
|
170
|
+
/**
|
|
171
|
+
* Get command definition by name.
|
|
172
|
+
*
|
|
173
|
+
* @param command - Command name (e.g., 'wu:create')
|
|
174
|
+
* @returns CommandDefinition or null if not found
|
|
175
|
+
*/
|
|
176
|
+
export function getCommandDefinition(command) {
|
|
177
|
+
return COMMAND_REGISTRY.get(command) ?? null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if a command's location requirement is satisfied.
|
|
181
|
+
*/
|
|
182
|
+
function isLocationValid(def, locationType) {
|
|
183
|
+
// null means any location is valid
|
|
184
|
+
if (def.requiredLocation === null)
|
|
185
|
+
return true;
|
|
186
|
+
return def.requiredLocation === locationType;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if a command's WU status requirement is satisfied.
|
|
190
|
+
*/
|
|
191
|
+
function isStatusValid(def, wuStatus) {
|
|
192
|
+
// null requirement means no status check needed
|
|
193
|
+
if (def.requiredWuStatus === null)
|
|
194
|
+
return true;
|
|
195
|
+
return def.requiredWuStatus === wuStatus;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Check if all predicates pass (ignoring warnings).
|
|
199
|
+
*/
|
|
200
|
+
function arePredicatesValid(def, context) {
|
|
201
|
+
if (!def.predicates || def.predicates.length === 0)
|
|
202
|
+
return true;
|
|
203
|
+
// Only check error-severity predicates for validity
|
|
204
|
+
return def.predicates.filter((p) => p.severity === SEVERITY.ERROR).every((p) => p.check(context));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get all commands valid for the current context.
|
|
208
|
+
*
|
|
209
|
+
* A command is valid if:
|
|
210
|
+
* - Location requirement is satisfied (or null = any)
|
|
211
|
+
* - WU status requirement is satisfied (or null = no WU required)
|
|
212
|
+
* - All error-severity predicates pass
|
|
213
|
+
*
|
|
214
|
+
* @param context - Current WU context
|
|
215
|
+
* @returns Array of valid CommandDefinitions
|
|
216
|
+
*/
|
|
217
|
+
export function getValidCommandsForContext(context) {
|
|
218
|
+
const validCommands = [];
|
|
219
|
+
const locationType = context.location.type;
|
|
220
|
+
const wuStatus = context.wu?.status ?? null;
|
|
221
|
+
for (const def of COMMAND_REGISTRY.values()) {
|
|
222
|
+
if (isLocationValid(def, locationType) &&
|
|
223
|
+
isStatusValid(def, wuStatus) &&
|
|
224
|
+
arePredicatesValid(def, context)) {
|
|
225
|
+
validCommands.push(def);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return validCommands;
|
|
229
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Module
|
|
3
|
+
*
|
|
4
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* - Type definitions
|
|
8
|
+
* - Command registry
|
|
9
|
+
* - Validation functions
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
export * from './types.js';
|
|
14
|
+
export * from './command-registry.js';
|
|
15
|
+
export * from './validate-command.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Module
|
|
3
|
+
*
|
|
4
|
+
* WU-1090: Context-aware state machine for WU lifecycle commands
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* - Type definitions
|
|
8
|
+
* - Command registry
|
|
9
|
+
* - Validation functions
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
export * from './types.js';
|
|
14
|
+
export * from './command-registry.js';
|
|
15
|
+
export * from './validate-command.js';
|