@lumenflow/core 2.2.2 → 2.3.2
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/dist/active-wu-detector.d.ts +1 -1
- package/dist/active-wu-detector.js +1 -1
- package/dist/arg-parser.js +51 -18
- package/dist/backlog-generator.d.ts +4 -4
- package/dist/backlog-generator.js +4 -4
- package/dist/backlog-sync-validator.js +1 -1
- package/dist/cleanup-lock.d.ts +9 -2
- package/dist/cleanup-lock.js +17 -7
- package/dist/code-path-validator.d.ts +3 -3
- package/dist/code-path-validator.js +3 -3
- package/dist/compliance-parser.d.ts +1 -1
- package/dist/compliance-parser.js +1 -1
- package/dist/constants/backlog-patterns.d.ts +1 -1
- package/dist/constants/backlog-patterns.js +1 -1
- package/dist/constants/dora-constants.d.ts +1 -1
- package/dist/constants/dora-constants.js +1 -1
- package/dist/constants/gate-constants.d.ts +1 -1
- package/dist/constants/gate-constants.js +1 -1
- package/dist/constants/linter-constants.d.ts +1 -1
- package/dist/constants/linter-constants.js +1 -1
- package/dist/constants/tokenizer-constants.d.ts +1 -1
- package/dist/constants/tokenizer-constants.js +1 -1
- package/dist/context/location-resolver.js +2 -1
- package/dist/context-validation-integration.d.ts +1 -0
- package/dist/core/scope-checker.d.ts +3 -3
- package/dist/core/scope-checker.js +3 -3
- package/dist/core/tool-runner.d.ts +5 -5
- package/dist/core/tool-runner.js +5 -5
- package/dist/core/tool.constants.d.ts +1 -1
- package/dist/core/tool.constants.js +1 -1
- package/dist/core/tool.schemas.d.ts +2 -2
- package/dist/core/tool.schemas.js +1 -1
- package/dist/core/worktree-guard.d.ts +1 -1
- package/dist/core/worktree-guard.js +1 -1
- package/dist/coverage-gate.d.ts +12 -3
- package/dist/coverage-gate.js +15 -8
- package/dist/date-utils.d.ts +4 -4
- package/dist/date-utils.js +4 -4
- package/dist/dependency-graph.d.ts +6 -0
- package/dist/dependency-graph.js +43 -2
- package/dist/dependency-guard.d.ts +2 -2
- package/dist/dependency-guard.js +3 -3
- package/dist/dependency-validator.d.ts +4 -4
- package/dist/dependency-validator.js +4 -7
- package/dist/domain/orchestration.constants.d.ts +31 -10
- package/dist/domain/orchestration.constants.js +45 -16
- package/dist/domain/orchestration.schemas.d.ts +54 -28
- package/dist/domain/orchestration.schemas.js +2 -2
- package/dist/domain/orchestration.types.d.ts +2 -2
- package/dist/domain/orchestration.types.js +2 -2
- package/dist/error-handler.d.ts +10 -10
- package/dist/error-handler.js +10 -10
- package/dist/file-classifiers.d.ts +6 -6
- package/dist/file-classifiers.js +6 -6
- package/dist/gates-config.d.ts +74 -0
- package/dist/gates-config.js +209 -2
- package/dist/git-adapter.d.ts +11 -11
- package/dist/git-adapter.js +11 -11
- package/dist/git-context-extractor.d.ts +112 -0
- package/dist/git-context-extractor.js +559 -0
- package/dist/hardcoded-strings.d.ts +1 -1
- package/dist/hardcoded-strings.js +1 -1
- package/dist/incremental-lint.d.ts +1 -1
- package/dist/incremental-lint.js +2 -2
- package/dist/incremental-test.d.ts +1 -1
- package/dist/incremental-test.js +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +25 -0
- package/dist/invariants/check-automated-tests.d.ts +2 -2
- package/dist/invariants/check-automated-tests.js +3 -3
- package/dist/lane-checker.d.ts +28 -7
- package/dist/lane-checker.js +316 -159
- package/dist/lane-suggest-prompt.d.ts +108 -0
- package/dist/lane-suggest-prompt.js +359 -0
- package/dist/lane-validator.d.ts +3 -3
- package/dist/lane-validator.js +3 -3
- package/dist/logs-lib.d.ts +1 -1
- package/dist/logs-lib.js +1 -1
- package/dist/lumenflow-config-schema.d.ts +162 -0
- package/dist/lumenflow-config-schema.js +180 -0
- package/dist/manual-test-validator.d.ts +2 -2
- package/dist/manual-test-validator.js +3 -3
- package/dist/merge-lock.d.ts +8 -1
- package/dist/merge-lock.js +16 -7
- package/dist/micro-worktree.d.ts +81 -13
- package/dist/micro-worktree.js +98 -17
- package/dist/migration-deployer.d.ts +1 -1
- package/dist/migration-deployer.js +1 -1
- package/dist/orchestration-advisory-loader.d.ts +2 -2
- package/dist/orchestration-advisory-loader.js +10 -6
- package/dist/orchestration-advisory.d.ts +3 -3
- package/dist/orchestration-advisory.js +4 -4
- package/dist/orchestration-di.d.ts +4 -4
- package/dist/orchestration-di.js +4 -4
- package/dist/orchestration-rules.d.ts +4 -4
- package/dist/orchestration-rules.js +18 -10
- package/dist/orphan-detector.d.ts +3 -3
- package/dist/orphan-detector.js +3 -3
- package/dist/patrol-loop.d.ts +170 -0
- package/dist/patrol-loop.js +186 -0
- package/dist/process-detector.d.ts +5 -5
- package/dist/process-detector.js +5 -5
- package/dist/rebase-artifact-cleanup.d.ts +3 -3
- package/dist/rebase-artifact-cleanup.js +3 -3
- package/dist/resolve-policy.d.ts +195 -0
- package/dist/resolve-policy.js +203 -0
- package/dist/risk-detector.d.ts +2 -2
- package/dist/risk-detector.js +2 -2
- package/dist/rollback-utils.d.ts +1 -1
- package/dist/rollback-utils.js +1 -1
- package/dist/section-headings.d.ts +1 -1
- package/dist/section-headings.js +1 -1
- package/dist/spawn-escalation.d.ts +4 -4
- package/dist/spawn-escalation.js +3 -3
- package/dist/spawn-monitor.d.ts +4 -4
- package/dist/spawn-monitor.js +4 -4
- package/dist/spawn-recovery.d.ts +3 -3
- package/dist/spawn-recovery.js +3 -3
- package/dist/spawn-registry-schema.d.ts +2 -2
- package/dist/spawn-registry-schema.js +2 -2
- package/dist/spawn-registry-store.d.ts +2 -2
- package/dist/spawn-registry-store.js +2 -2
- package/dist/spawn-strategy.d.ts +17 -11
- package/dist/spawn-strategy.js +47 -44
- package/dist/spawn-tree.d.ts +3 -3
- package/dist/spawn-tree.js +3 -3
- package/dist/state-cleanup-core.d.ts +205 -0
- package/dist/state-cleanup-core.js +240 -0
- package/dist/state-doctor-core.d.ts +168 -0
- package/dist/state-doctor-core.js +251 -0
- package/dist/stream-error-handler.d.ts +67 -0
- package/dist/stream-error-handler.js +94 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.js +1 -1
- package/dist/template-loader.d.ts +162 -0
- package/dist/template-loader.js +372 -0
- package/dist/test-baseline.d.ts +176 -0
- package/dist/test-baseline.js +282 -0
- package/dist/usecases/get-suggestions.usecase.d.ts +1 -1
- package/dist/validation/command-registry.js +37 -0
- package/dist/validators/backlog-sync.js +4 -2
- package/dist/worktree-scanner.d.ts +1 -1
- package/dist/worktree-scanner.js +1 -1
- package/dist/worktree-symlink.d.ts +3 -3
- package/dist/worktree-symlink.js +3 -3
- package/dist/wu-backlog-updater.d.ts +1 -1
- package/dist/wu-backlog-updater.js +1 -1
- package/dist/wu-claim-helpers.d.ts +1 -1
- package/dist/wu-claim-helpers.js +1 -1
- package/dist/wu-claim-resume.d.ts +1 -1
- package/dist/wu-claim-resume.js +1 -1
- package/dist/wu-consistency-checker.d.ts +1 -1
- package/dist/wu-consistency-checker.js +17 -11
- package/dist/wu-constants.d.ts +73 -21
- package/dist/wu-constants.js +65 -22
- package/dist/wu-done-branch-only.d.ts +1 -1
- package/dist/wu-done-branch-only.js +1 -1
- package/dist/wu-done-docs-generate.d.ts +1 -1
- package/dist/wu-done-docs-generate.js +1 -1
- package/dist/wu-done-messages.d.ts +2 -2
- package/dist/wu-done-messages.js +2 -2
- package/dist/wu-done-metadata.d.ts +3 -3
- package/dist/wu-done-metadata.js +3 -3
- package/dist/wu-done-pr.d.ts +1 -1
- package/dist/wu-done-pr.js +4 -2
- package/dist/wu-done-preflight.d.ts +8 -0
- package/dist/wu-done-preflight.js +18 -2
- package/dist/wu-done-ui.d.ts +3 -3
- package/dist/wu-done-ui.js +3 -3
- package/dist/wu-done-validation.d.ts +30 -0
- package/dist/wu-done-validation.js +106 -1
- package/dist/wu-done-worktree.d.ts +1 -1
- package/dist/wu-done-worktree.js +11 -1
- package/dist/wu-events-cleanup.d.ts +148 -0
- package/dist/wu-events-cleanup.js +401 -0
- package/dist/wu-helpers.d.ts +2 -2
- package/dist/wu-helpers.js +2 -2
- package/dist/wu-id-generator.d.ts +58 -0
- package/dist/wu-id-generator.js +103 -0
- package/dist/wu-lint.js +1 -1
- package/dist/wu-preflight-validators.d.ts +13 -1
- package/dist/wu-preflight-validators.js +56 -1
- package/dist/wu-recovery.d.ts +2 -2
- package/dist/wu-recovery.js +4 -4
- package/dist/wu-repair-core.d.ts +5 -5
- package/dist/wu-repair-core.js +6 -6
- package/dist/wu-schema-normalization.d.ts +1 -1
- package/dist/wu-schema-normalization.js +1 -1
- package/dist/wu-schema.d.ts +7 -7
- package/dist/wu-schema.js +8 -8
- package/dist/wu-spawn-context.d.ts +87 -0
- package/dist/wu-spawn-context.js +175 -0
- package/dist/wu-spawn-helpers.d.ts +1 -1
- package/dist/wu-spawn-helpers.js +1 -1
- package/dist/wu-spawn.d.ts +177 -4
- package/dist/wu-spawn.js +694 -72
- package/dist/wu-state-schema.d.ts +1 -1
- package/dist/wu-state-schema.js +1 -1
- package/dist/wu-state-store.d.ts +3 -3
- package/dist/wu-state-store.js +3 -3
- package/dist/wu-status-transition.d.ts +1 -1
- package/dist/wu-status-transition.js +1 -1
- package/dist/wu-status-updater.d.ts +3 -3
- package/dist/wu-status-updater.js +3 -3
- package/dist/wu-validation-constants.d.ts +2 -2
- package/dist/wu-validation-constants.js +2 -2
- package/dist/wu-validation.d.ts +3 -3
- package/dist/wu-validation.js +3 -3
- package/dist/wu-yaml-fixer.d.ts +2 -2
- package/dist/wu-yaml-fixer.js +3 -3
- package/dist/wu-yaml.d.ts +23 -0
- package/dist/wu-yaml.js +76 -2
- package/package.json +5 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Cleanup Core (WU-1208)
|
|
3
|
+
*
|
|
4
|
+
* Unified orchestration of all state cleanup operations:
|
|
5
|
+
* - Signal cleanup (TTL-based, from @lumenflow/memory)
|
|
6
|
+
* - Memory cleanup (lifecycle-based, from @lumenflow/memory)
|
|
7
|
+
* - Event archival (age-based, from @lumenflow/core)
|
|
8
|
+
*
|
|
9
|
+
* Cleanup order: signals -> memory -> events (dependency order)
|
|
10
|
+
*
|
|
11
|
+
* Design principles:
|
|
12
|
+
* - Non-fatal errors: failures in one cleanup type don't block others
|
|
13
|
+
* - Consistent summary: aggregated counts for all cleanup types
|
|
14
|
+
* - Configurable: supports --dry-run and type-specific flags
|
|
15
|
+
*
|
|
16
|
+
* @see {@link packages/@lumenflow/cli/src/state-cleanup.ts} - CLI wrapper
|
|
17
|
+
* @see {@link packages/@lumenflow/core/src/__tests__/state-cleanup-core.test.ts} - Tests
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* All cleanup types in dependency order
|
|
21
|
+
*/
|
|
22
|
+
const ALL_CLEANUP_TYPES = ['signals', 'memory', 'events'];
|
|
23
|
+
/**
|
|
24
|
+
* Determine which cleanup types to execute based on options
|
|
25
|
+
*
|
|
26
|
+
* @param options - State cleanup options
|
|
27
|
+
* @returns Array of cleanup types to execute
|
|
28
|
+
*/
|
|
29
|
+
function getTypesToExecute(options) {
|
|
30
|
+
if (options.signalsOnly) {
|
|
31
|
+
return ['signals'];
|
|
32
|
+
}
|
|
33
|
+
if (options.memoryOnly) {
|
|
34
|
+
return ['memory'];
|
|
35
|
+
}
|
|
36
|
+
if (options.eventsOnly) {
|
|
37
|
+
return ['events'];
|
|
38
|
+
}
|
|
39
|
+
return ALL_CLEANUP_TYPES;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Execute signal cleanup and capture results or errors
|
|
43
|
+
*
|
|
44
|
+
* @param baseDir - Project base directory
|
|
45
|
+
* @param options - Cleanup options
|
|
46
|
+
* @returns Signal cleanup summary or undefined on error
|
|
47
|
+
*/
|
|
48
|
+
async function executeSignalCleanup(baseDir, options) {
|
|
49
|
+
if (!options.cleanupSignals) {
|
|
50
|
+
return { error: { type: 'signals', message: 'No cleanupSignals function provided' } };
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const result = await options.cleanupSignals(baseDir, { dryRun: options.dryRun });
|
|
54
|
+
return {
|
|
55
|
+
summary: {
|
|
56
|
+
removedCount: result.removedIds.length,
|
|
57
|
+
retainedCount: result.retainedIds.length,
|
|
58
|
+
bytesFreed: result.bytesFreed,
|
|
59
|
+
breakdown: result.breakdown,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const error = err;
|
|
65
|
+
return {
|
|
66
|
+
error: {
|
|
67
|
+
type: 'signals',
|
|
68
|
+
message: error.message,
|
|
69
|
+
error,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Execute memory cleanup and capture results or errors
|
|
76
|
+
*
|
|
77
|
+
* @param baseDir - Project base directory
|
|
78
|
+
* @param options - Cleanup options
|
|
79
|
+
* @returns Memory cleanup summary or undefined on error
|
|
80
|
+
*/
|
|
81
|
+
async function executeMemoryCleanup(baseDir, options) {
|
|
82
|
+
if (!options.cleanupMemory) {
|
|
83
|
+
return { error: { type: 'memory', message: 'No cleanupMemory function provided' } };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const result = await options.cleanupMemory(baseDir, { dryRun: options.dryRun });
|
|
87
|
+
return {
|
|
88
|
+
summary: {
|
|
89
|
+
removedCount: result.removedIds.length,
|
|
90
|
+
retainedCount: result.retainedIds.length,
|
|
91
|
+
bytesFreed: result.bytesFreed,
|
|
92
|
+
breakdown: result.breakdown,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const error = err;
|
|
98
|
+
return {
|
|
99
|
+
error: {
|
|
100
|
+
type: 'memory',
|
|
101
|
+
message: error.message,
|
|
102
|
+
error,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Execute event archival and capture results or errors
|
|
109
|
+
*
|
|
110
|
+
* @param baseDir - Project base directory
|
|
111
|
+
* @param options - Cleanup options
|
|
112
|
+
* @returns Event archival summary or undefined on error
|
|
113
|
+
*/
|
|
114
|
+
async function executeEventArchival(baseDir, options) {
|
|
115
|
+
if (!options.archiveEvents) {
|
|
116
|
+
return { error: { type: 'events', message: 'No archiveEvents function provided' } };
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const result = await options.archiveEvents(baseDir, { dryRun: options.dryRun });
|
|
120
|
+
return {
|
|
121
|
+
summary: {
|
|
122
|
+
archivedWuCount: result.archivedWuIds.length,
|
|
123
|
+
retainedWuCount: result.retainedWuIds.length,
|
|
124
|
+
archivedEventCount: result.archivedEventCount,
|
|
125
|
+
retainedEventCount: result.retainedEventCount,
|
|
126
|
+
bytesArchived: result.bytesArchived,
|
|
127
|
+
breakdown: result.breakdown,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
const error = err;
|
|
133
|
+
return {
|
|
134
|
+
error: {
|
|
135
|
+
type: 'events',
|
|
136
|
+
message: error.message,
|
|
137
|
+
error,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Calculate total bytes freed from all cleanup summaries
|
|
144
|
+
*
|
|
145
|
+
* @param signals - Signal cleanup summary
|
|
146
|
+
* @param memory - Memory cleanup summary
|
|
147
|
+
* @param events - Event archival summary
|
|
148
|
+
* @returns Total bytes freed
|
|
149
|
+
*/
|
|
150
|
+
function calculateTotalBytesFreed(signals, memory, events) {
|
|
151
|
+
let total = 0;
|
|
152
|
+
if (signals) {
|
|
153
|
+
total += signals.bytesFreed;
|
|
154
|
+
}
|
|
155
|
+
if (memory) {
|
|
156
|
+
total += memory.bytesFreed;
|
|
157
|
+
}
|
|
158
|
+
if (events) {
|
|
159
|
+
total += events.bytesArchived;
|
|
160
|
+
}
|
|
161
|
+
return total;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Orchestrate all state cleanup operations in dependency order.
|
|
165
|
+
*
|
|
166
|
+
* Executes cleanups in order: signals -> memory -> events
|
|
167
|
+
*
|
|
168
|
+
* Non-fatal: failures in one cleanup type don't block others.
|
|
169
|
+
* All errors are collected and reported in the result.
|
|
170
|
+
*
|
|
171
|
+
* @param baseDir - Project base directory
|
|
172
|
+
* @param options - State cleanup options
|
|
173
|
+
* @returns Unified cleanup result with summaries and errors
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // Full cleanup with dry-run
|
|
177
|
+
* const result = await cleanupState(baseDir, { dryRun: true });
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Signals only
|
|
181
|
+
* const result = await cleanupState(baseDir, { signalsOnly: true });
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* // With injected cleanup functions (for testing or custom implementations)
|
|
185
|
+
* const result = await cleanupState(baseDir, {
|
|
186
|
+
* cleanupSignals: myCustomSignalCleanup,
|
|
187
|
+
* cleanupMemory: myCustomMemoryCleanup,
|
|
188
|
+
* archiveEvents: myCustomEventArchival,
|
|
189
|
+
* });
|
|
190
|
+
*/
|
|
191
|
+
export async function cleanupState(baseDir, options = {}) {
|
|
192
|
+
const typesToExecute = getTypesToExecute(options);
|
|
193
|
+
const typesSkipped = ALL_CLEANUP_TYPES.filter((t) => !typesToExecute.includes(t));
|
|
194
|
+
const errors = [];
|
|
195
|
+
let signalsSummary;
|
|
196
|
+
let memorySummary;
|
|
197
|
+
let eventsSummary;
|
|
198
|
+
// Execute cleanups in dependency order: signals -> memory -> events
|
|
199
|
+
if (typesToExecute.includes('signals')) {
|
|
200
|
+
const result = await executeSignalCleanup(baseDir, options);
|
|
201
|
+
if (result.error) {
|
|
202
|
+
errors.push(result.error);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
signalsSummary = result.summary;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (typesToExecute.includes('memory')) {
|
|
209
|
+
const result = await executeMemoryCleanup(baseDir, options);
|
|
210
|
+
if (result.error) {
|
|
211
|
+
errors.push(result.error);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
memorySummary = result.summary;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (typesToExecute.includes('events')) {
|
|
218
|
+
const result = await executeEventArchival(baseDir, options);
|
|
219
|
+
if (result.error) {
|
|
220
|
+
errors.push(result.error);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
eventsSummary = result.summary;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const totalBytesFreed = calculateTotalBytesFreed(signalsSummary, memorySummary, eventsSummary);
|
|
227
|
+
return {
|
|
228
|
+
success: errors.length === 0,
|
|
229
|
+
dryRun: options.dryRun,
|
|
230
|
+
signals: signalsSummary,
|
|
231
|
+
memory: memorySummary,
|
|
232
|
+
events: eventsSummary,
|
|
233
|
+
errors,
|
|
234
|
+
summary: {
|
|
235
|
+
totalBytesFreed,
|
|
236
|
+
typesExecuted: typesToExecute,
|
|
237
|
+
typesSkipped,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Doctor Core (WU-1209)
|
|
3
|
+
*
|
|
4
|
+
* Integrity checker for LumenFlow state that detects:
|
|
5
|
+
* - Orphaned WUs (done status but no stamp)
|
|
6
|
+
* - Dangling signals (reference non-existent WUs)
|
|
7
|
+
* - Broken memory relationships (events for missing WU specs)
|
|
8
|
+
*
|
|
9
|
+
* Inspired by Beads bd doctor command.
|
|
10
|
+
*
|
|
11
|
+
* Design principles:
|
|
12
|
+
* - Non-destructive by default (read-only diagnosis)
|
|
13
|
+
* - --fix flag for safe auto-repair of resolvable issues
|
|
14
|
+
* - Dependency injection for testability
|
|
15
|
+
* - Human-readable output with actionable suggestions
|
|
16
|
+
*
|
|
17
|
+
* @see {@link packages/@lumenflow/cli/src/state-doctor.ts} - CLI wrapper
|
|
18
|
+
* @see {@link packages/@lumenflow/core/src/__tests__/state-doctor-core.test.ts} - Tests
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Issue type constants
|
|
22
|
+
*/
|
|
23
|
+
export declare const ISSUE_TYPES: {
|
|
24
|
+
/** WU has done status but no stamp file */
|
|
25
|
+
readonly ORPHANED_WU: "orphaned_wu";
|
|
26
|
+
/** Signal references a WU that doesn't exist */
|
|
27
|
+
readonly DANGLING_SIGNAL: "dangling_signal";
|
|
28
|
+
/** Event references a WU that doesn't exist */
|
|
29
|
+
readonly BROKEN_EVENT: "broken_event";
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Issue severity levels
|
|
33
|
+
*/
|
|
34
|
+
export declare const ISSUE_SEVERITY: {
|
|
35
|
+
/** Critical issues that may cause data loss */
|
|
36
|
+
readonly ERROR: "error";
|
|
37
|
+
/** Issues that should be fixed but aren't blocking */
|
|
38
|
+
readonly WARNING: "warning";
|
|
39
|
+
/** Informational findings */
|
|
40
|
+
readonly INFO: "info";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Issue type (union)
|
|
44
|
+
*/
|
|
45
|
+
export type IssueType = (typeof ISSUE_TYPES)[keyof typeof ISSUE_TYPES];
|
|
46
|
+
/**
|
|
47
|
+
* Issue severity (union)
|
|
48
|
+
*/
|
|
49
|
+
export type IssueSeverity = (typeof ISSUE_SEVERITY)[keyof typeof ISSUE_SEVERITY];
|
|
50
|
+
/**
|
|
51
|
+
* Mock WU YAML content
|
|
52
|
+
*/
|
|
53
|
+
export interface MockWU {
|
|
54
|
+
id: string;
|
|
55
|
+
status: string;
|
|
56
|
+
lane?: string;
|
|
57
|
+
title?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Mock signal content
|
|
61
|
+
*/
|
|
62
|
+
export interface MockSignal {
|
|
63
|
+
id: string;
|
|
64
|
+
wuId?: string;
|
|
65
|
+
timestamp?: string;
|
|
66
|
+
message?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Mock event content
|
|
70
|
+
*/
|
|
71
|
+
export interface MockEvent {
|
|
72
|
+
wuId: string;
|
|
73
|
+
type: string;
|
|
74
|
+
timestamp?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Dependencies for state doctor (injectable for testing)
|
|
78
|
+
*/
|
|
79
|
+
export interface StateDoctorDeps {
|
|
80
|
+
/** List all WU YAML files */
|
|
81
|
+
listWUs: () => Promise<MockWU[]>;
|
|
82
|
+
/** List all stamp file IDs */
|
|
83
|
+
listStamps: () => Promise<string[]>;
|
|
84
|
+
/** List all signals */
|
|
85
|
+
listSignals: () => Promise<MockSignal[]>;
|
|
86
|
+
/** List all events */
|
|
87
|
+
listEvents: () => Promise<MockEvent[]>;
|
|
88
|
+
/** Remove a signal by ID (for --fix) */
|
|
89
|
+
removeSignal?: (id: string) => Promise<void>;
|
|
90
|
+
/** Remove events for a WU (for --fix) */
|
|
91
|
+
removeEvent?: (wuId: string) => Promise<void>;
|
|
92
|
+
/** Create a stamp for a WU (for --fix) */
|
|
93
|
+
createStamp?: (wuId: string, title: string) => Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A detected issue in the state
|
|
97
|
+
*/
|
|
98
|
+
export interface DiagnosisIssue {
|
|
99
|
+
/** Type of issue */
|
|
100
|
+
type: IssueType;
|
|
101
|
+
/** Severity level */
|
|
102
|
+
severity: IssueSeverity;
|
|
103
|
+
/** WU ID involved (if applicable) */
|
|
104
|
+
wuId?: string;
|
|
105
|
+
/** Signal ID involved (if applicable) */
|
|
106
|
+
signalId?: string;
|
|
107
|
+
/** Human-readable description */
|
|
108
|
+
description: string;
|
|
109
|
+
/** Suggested fix */
|
|
110
|
+
suggestion: string;
|
|
111
|
+
/** Whether this issue can be auto-fixed */
|
|
112
|
+
canAutoFix: boolean;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A fix error that occurred during auto-repair
|
|
116
|
+
*/
|
|
117
|
+
export interface FixError {
|
|
118
|
+
/** Type of issue that failed to fix */
|
|
119
|
+
type: IssueType;
|
|
120
|
+
/** WU ID involved (if applicable) */
|
|
121
|
+
wuId?: string;
|
|
122
|
+
/** Signal ID involved (if applicable) */
|
|
123
|
+
signalId?: string;
|
|
124
|
+
/** Error message */
|
|
125
|
+
error: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Summary statistics
|
|
129
|
+
*/
|
|
130
|
+
export interface DiagnosisSummary {
|
|
131
|
+
/** Number of orphaned WUs */
|
|
132
|
+
orphanedWUs: number;
|
|
133
|
+
/** Number of dangling signals */
|
|
134
|
+
danglingSignals: number;
|
|
135
|
+
/** Number of broken events */
|
|
136
|
+
brokenEvents: number;
|
|
137
|
+
/** Total number of issues */
|
|
138
|
+
totalIssues: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Options for diagnosis
|
|
142
|
+
*/
|
|
143
|
+
export interface DiagnosisOptions {
|
|
144
|
+
/** Whether to attempt auto-fixes */
|
|
145
|
+
fix?: boolean;
|
|
146
|
+
/** Dry-run mode (report what would be fixed) */
|
|
147
|
+
dryRun?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Result of state diagnosis
|
|
151
|
+
*/
|
|
152
|
+
export interface StateDiagnosis {
|
|
153
|
+
/** Whether the state is healthy */
|
|
154
|
+
healthy: boolean;
|
|
155
|
+
/** List of detected issues */
|
|
156
|
+
issues: DiagnosisIssue[];
|
|
157
|
+
/** Summary statistics */
|
|
158
|
+
summary: DiagnosisSummary;
|
|
159
|
+
/** Issues that were fixed */
|
|
160
|
+
fixed: DiagnosisIssue[];
|
|
161
|
+
/** Errors that occurred during fixing */
|
|
162
|
+
fixErrors: FixError[];
|
|
163
|
+
/** Whether this was a dry-run */
|
|
164
|
+
dryRun?: boolean;
|
|
165
|
+
/** Issues that would be fixed (in dry-run mode) */
|
|
166
|
+
wouldFix?: DiagnosisIssue[];
|
|
167
|
+
}
|
|
168
|
+
export declare function diagnoseState(_baseDir: string, deps: StateDoctorDeps, options?: DiagnosisOptions): Promise<StateDiagnosis>;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Doctor Core (WU-1209)
|
|
3
|
+
*
|
|
4
|
+
* Integrity checker for LumenFlow state that detects:
|
|
5
|
+
* - Orphaned WUs (done status but no stamp)
|
|
6
|
+
* - Dangling signals (reference non-existent WUs)
|
|
7
|
+
* - Broken memory relationships (events for missing WU specs)
|
|
8
|
+
*
|
|
9
|
+
* Inspired by Beads bd doctor command.
|
|
10
|
+
*
|
|
11
|
+
* Design principles:
|
|
12
|
+
* - Non-destructive by default (read-only diagnosis)
|
|
13
|
+
* - --fix flag for safe auto-repair of resolvable issues
|
|
14
|
+
* - Dependency injection for testability
|
|
15
|
+
* - Human-readable output with actionable suggestions
|
|
16
|
+
*
|
|
17
|
+
* @see {@link packages/@lumenflow/cli/src/state-doctor.ts} - CLI wrapper
|
|
18
|
+
* @see {@link packages/@lumenflow/core/src/__tests__/state-doctor-core.test.ts} - Tests
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Issue type constants
|
|
22
|
+
*/
|
|
23
|
+
export const ISSUE_TYPES = {
|
|
24
|
+
/** WU has done status but no stamp file */
|
|
25
|
+
ORPHANED_WU: 'orphaned_wu',
|
|
26
|
+
/** Signal references a WU that doesn't exist */
|
|
27
|
+
DANGLING_SIGNAL: 'dangling_signal',
|
|
28
|
+
/** Event references a WU that doesn't exist */
|
|
29
|
+
BROKEN_EVENT: 'broken_event',
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Issue severity levels
|
|
33
|
+
*/
|
|
34
|
+
export const ISSUE_SEVERITY = {
|
|
35
|
+
/** Critical issues that may cause data loss */
|
|
36
|
+
ERROR: 'error',
|
|
37
|
+
/** Issues that should be fixed but aren't blocking */
|
|
38
|
+
WARNING: 'warning',
|
|
39
|
+
/** Informational findings */
|
|
40
|
+
INFO: 'info',
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Detect orphaned WUs (done status but no stamp)
|
|
44
|
+
*/
|
|
45
|
+
function detectOrphanedWUs(wus, stamps) {
|
|
46
|
+
const issues = [];
|
|
47
|
+
for (const wu of wus) {
|
|
48
|
+
if (wu.status === 'done' && !stamps.has(wu.id)) {
|
|
49
|
+
issues.push({
|
|
50
|
+
type: ISSUE_TYPES.ORPHANED_WU,
|
|
51
|
+
severity: ISSUE_SEVERITY.WARNING,
|
|
52
|
+
wuId: wu.id,
|
|
53
|
+
description: `WU ${wu.id} has status 'done' but no stamp file exists`,
|
|
54
|
+
suggestion: `Create stamp file for ${wu.id} using: pnpm state:doctor --fix`,
|
|
55
|
+
canAutoFix: true,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return issues;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detect dangling signals (reference non-existent WUs)
|
|
63
|
+
*/
|
|
64
|
+
function detectDanglingSignals(signals, wuIds) {
|
|
65
|
+
const issues = [];
|
|
66
|
+
for (const signal of signals) {
|
|
67
|
+
// Skip signals without WU references
|
|
68
|
+
if (!signal.wuId) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!wuIds.has(signal.wuId)) {
|
|
72
|
+
issues.push({
|
|
73
|
+
type: ISSUE_TYPES.DANGLING_SIGNAL,
|
|
74
|
+
severity: ISSUE_SEVERITY.WARNING,
|
|
75
|
+
wuId: signal.wuId,
|
|
76
|
+
signalId: signal.id,
|
|
77
|
+
description: `Signal ${signal.id} references non-existent WU ${signal.wuId}`,
|
|
78
|
+
suggestion: `Remove dangling signal using: pnpm state:doctor --fix`,
|
|
79
|
+
canAutoFix: true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return issues;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Detect broken events (reference non-existent WUs)
|
|
87
|
+
*/
|
|
88
|
+
function detectBrokenEvents(events, wuIds) {
|
|
89
|
+
const issues = [];
|
|
90
|
+
const seenWuIds = new Set();
|
|
91
|
+
for (const event of events) {
|
|
92
|
+
// Only report once per WU
|
|
93
|
+
if (seenWuIds.has(event.wuId)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!wuIds.has(event.wuId)) {
|
|
97
|
+
seenWuIds.add(event.wuId);
|
|
98
|
+
issues.push({
|
|
99
|
+
type: ISSUE_TYPES.BROKEN_EVENT,
|
|
100
|
+
severity: ISSUE_SEVERITY.WARNING,
|
|
101
|
+
wuId: event.wuId,
|
|
102
|
+
description: `Events exist for non-existent WU ${event.wuId}`,
|
|
103
|
+
suggestion: `Archive or remove events for missing WU using: pnpm state:doctor --fix`,
|
|
104
|
+
canAutoFix: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return issues;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Calculate summary statistics from issues
|
|
112
|
+
*/
|
|
113
|
+
function calculateSummary(issues) {
|
|
114
|
+
let orphanedWUs = 0;
|
|
115
|
+
let danglingSignals = 0;
|
|
116
|
+
let brokenEvents = 0;
|
|
117
|
+
for (const issue of issues) {
|
|
118
|
+
switch (issue.type) {
|
|
119
|
+
case ISSUE_TYPES.ORPHANED_WU:
|
|
120
|
+
orphanedWUs++;
|
|
121
|
+
break;
|
|
122
|
+
case ISSUE_TYPES.DANGLING_SIGNAL:
|
|
123
|
+
danglingSignals++;
|
|
124
|
+
break;
|
|
125
|
+
case ISSUE_TYPES.BROKEN_EVENT:
|
|
126
|
+
brokenEvents++;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
orphanedWUs,
|
|
132
|
+
danglingSignals,
|
|
133
|
+
brokenEvents,
|
|
134
|
+
totalIssues: issues.length,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Attempt to fix an issue
|
|
139
|
+
*/
|
|
140
|
+
async function fixIssue(issue, deps, wus) {
|
|
141
|
+
try {
|
|
142
|
+
switch (issue.type) {
|
|
143
|
+
case ISSUE_TYPES.DANGLING_SIGNAL:
|
|
144
|
+
if (deps.removeSignal && issue.signalId) {
|
|
145
|
+
await deps.removeSignal(issue.signalId);
|
|
146
|
+
return { fixed: true };
|
|
147
|
+
}
|
|
148
|
+
return { fixed: false, error: 'No removeSignal function provided' };
|
|
149
|
+
case ISSUE_TYPES.BROKEN_EVENT:
|
|
150
|
+
if (deps.removeEvent && issue.wuId) {
|
|
151
|
+
await deps.removeEvent(issue.wuId);
|
|
152
|
+
return { fixed: true };
|
|
153
|
+
}
|
|
154
|
+
return { fixed: false, error: 'No removeEvent function provided' };
|
|
155
|
+
case ISSUE_TYPES.ORPHANED_WU:
|
|
156
|
+
if (deps.createStamp && issue.wuId) {
|
|
157
|
+
const wu = wus.find((w) => w.id === issue.wuId);
|
|
158
|
+
await deps.createStamp(issue.wuId, wu?.title || `WU ${issue.wuId}`);
|
|
159
|
+
return { fixed: true };
|
|
160
|
+
}
|
|
161
|
+
return { fixed: false, error: 'No createStamp function provided' };
|
|
162
|
+
default:
|
|
163
|
+
return { fixed: false, error: `Unknown issue type: ${issue.type}` };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
168
|
+
return { fixed: false, error };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Diagnose state integrity issues.
|
|
173
|
+
*
|
|
174
|
+
* Detects:
|
|
175
|
+
* - Orphaned WUs (done status but no stamp)
|
|
176
|
+
* - Dangling signals (reference non-existent WUs)
|
|
177
|
+
* - Broken events (events for missing WU specs)
|
|
178
|
+
*
|
|
179
|
+
* @param baseDir - Project base directory
|
|
180
|
+
* @param deps - Dependency functions for reading/writing state
|
|
181
|
+
* @param options - Diagnosis options
|
|
182
|
+
* @returns Diagnosis result with issues and optional fixes
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* // Diagnose without fixing
|
|
186
|
+
* const result = await diagnoseState(baseDir, deps);
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* // Diagnose and fix
|
|
190
|
+
* const result = await diagnoseState(baseDir, deps, { fix: true });
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* // Dry-run (report what would be fixed)
|
|
194
|
+
* const result = await diagnoseState(baseDir, deps, { fix: true, dryRun: true });
|
|
195
|
+
*/
|
|
196
|
+
/**
|
|
197
|
+
* Apply fixes to auto-fixable issues
|
|
198
|
+
*/
|
|
199
|
+
async function applyFixes(issues, deps, wus, result) {
|
|
200
|
+
const fixableIssues = issues.filter((i) => i.canAutoFix);
|
|
201
|
+
for (const issue of fixableIssues) {
|
|
202
|
+
const fixResult = await fixIssue(issue, deps, wus);
|
|
203
|
+
if (fixResult.fixed) {
|
|
204
|
+
result.fixed.push(issue);
|
|
205
|
+
}
|
|
206
|
+
else if (fixResult.error) {
|
|
207
|
+
result.fixErrors.push({
|
|
208
|
+
type: issue.type,
|
|
209
|
+
wuId: issue.wuId,
|
|
210
|
+
signalId: issue.signalId,
|
|
211
|
+
error: fixResult.error,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export async function diagnoseState(_baseDir, deps, options = {}) {
|
|
217
|
+
const { fix = false, dryRun = false } = options;
|
|
218
|
+
// Gather state data
|
|
219
|
+
const [wus, stamps, signals, events] = await Promise.all([
|
|
220
|
+
deps.listWUs(),
|
|
221
|
+
deps.listStamps(),
|
|
222
|
+
deps.listSignals(),
|
|
223
|
+
deps.listEvents(),
|
|
224
|
+
]);
|
|
225
|
+
// Build lookup sets
|
|
226
|
+
const wuIds = new Set(wus.map((wu) => wu.id));
|
|
227
|
+
const stampIds = new Set(stamps);
|
|
228
|
+
// Detect issues
|
|
229
|
+
const orphanedWUissues = detectOrphanedWUs(wus, stampIds);
|
|
230
|
+
const danglingSignalIssues = detectDanglingSignals(signals, wuIds);
|
|
231
|
+
const brokenEventIssues = detectBrokenEvents(events, wuIds);
|
|
232
|
+
const issues = [...orphanedWUissues, ...danglingSignalIssues, ...brokenEventIssues];
|
|
233
|
+
const summary = calculateSummary(issues);
|
|
234
|
+
// Initialize result
|
|
235
|
+
const result = {
|
|
236
|
+
healthy: issues.length === 0,
|
|
237
|
+
issues,
|
|
238
|
+
summary,
|
|
239
|
+
fixed: [],
|
|
240
|
+
fixErrors: [],
|
|
241
|
+
};
|
|
242
|
+
// Handle fixing
|
|
243
|
+
if (fix && dryRun) {
|
|
244
|
+
result.dryRun = true;
|
|
245
|
+
result.wouldFix = issues.filter((i) => i.canAutoFix);
|
|
246
|
+
}
|
|
247
|
+
else if (fix) {
|
|
248
|
+
await applyFixes(issues, deps, wus, result);
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|