@lumenflow/core 1.5.0 → 2.0.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.
Files changed (74) hide show
  1. package/README.md +325 -1
  2. package/dist/adapters/context-adapters.d.ts +90 -0
  3. package/dist/adapters/context-adapters.js +99 -0
  4. package/dist/adapters/index.d.ts +14 -0
  5. package/dist/adapters/index.js +18 -0
  6. package/dist/adapters/recovery-adapters.d.ts +40 -0
  7. package/dist/adapters/recovery-adapters.js +43 -0
  8. package/dist/adapters/validation-adapters.d.ts +52 -0
  9. package/dist/adapters/validation-adapters.js +59 -0
  10. package/dist/context/context-computer.d.ts +46 -0
  11. package/dist/context/context-computer.js +125 -0
  12. package/dist/context/git-state-reader.d.ts +51 -0
  13. package/dist/context/git-state-reader.js +61 -0
  14. package/dist/context/index.d.ts +17 -0
  15. package/dist/context/index.js +17 -0
  16. package/dist/context/location-resolver.d.ts +48 -0
  17. package/dist/context/location-resolver.js +175 -0
  18. package/dist/context/wu-state-reader.d.ts +37 -0
  19. package/dist/context/wu-state-reader.js +76 -0
  20. package/dist/context-di.d.ts +184 -0
  21. package/dist/context-di.js +178 -0
  22. package/dist/context-validation-integration.d.ts +77 -0
  23. package/dist/context-validation-integration.js +157 -0
  24. package/dist/domain/context.schemas.d.ts +147 -0
  25. package/dist/domain/context.schemas.js +126 -0
  26. package/dist/domain/index.d.ts +14 -0
  27. package/dist/domain/index.js +18 -0
  28. package/dist/domain/recovery.schemas.d.ts +115 -0
  29. package/dist/domain/recovery.schemas.js +83 -0
  30. package/dist/domain/validation.schemas.d.ts +146 -0
  31. package/dist/domain/validation.schemas.js +114 -0
  32. package/dist/index.d.ts +17 -0
  33. package/dist/index.js +43 -0
  34. package/dist/lumenflow-config-schema.d.ts +41 -0
  35. package/dist/lumenflow-config-schema.js +37 -0
  36. package/dist/ports/context.ports.d.ts +135 -0
  37. package/dist/ports/context.ports.js +21 -0
  38. package/dist/ports/core-tools.ports.d.ts +266 -0
  39. package/dist/ports/core-tools.ports.js +21 -0
  40. package/dist/ports/git-validator.ports.d.ts +314 -0
  41. package/dist/ports/git-validator.ports.js +12 -0
  42. package/dist/ports/index.d.ts +20 -0
  43. package/dist/ports/index.js +20 -0
  44. package/dist/ports/recovery.ports.d.ts +58 -0
  45. package/dist/ports/recovery.ports.js +17 -0
  46. package/dist/ports/validation.ports.d.ts +74 -0
  47. package/dist/ports/validation.ports.js +17 -0
  48. package/dist/ports/wu-helpers.ports.d.ts +224 -0
  49. package/dist/ports/wu-helpers.ports.js +12 -0
  50. package/dist/recovery/index.d.ts +11 -0
  51. package/dist/recovery/index.js +11 -0
  52. package/dist/recovery/recovery-analyzer.d.ts +66 -0
  53. package/dist/recovery/recovery-analyzer.js +129 -0
  54. package/dist/usecases/analyze-recovery.usecase.d.ts +42 -0
  55. package/dist/usecases/analyze-recovery.usecase.js +45 -0
  56. package/dist/usecases/compute-context.usecase.d.ts +62 -0
  57. package/dist/usecases/compute-context.usecase.js +101 -0
  58. package/dist/usecases/index.d.ts +14 -0
  59. package/dist/usecases/index.js +18 -0
  60. package/dist/usecases/validate-command.usecase.d.ts +55 -0
  61. package/dist/usecases/validate-command.usecase.js +154 -0
  62. package/dist/validation/command-registry.d.ts +38 -0
  63. package/dist/validation/command-registry.js +229 -0
  64. package/dist/validation/index.d.ts +15 -0
  65. package/dist/validation/index.js +15 -0
  66. package/dist/validation/types.d.ts +135 -0
  67. package/dist/validation/types.js +11 -0
  68. package/dist/validation/validate-command.d.ts +27 -0
  69. package/dist/validation/validate-command.js +160 -0
  70. package/dist/wu-constants.d.ts +136 -0
  71. package/dist/wu-constants.js +124 -0
  72. package/dist/wu-helpers.d.ts +5 -1
  73. package/dist/wu-helpers.js +12 -1
  74. package/package.json +4 -2
@@ -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';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Validation Types for WU Context
3
+ *
4
+ * WU-1090: Context-aware state machine for WU lifecycle commands
5
+ *
6
+ * Type definitions for the unified context model, command definitions,
7
+ * and validation results.
8
+ *
9
+ * @module
10
+ */
11
+ import type { LocationType, PredicateSeverity, ValidationErrorCode } from '../wu-constants.js';
12
+ import type { LocationContext } from '../context/location-resolver.js';
13
+ import type { GitState } from '../context/git-state-reader.js';
14
+ /**
15
+ * WU state information from YAML and state store.
16
+ */
17
+ export interface WuState {
18
+ /** WU ID (e.g., 'WU-1090') */
19
+ id: string;
20
+ /** Current status (ready, in_progress, blocked, done, etc.) */
21
+ status: string;
22
+ /** Lane name */
23
+ lane: string;
24
+ /** WU title */
25
+ title: string;
26
+ /** Path to WU YAML file */
27
+ yamlPath: string;
28
+ /** Whether YAML and state store are consistent */
29
+ isConsistent: boolean;
30
+ /** Reason for inconsistency if not consistent */
31
+ inconsistencyReason: string | null;
32
+ }
33
+ /**
34
+ * Session state for active WU work.
35
+ */
36
+ export interface SessionState {
37
+ /** Whether a session is active */
38
+ isActive: boolean;
39
+ /** Session ID if active */
40
+ sessionId: string | null;
41
+ }
42
+ /**
43
+ * Unified context model for WU operations.
44
+ *
45
+ * Captures all environmental state relevant to command execution.
46
+ */
47
+ export interface WuContext {
48
+ /** Location context (main vs worktree) */
49
+ location: LocationContext;
50
+ /** Git state (branch, dirty, staged, ahead/behind) */
51
+ git: GitState;
52
+ /** WU state (null if no WU specified) */
53
+ wu: WuState | null;
54
+ /** Session state */
55
+ session: SessionState;
56
+ /**
57
+ * Git state of the WU's worktree (WU-1092).
58
+ *
59
+ * When running wu:done from main checkout, we need to check the worktree's
60
+ * git state, not main's. This field is populated when:
61
+ * - Running from main checkout (location.type === 'main')
62
+ * - A WU is specified (wu !== null)
63
+ * - WU has an active worktree (status === 'in_progress')
64
+ *
65
+ * If undefined, predicates should fall back to checking `git.isDirty`.
66
+ */
67
+ worktreeGit?: GitState;
68
+ }
69
+ /**
70
+ * Command predicate for custom validation checks.
71
+ */
72
+ export interface CommandPredicate {
73
+ /** Unique identifier for the predicate */
74
+ id: string;
75
+ /** Human-readable description */
76
+ description: string;
77
+ /** Severity: 'error' blocks execution, 'warning' allows with warning */
78
+ severity: PredicateSeverity;
79
+ /** Function that checks the predicate against context */
80
+ check: (context: WuContext) => boolean;
81
+ /** Function that generates fix message if check fails */
82
+ getFixMessage?: (context: WuContext) => string;
83
+ }
84
+ /**
85
+ * Command definition for a wu:* command.
86
+ */
87
+ export interface CommandDefinition {
88
+ /** Command name (e.g., 'wu:create') */
89
+ name: string;
90
+ /** Human-readable description */
91
+ description: string;
92
+ /** Required location type (null = any location) */
93
+ requiredLocation: LocationType | null;
94
+ /** Required WU status (null = no status requirement) */
95
+ requiredWuStatus: string | null;
96
+ /** Custom predicates for additional checks */
97
+ predicates?: CommandPredicate[];
98
+ /** Function to get next steps after success */
99
+ getNextSteps?: (context: WuContext) => string[];
100
+ }
101
+ /**
102
+ * Validation error with fix guidance.
103
+ */
104
+ export interface ValidationError {
105
+ /** Error code */
106
+ code: ValidationErrorCode;
107
+ /** Human-readable message */
108
+ message: string;
109
+ /** Copy-paste ready fix command (if available) */
110
+ fixCommand: string | null;
111
+ /** Additional context for debugging */
112
+ context?: Record<string, unknown>;
113
+ }
114
+ /**
115
+ * Validation warning (non-blocking).
116
+ */
117
+ export interface ValidationWarning {
118
+ /** Warning ID */
119
+ id: string;
120
+ /** Human-readable message */
121
+ message: string;
122
+ }
123
+ /**
124
+ * Validation result for a command.
125
+ */
126
+ export interface ValidationResult {
127
+ /** Whether command can proceed */
128
+ valid: boolean;
129
+ /** Errors that block execution */
130
+ errors: ValidationError[];
131
+ /** Warnings that don't block execution */
132
+ warnings: ValidationWarning[];
133
+ /** Context used for validation (for debugging) */
134
+ context: WuContext;
135
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Validation Types for WU Context
3
+ *
4
+ * WU-1090: Context-aware state machine for WU lifecycle commands
5
+ *
6
+ * Type definitions for the unified context model, command definitions,
7
+ * and validation results.
8
+ *
9
+ * @module
10
+ */
11
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Command Validation for WU Lifecycle Commands
3
+ *
4
+ * WU-1090: Context-aware state machine for WU lifecycle commands
5
+ *
6
+ * Validates commands against the current context and provides:
7
+ * - Location validation with copy-paste fix commands
8
+ * - WU status validation with guidance
9
+ * - Predicate validation with severity levels
10
+ *
11
+ * @module
12
+ */
13
+ import type { ValidationResult, WuContext } from './types.js';
14
+ /**
15
+ * Validate a command against the current context.
16
+ *
17
+ * Returns a ValidationResult with:
18
+ * - valid: boolean - whether command can proceed
19
+ * - errors: array of blocking errors with fix guidance
20
+ * - warnings: array of non-blocking warnings
21
+ * - context: the input context for debugging
22
+ *
23
+ * @param command - Command name (e.g., 'wu:done')
24
+ * @param context - Current WU context
25
+ * @returns ValidationResult
26
+ */
27
+ export declare function validateCommand(command: string, context: WuContext): ValidationResult;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Command Validation for WU Lifecycle Commands
3
+ *
4
+ * WU-1090: Context-aware state machine for WU lifecycle commands
5
+ *
6
+ * Validates commands against the current context and provides:
7
+ * - Location validation with copy-paste fix commands
8
+ * - WU status validation with guidance
9
+ * - Predicate validation with severity levels
10
+ *
11
+ * @module
12
+ */
13
+ import { CONTEXT_VALIDATION, } from '../wu-constants.js';
14
+ import { getCommandDefinition } from './command-registry.js';
15
+ const { LOCATION_TYPES, ERROR_CODES, SEVERITY } = CONTEXT_VALIDATION;
16
+ /**
17
+ * Create a validation error with fix command.
18
+ */
19
+ function createError(code, message, fixCommand = null, context) {
20
+ return { code, message, fixCommand, context };
21
+ }
22
+ /**
23
+ * Create a validation warning.
24
+ */
25
+ function createWarning(id, message) {
26
+ return { id, message };
27
+ }
28
+ /**
29
+ * Get human-readable location name.
30
+ */
31
+ function locationName(type) {
32
+ switch (type) {
33
+ case LOCATION_TYPES.MAIN:
34
+ return 'main checkout';
35
+ case LOCATION_TYPES.WORKTREE:
36
+ return 'worktree';
37
+ case LOCATION_TYPES.DETACHED:
38
+ return 'detached HEAD';
39
+ default:
40
+ return 'unknown location';
41
+ }
42
+ }
43
+ /**
44
+ * Validate location requirement.
45
+ */
46
+ function validateLocation(def, context) {
47
+ if (def.requiredLocation === null)
48
+ return null;
49
+ const currentLocation = context.location.type;
50
+ if (currentLocation === def.requiredLocation)
51
+ return null;
52
+ const requiredName = locationName(def.requiredLocation);
53
+ const currentName = locationName(currentLocation);
54
+ // Generate copy-paste fix command with actual path
55
+ let fixCommand = null;
56
+ if (def.requiredLocation === LOCATION_TYPES.MAIN) {
57
+ fixCommand = `cd ${context.location.mainCheckout}`;
58
+ }
59
+ else if (def.requiredLocation === LOCATION_TYPES.WORKTREE) {
60
+ // If we know the WU ID, suggest the expected worktree path
61
+ if (context.wu?.id) {
62
+ const laneKebab = (context.wu.lane || 'lane').toLowerCase().replace(/[: ]+/g, '-');
63
+ const wuIdLower = context.wu.id.toLowerCase();
64
+ fixCommand = `cd ${context.location.mainCheckout}/worktrees/${laneKebab}-${wuIdLower}`;
65
+ }
66
+ }
67
+ return createError(ERROR_CODES.WRONG_LOCATION, `${def.name} requires ${requiredName}, but you are in ${currentName}`, fixCommand, {
68
+ required: def.requiredLocation,
69
+ current: currentLocation,
70
+ });
71
+ }
72
+ /**
73
+ * Validate WU status requirement.
74
+ */
75
+ function validateWuStatus(def, context) {
76
+ // No status requirement means no WU needed
77
+ if (def.requiredWuStatus === null)
78
+ return null;
79
+ // Status requirement but no WU provided
80
+ if (context.wu === null) {
81
+ return createError(ERROR_CODES.WU_NOT_FOUND, `${def.name} requires a WU with status '${def.requiredWuStatus}', but no WU was specified`, null, { required: def.requiredWuStatus });
82
+ }
83
+ // Check if status matches
84
+ if (context.wu.status !== def.requiredWuStatus) {
85
+ return createError(ERROR_CODES.WRONG_WU_STATUS, `${def.name} requires WU status '${def.requiredWuStatus}', but ${context.wu.id} is '${context.wu.status}'`, null, {
86
+ required: def.requiredWuStatus,
87
+ current: context.wu.status,
88
+ wuId: context.wu.id,
89
+ });
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * Validate command predicates.
95
+ */
96
+ function validatePredicates(def, context) {
97
+ const errors = [];
98
+ const warnings = [];
99
+ if (!def.predicates || def.predicates.length === 0) {
100
+ return { errors, warnings };
101
+ }
102
+ for (const predicate of def.predicates) {
103
+ const passed = predicate.check(context);
104
+ if (!passed) {
105
+ const fixMessage = predicate.getFixMessage ? predicate.getFixMessage(context) : null;
106
+ if (predicate.severity === SEVERITY.ERROR) {
107
+ errors.push(createError(ERROR_CODES.GATES_NOT_PASSED, // Using as generic predicate failure
108
+ predicate.description, fixMessage, { predicateId: predicate.id }));
109
+ }
110
+ else {
111
+ warnings.push(createWarning(predicate.id, predicate.description));
112
+ }
113
+ }
114
+ }
115
+ return { errors, warnings };
116
+ }
117
+ /**
118
+ * Validate a command against the current context.
119
+ *
120
+ * Returns a ValidationResult with:
121
+ * - valid: boolean - whether command can proceed
122
+ * - errors: array of blocking errors with fix guidance
123
+ * - warnings: array of non-blocking warnings
124
+ * - context: the input context for debugging
125
+ *
126
+ * @param command - Command name (e.g., 'wu:done')
127
+ * @param context - Current WU context
128
+ * @returns ValidationResult
129
+ */
130
+ export function validateCommand(command, context) {
131
+ const errors = [];
132
+ const warnings = [];
133
+ // Get command definition
134
+ const def = getCommandDefinition(command);
135
+ if (!def) {
136
+ errors.push(createError(ERROR_CODES.WU_NOT_FOUND, // Reusing for unknown command
137
+ `Unknown command: ${command}`, null));
138
+ return { valid: false, errors, warnings, context };
139
+ }
140
+ // Validate location requirement
141
+ const locationError = validateLocation(def, context);
142
+ if (locationError) {
143
+ errors.push(locationError);
144
+ }
145
+ // Validate WU status requirement
146
+ const statusError = validateWuStatus(def, context);
147
+ if (statusError) {
148
+ errors.push(statusError);
149
+ }
150
+ // Validate predicates
151
+ const predicateResults = validatePredicates(def, context);
152
+ errors.push(...predicateResults.errors);
153
+ warnings.push(...predicateResults.warnings);
154
+ return {
155
+ valid: errors.length === 0,
156
+ errors,
157
+ warnings,
158
+ context,
159
+ };
160
+ }