@lumenflow/core 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/active-wu-detector.d.ts +1 -1
  2. package/dist/active-wu-detector.js +1 -1
  3. package/dist/arg-parser.js +51 -18
  4. package/dist/backlog-generator.d.ts +4 -4
  5. package/dist/backlog-generator.js +4 -4
  6. package/dist/backlog-sync-validator.js +1 -1
  7. package/dist/cleanup-lock.d.ts +9 -2
  8. package/dist/cleanup-lock.js +17 -7
  9. package/dist/code-path-validator.d.ts +3 -3
  10. package/dist/code-path-validator.js +3 -3
  11. package/dist/compliance-parser.d.ts +1 -1
  12. package/dist/compliance-parser.js +1 -1
  13. package/dist/constants/backlog-patterns.d.ts +1 -1
  14. package/dist/constants/backlog-patterns.js +1 -1
  15. package/dist/constants/dora-constants.d.ts +1 -1
  16. package/dist/constants/dora-constants.js +1 -1
  17. package/dist/constants/gate-constants.d.ts +1 -1
  18. package/dist/constants/gate-constants.js +1 -1
  19. package/dist/constants/linter-constants.d.ts +1 -1
  20. package/dist/constants/linter-constants.js +1 -1
  21. package/dist/constants/tokenizer-constants.d.ts +1 -1
  22. package/dist/constants/tokenizer-constants.js +1 -1
  23. package/dist/context/location-resolver.js +2 -1
  24. package/dist/context-validation-integration.d.ts +1 -0
  25. package/dist/core/scope-checker.d.ts +3 -3
  26. package/dist/core/scope-checker.js +3 -3
  27. package/dist/core/tool-runner.d.ts +5 -5
  28. package/dist/core/tool-runner.js +5 -5
  29. package/dist/core/tool.constants.d.ts +1 -1
  30. package/dist/core/tool.constants.js +1 -1
  31. package/dist/core/tool.schemas.d.ts +2 -2
  32. package/dist/core/tool.schemas.js +1 -1
  33. package/dist/core/worktree-guard.d.ts +1 -1
  34. package/dist/core/worktree-guard.js +1 -1
  35. package/dist/coverage-gate.d.ts +12 -3
  36. package/dist/coverage-gate.js +15 -8
  37. package/dist/date-utils.d.ts +4 -4
  38. package/dist/date-utils.js +4 -4
  39. package/dist/dependency-graph.d.ts +6 -0
  40. package/dist/dependency-graph.js +43 -2
  41. package/dist/dependency-guard.d.ts +2 -2
  42. package/dist/dependency-guard.js +3 -3
  43. package/dist/dependency-validator.d.ts +4 -4
  44. package/dist/dependency-validator.js +4 -7
  45. package/dist/domain/orchestration.constants.d.ts +31 -10
  46. package/dist/domain/orchestration.constants.js +45 -16
  47. package/dist/domain/orchestration.schemas.d.ts +54 -28
  48. package/dist/domain/orchestration.schemas.js +2 -2
  49. package/dist/domain/orchestration.types.d.ts +2 -2
  50. package/dist/domain/orchestration.types.js +2 -2
  51. package/dist/error-handler.d.ts +10 -10
  52. package/dist/error-handler.js +10 -10
  53. package/dist/file-classifiers.d.ts +6 -6
  54. package/dist/file-classifiers.js +6 -6
  55. package/dist/gates-config.d.ts +74 -0
  56. package/dist/gates-config.js +209 -2
  57. package/dist/git-adapter.d.ts +11 -11
  58. package/dist/git-adapter.js +11 -11
  59. package/dist/git-context-extractor.d.ts +112 -0
  60. package/dist/git-context-extractor.js +559 -0
  61. package/dist/hardcoded-strings.d.ts +1 -1
  62. package/dist/hardcoded-strings.js +1 -1
  63. package/dist/incremental-lint.d.ts +1 -1
  64. package/dist/incremental-lint.js +2 -2
  65. package/dist/incremental-test.d.ts +1 -1
  66. package/dist/incremental-test.js +1 -1
  67. package/dist/index.d.ts +13 -0
  68. package/dist/index.js +25 -0
  69. package/dist/invariants/check-automated-tests.d.ts +2 -2
  70. package/dist/invariants/check-automated-tests.js +3 -3
  71. package/dist/lane-checker.d.ts +28 -7
  72. package/dist/lane-checker.js +316 -159
  73. package/dist/lane-suggest-prompt.d.ts +108 -0
  74. package/dist/lane-suggest-prompt.js +359 -0
  75. package/dist/lane-validator.d.ts +3 -3
  76. package/dist/lane-validator.js +3 -3
  77. package/dist/logs-lib.d.ts +1 -1
  78. package/dist/logs-lib.js +1 -1
  79. package/dist/lumenflow-config-schema.d.ts +162 -0
  80. package/dist/lumenflow-config-schema.js +180 -0
  81. package/dist/manual-test-validator.d.ts +2 -2
  82. package/dist/manual-test-validator.js +3 -3
  83. package/dist/merge-lock.d.ts +8 -1
  84. package/dist/merge-lock.js +16 -7
  85. package/dist/micro-worktree.d.ts +81 -13
  86. package/dist/micro-worktree.js +98 -17
  87. package/dist/migration-deployer.d.ts +1 -1
  88. package/dist/migration-deployer.js +1 -1
  89. package/dist/orchestration-advisory-loader.d.ts +2 -2
  90. package/dist/orchestration-advisory-loader.js +10 -6
  91. package/dist/orchestration-advisory.d.ts +3 -3
  92. package/dist/orchestration-advisory.js +4 -4
  93. package/dist/orchestration-di.d.ts +4 -4
  94. package/dist/orchestration-di.js +4 -4
  95. package/dist/orchestration-rules.d.ts +4 -4
  96. package/dist/orchestration-rules.js +18 -10
  97. package/dist/orphan-detector.d.ts +3 -3
  98. package/dist/orphan-detector.js +3 -3
  99. package/dist/patrol-loop.d.ts +170 -0
  100. package/dist/patrol-loop.js +186 -0
  101. package/dist/process-detector.d.ts +5 -5
  102. package/dist/process-detector.js +5 -5
  103. package/dist/rebase-artifact-cleanup.d.ts +3 -3
  104. package/dist/rebase-artifact-cleanup.js +3 -3
  105. package/dist/resolve-policy.d.ts +195 -0
  106. package/dist/resolve-policy.js +203 -0
  107. package/dist/risk-detector.d.ts +2 -2
  108. package/dist/risk-detector.js +2 -2
  109. package/dist/rollback-utils.d.ts +1 -1
  110. package/dist/rollback-utils.js +1 -1
  111. package/dist/section-headings.d.ts +1 -1
  112. package/dist/section-headings.js +1 -1
  113. package/dist/spawn-escalation.d.ts +4 -4
  114. package/dist/spawn-escalation.js +3 -3
  115. package/dist/spawn-monitor.d.ts +4 -4
  116. package/dist/spawn-monitor.js +4 -4
  117. package/dist/spawn-recovery.d.ts +3 -3
  118. package/dist/spawn-recovery.js +3 -3
  119. package/dist/spawn-registry-schema.d.ts +2 -2
  120. package/dist/spawn-registry-schema.js +2 -2
  121. package/dist/spawn-registry-store.d.ts +2 -2
  122. package/dist/spawn-registry-store.js +2 -2
  123. package/dist/spawn-strategy.d.ts +17 -11
  124. package/dist/spawn-strategy.js +47 -44
  125. package/dist/spawn-tree.d.ts +3 -3
  126. package/dist/spawn-tree.js +3 -3
  127. package/dist/state-cleanup-core.d.ts +205 -0
  128. package/dist/state-cleanup-core.js +240 -0
  129. package/dist/state-doctor-core.d.ts +168 -0
  130. package/dist/state-doctor-core.js +251 -0
  131. package/dist/stream-error-handler.d.ts +67 -0
  132. package/dist/stream-error-handler.js +94 -0
  133. package/dist/system-map-validator.d.ts +18 -0
  134. package/dist/system-map-validator.js +50 -16
  135. package/dist/telemetry.d.ts +1 -1
  136. package/dist/telemetry.js +1 -1
  137. package/dist/template-loader.d.ts +162 -0
  138. package/dist/template-loader.js +372 -0
  139. package/dist/test-baseline.d.ts +176 -0
  140. package/dist/test-baseline.js +282 -0
  141. package/dist/usecases/get-suggestions.usecase.d.ts +1 -1
  142. package/dist/validation/command-registry.js +37 -0
  143. package/dist/validators/backlog-sync.d.ts +14 -0
  144. package/dist/validators/backlog-sync.js +62 -0
  145. package/dist/validators/supabase-docs-linter.d.ts +18 -0
  146. package/dist/validators/supabase-docs-linter.js +42 -0
  147. package/dist/validators/wu-tasks.d.ts +24 -0
  148. package/dist/validators/wu-tasks.js +90 -0
  149. package/dist/worktree-scanner.d.ts +1 -1
  150. package/dist/worktree-scanner.js +1 -1
  151. package/dist/worktree-symlink.d.ts +3 -3
  152. package/dist/worktree-symlink.js +3 -3
  153. package/dist/wu-backlog-updater.d.ts +1 -1
  154. package/dist/wu-backlog-updater.js +1 -1
  155. package/dist/wu-claim-helpers.d.ts +1 -1
  156. package/dist/wu-claim-helpers.js +1 -1
  157. package/dist/wu-claim-resume.d.ts +1 -1
  158. package/dist/wu-claim-resume.js +1 -1
  159. package/dist/wu-consistency-checker.d.ts +1 -1
  160. package/dist/wu-consistency-checker.js +17 -11
  161. package/dist/wu-constants.d.ts +73 -36
  162. package/dist/wu-constants.js +65 -92
  163. package/dist/wu-done-branch-only.d.ts +1 -1
  164. package/dist/wu-done-branch-only.js +1 -1
  165. package/dist/wu-done-docs-generate.d.ts +1 -1
  166. package/dist/wu-done-docs-generate.js +1 -1
  167. package/dist/wu-done-messages.d.ts +2 -2
  168. package/dist/wu-done-messages.js +2 -2
  169. package/dist/wu-done-metadata.d.ts +3 -3
  170. package/dist/wu-done-metadata.js +3 -3
  171. package/dist/wu-done-pr.d.ts +1 -1
  172. package/dist/wu-done-pr.js +4 -2
  173. package/dist/wu-done-preflight.d.ts +20 -10
  174. package/dist/wu-done-preflight.js +48 -47
  175. package/dist/wu-done-ui.d.ts +3 -3
  176. package/dist/wu-done-ui.js +3 -3
  177. package/dist/wu-done-validation.d.ts +30 -0
  178. package/dist/wu-done-validation.js +106 -1
  179. package/dist/wu-done-validators.d.ts +1 -1
  180. package/dist/wu-done-worktree.d.ts +1 -1
  181. package/dist/wu-done-worktree.js +11 -1
  182. package/dist/wu-events-cleanup.d.ts +148 -0
  183. package/dist/wu-events-cleanup.js +401 -0
  184. package/dist/wu-helpers.d.ts +2 -2
  185. package/dist/wu-helpers.js +2 -2
  186. package/dist/wu-id-generator.d.ts +58 -0
  187. package/dist/wu-id-generator.js +103 -0
  188. package/dist/wu-lint.js +1 -1
  189. package/dist/wu-preflight-validators.d.ts +13 -1
  190. package/dist/wu-preflight-validators.js +56 -1
  191. package/dist/wu-recovery.d.ts +2 -2
  192. package/dist/wu-recovery.js +4 -4
  193. package/dist/wu-repair-core.d.ts +5 -5
  194. package/dist/wu-repair-core.js +6 -6
  195. package/dist/wu-schema-normalization.d.ts +1 -1
  196. package/dist/wu-schema-normalization.js +1 -1
  197. package/dist/wu-schema.d.ts +7 -7
  198. package/dist/wu-schema.js +8 -8
  199. package/dist/wu-spawn-context.d.ts +87 -0
  200. package/dist/wu-spawn-context.js +175 -0
  201. package/dist/wu-spawn-helpers.d.ts +1 -1
  202. package/dist/wu-spawn-helpers.js +1 -1
  203. package/dist/wu-spawn.d.ts +177 -4
  204. package/dist/wu-spawn.js +694 -72
  205. package/dist/wu-state-schema.d.ts +1 -1
  206. package/dist/wu-state-schema.js +1 -1
  207. package/dist/wu-state-store.d.ts +3 -3
  208. package/dist/wu-state-store.js +3 -3
  209. package/dist/wu-status-transition.d.ts +1 -1
  210. package/dist/wu-status-transition.js +1 -1
  211. package/dist/wu-status-updater.d.ts +3 -3
  212. package/dist/wu-status-updater.js +3 -3
  213. package/dist/wu-validation-constants.d.ts +2 -2
  214. package/dist/wu-validation-constants.js +2 -2
  215. package/dist/wu-validation.d.ts +3 -3
  216. package/dist/wu-validation.js +3 -3
  217. package/dist/wu-yaml-fixer.d.ts +2 -2
  218. package/dist/wu-yaml-fixer.js +3 -3
  219. package/dist/wu-yaml.d.ts +23 -0
  220. package/dist/wu-yaml.js +76 -2
  221. package/package.json +5 -2
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Test Baseline - Test Ratchet Pattern (WU-1253)
3
+ *
4
+ * Implements a "ratchet" pattern for test failures:
5
+ * - Track known failures in a baseline file (.lumenflow/test-baseline.json)
6
+ * - Block NEW failures (not in baseline)
7
+ * - Allow pre-existing failures with warning
8
+ * - Auto-update baseline when tests are fixed (ratchet forward)
9
+ *
10
+ * This enables agents to work on WUs without being blocked by unrelated
11
+ * test failures, while still preventing introduction of NEW failures.
12
+ *
13
+ * @see https://lumenflow.dev/reference/test-ratchet/
14
+ */
15
+ import { z } from 'zod';
16
+ import { parseISO, isValid } from 'date-fns';
17
+ // ============================================================================
18
+ // Constants
19
+ // ============================================================================
20
+ /** Default path for the test baseline file */
21
+ export const DEFAULT_BASELINE_PATH = '.lumenflow/test-baseline.json';
22
+ /** Environment variable to override baseline path */
23
+ export const BASELINE_PATH_ENV = 'LUMENFLOW_TEST_BASELINE';
24
+ /** Current schema version */
25
+ export const BASELINE_VERSION = 1;
26
+ /**
27
+ * Zod schema for ISO8601 datetime strings.
28
+ * Uses date-fns (well-maintained library) for validation instead of regex.
29
+ */
30
+ const isoDateTimeString = z.string().refine((val) => {
31
+ const parsed = parseISO(val);
32
+ return isValid(parsed);
33
+ }, { message: 'Invalid ISO8601 datetime string' });
34
+ // ============================================================================
35
+ // Schema Definitions
36
+ // ============================================================================
37
+ /**
38
+ * Schema for a known test failure entry
39
+ */
40
+ export const KnownFailureSchema = z.object({
41
+ /** Name of the failing test (describe > it format) */
42
+ test_name: z.string().min(1),
43
+ /** Path to the test file */
44
+ file_path: z.string().min(1),
45
+ /** Why this failure is in the baseline */
46
+ failure_reason: z.string().min(1),
47
+ /** When this failure was added to baseline */
48
+ added_at: isoDateTimeString,
49
+ /** Which WU added this to baseline */
50
+ added_by_wu: z.string().regex(/^WU-\d+$/),
51
+ /** WU expected to fix this failure (optional) */
52
+ expected_fix_wu: z
53
+ .string()
54
+ .regex(/^WU-\d+$/)
55
+ .optional(),
56
+ /** Optional reason for skipping this test */
57
+ skip_reason: z.string().optional(),
58
+ });
59
+ /**
60
+ * Schema for baseline statistics
61
+ */
62
+ export const BaselineStatsSchema = z.object({
63
+ /** Total number of known failures */
64
+ total_known_failures: z.number().int().min(0),
65
+ /** Last time the baseline ratcheted forward (tests fixed) */
66
+ last_ratchet_forward: isoDateTimeString.optional(),
67
+ });
68
+ /**
69
+ * Schema for the complete test baseline file
70
+ */
71
+ export const TestBaselineSchema = z.object({
72
+ /** Schema version for future migrations */
73
+ version: z.literal(BASELINE_VERSION),
74
+ /** When the baseline was last updated */
75
+ updated_at: isoDateTimeString,
76
+ /** Which WU last updated the baseline */
77
+ updated_by: z.string().regex(/^WU-\d+$/),
78
+ /** List of known test failures */
79
+ known_failures: z.array(KnownFailureSchema),
80
+ /** Baseline statistics */
81
+ stats: BaselineStatsSchema,
82
+ });
83
+ // ============================================================================
84
+ // Core Functions
85
+ // ============================================================================
86
+ /**
87
+ * Parse a test baseline JSON string
88
+ *
89
+ * @param json - JSON string content of baseline file
90
+ * @returns Parse result with baseline data or error
91
+ */
92
+ export function parseTestBaseline(json) {
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(json);
96
+ }
97
+ catch (e) {
98
+ return {
99
+ success: false,
100
+ error: `Invalid JSON: ${e instanceof Error ? e.message : 'Unknown error'}`,
101
+ };
102
+ }
103
+ const result = TestBaselineSchema.safeParse(parsed);
104
+ if (!result.success) {
105
+ return {
106
+ success: false,
107
+ error: `Schema validation failed: ${result.error.message}`,
108
+ };
109
+ }
110
+ return { success: true, data: result.data };
111
+ }
112
+ /**
113
+ * Create a new test baseline
114
+ *
115
+ * @param wuId - WU creating the baseline
116
+ * @param initialFailures - Optional initial failures to add
117
+ * @returns New test baseline
118
+ */
119
+ export function createTestBaseline(wuId, initialFailures) {
120
+ const now = new Date().toISOString();
121
+ const knownFailures = (initialFailures ?? []).map((f) => ({
122
+ test_name: f.test_name,
123
+ file_path: f.file_path,
124
+ failure_reason: f.failure_reason,
125
+ added_at: now,
126
+ added_by_wu: wuId,
127
+ expected_fix_wu: f.expected_fix_wu,
128
+ }));
129
+ return {
130
+ version: BASELINE_VERSION,
131
+ updated_at: now,
132
+ updated_by: wuId,
133
+ known_failures: knownFailures,
134
+ stats: {
135
+ total_known_failures: knownFailures.length,
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * Compare current test results against the baseline
141
+ *
142
+ * This is the core ratchet logic:
143
+ * - NEW failures (not in baseline) block the gate
144
+ * - Pre-existing failures (in baseline) show warning
145
+ * - Fixed tests (in baseline but now passing) trigger ratchet forward
146
+ *
147
+ * @param baseline - The test baseline
148
+ * @param currentFailures - Current test failures from test run
149
+ * @returns Comparison result
150
+ */
151
+ export function compareTestResults(baseline, currentFailures) {
152
+ const failingTests = currentFailures.filter((t) => !t.passed);
153
+ // Build lookup sets for efficient comparison
154
+ const baselineTestNames = new Set(baseline.known_failures.map((f) => f.test_name));
155
+ const currentFailingNames = new Set(failingTests.map((f) => f.test_name));
156
+ // Find NEW failures (in current failures, NOT in baseline)
157
+ const newFailures = failingTests.filter((f) => !baselineTestNames.has(f.test_name));
158
+ // Find pre-existing failures (in current failures AND in baseline)
159
+ const preExistingFailures = baseline.known_failures.filter((f) => currentFailingNames.has(f.test_name));
160
+ // Find fixed tests (in baseline but NOT in current failures)
161
+ const fixedTests = baseline.known_failures.filter((f) => !currentFailingNames.has(f.test_name));
162
+ return {
163
+ newFailures,
164
+ preExistingFailures,
165
+ fixedTests,
166
+ shouldBlock: newFailures.length > 0,
167
+ hasWarnings: preExistingFailures.length > 0,
168
+ shouldRatchetForward: fixedTests.length > 0,
169
+ };
170
+ }
171
+ /**
172
+ * Update the baseline (ratchet forward or add new known failures)
173
+ *
174
+ * @param baseline - Current baseline
175
+ * @param wuId - WU making the update
176
+ * @param options - Update options
177
+ * @returns Updated baseline (immutable)
178
+ */
179
+ export function updateBaseline(baseline, wuId, options) {
180
+ const now = new Date().toISOString();
181
+ const { fixedTests = [], newKnownFailures = [] } = options;
182
+ // Remove fixed tests (ratchet forward)
183
+ const fixedTestSet = new Set(fixedTests);
184
+ let knownFailures = baseline.known_failures.filter((f) => !fixedTestSet.has(f.test_name));
185
+ // Add new known failures
186
+ const newEntries = newKnownFailures.map((f) => ({
187
+ test_name: f.test_name,
188
+ file_path: f.file_path,
189
+ failure_reason: f.failure_reason,
190
+ added_at: now,
191
+ added_by_wu: wuId,
192
+ expected_fix_wu: f.expected_fix_wu,
193
+ }));
194
+ knownFailures = [...knownFailures, ...newEntries];
195
+ const stats = {
196
+ total_known_failures: knownFailures.length,
197
+ };
198
+ // Record ratchet forward if we removed tests
199
+ if (fixedTests.length > 0) {
200
+ stats.last_ratchet_forward = now;
201
+ }
202
+ return {
203
+ version: BASELINE_VERSION,
204
+ updated_at: now,
205
+ updated_by: wuId,
206
+ known_failures: knownFailures,
207
+ stats,
208
+ };
209
+ }
210
+ // ============================================================================
211
+ // Formatting Functions
212
+ // ============================================================================
213
+ /**
214
+ * Format a warning message for pre-existing failures
215
+ *
216
+ * @param preExisting - Pre-existing failures from baseline
217
+ * @returns Formatted warning string
218
+ */
219
+ export function formatBaselineWarning(preExisting) {
220
+ const lines = [
221
+ '',
222
+ '='.repeat(70),
223
+ ' Pre-existing test failures (from baseline)',
224
+ '='.repeat(70),
225
+ '',
226
+ ` These failures are tracked in .lumenflow/test-baseline.json`,
227
+ ' They do not block your WU, but should be fixed eventually.',
228
+ '',
229
+ ];
230
+ for (const failure of preExisting) {
231
+ lines.push(` - ${failure.test_name}`);
232
+ lines.push(` File: ${failure.file_path}`);
233
+ lines.push(` Reason: ${failure.failure_reason}`);
234
+ if (failure.expected_fix_wu) {
235
+ lines.push(` Expected fix: ${failure.expected_fix_wu}`);
236
+ }
237
+ lines.push('');
238
+ }
239
+ lines.push('='.repeat(70));
240
+ return lines.join('\n');
241
+ }
242
+ /**
243
+ * Format an error message for new failures
244
+ *
245
+ * @param newFailures - New test failures
246
+ * @returns Formatted error string
247
+ */
248
+ export function formatNewFailureError(newFailures) {
249
+ const lines = [
250
+ '',
251
+ '='.repeat(70),
252
+ ' NEW test failure(s) detected!',
253
+ '='.repeat(70),
254
+ '',
255
+ ' The following tests failed and are NOT in the baseline.',
256
+ ' This blocks your WU from completion.',
257
+ '',
258
+ ' Options:',
259
+ ' 1. Fix the test or add to baseline with:',
260
+ ' pnpm baseline:add --test "<test_name>" --reason "<why>" --fix-wu WU-XXXX',
261
+ ' 2. If this is a pre-existing failure on main, it should be in the baseline.',
262
+ '',
263
+ ];
264
+ for (const failure of newFailures) {
265
+ lines.push(` - ${failure.test_name}`);
266
+ lines.push(` File: ${failure.file_path}`);
267
+ if (failure.error_message) {
268
+ lines.push(` Error: ${failure.error_message.substring(0, 100)}...`);
269
+ }
270
+ lines.push('');
271
+ }
272
+ lines.push('='.repeat(70));
273
+ return lines.join('\n');
274
+ }
275
+ /**
276
+ * Get the path to the test baseline file
277
+ *
278
+ * @returns Baseline file path
279
+ */
280
+ export function getBaselineFilePath() {
281
+ return process.env[BASELINE_PATH_ENV] ?? DEFAULT_BASELINE_PATH;
282
+ }
@@ -29,7 +29,7 @@ export interface GetSuggestionsOptions {
29
29
  * When provided, suggestions for high-impact WUs are ranked higher
30
30
  * within the same priority level.
31
31
  *
32
- * @see flow-bottlenecks.mjs for score calculation
32
+ * @see flow-bottlenecks.ts for score calculation
33
33
  */
34
34
  bottleneckScores?: BottleneckScores;
35
35
  }
@@ -92,8 +92,44 @@ const wuClaim = {
92
92
  ];
93
93
  },
94
94
  };
95
+ /**
96
+ * Predicate: Check if worktree has no uncommitted changes.
97
+ * For wu:prep running from worktree itself.
98
+ */
99
+ const gitCleanPredicate = {
100
+ id: 'git-clean',
101
+ description: 'Worktree must not have uncommitted changes',
102
+ severity: SEVERITY.ERROR,
103
+ check: (context) => !context.git.isDirty,
104
+ getFixMessage: () => 'Commit or stash changes in worktree before running wu:prep',
105
+ };
106
+ /**
107
+ * Command definition for wu:prep (WU-1223).
108
+ *
109
+ * Runs gates and generates docs in the worktree, then prints copy-paste
110
+ * instruction to run wu:done from main checkout for merge/cleanup.
111
+ */
112
+ const wuPrep = {
113
+ name: COMMANDS.WU_PREP,
114
+ description: 'Run gates + docs in worktree, prepare for wu:done',
115
+ requiredLocation: LOCATION_TYPES.WORKTREE,
116
+ requiredWuStatus: WU_STATUS.IN_PROGRESS,
117
+ predicates: [gitCleanPredicate, stateConsistentPredicate],
118
+ getNextSteps: (context) => {
119
+ const mainPath = context.location.mainCheckout || '/path/to/main';
120
+ const wuId = context.wu?.id || 'WU-XXX';
121
+ return [
122
+ `1. Gates and docs generated in worktree`,
123
+ `2. Return to main checkout and complete:`,
124
+ ` cd ${mainPath} && pnpm wu:done --id ${wuId}`,
125
+ ];
126
+ },
127
+ };
95
128
  /**
96
129
  * Command definition for wu:done.
130
+ *
131
+ * WU-1223: Now requires main checkout (merge + cleanup only).
132
+ * Use wu:prep from worktree for gates and docs generation.
97
133
  */
98
134
  const wuDone = {
99
135
  name: COMMANDS.WU_DONE,
@@ -161,6 +197,7 @@ const wuRecover = {
161
197
  export const COMMAND_REGISTRY = new Map([
162
198
  [COMMANDS.WU_CREATE, wuCreate],
163
199
  [COMMANDS.WU_CLAIM, wuClaim],
200
+ [COMMANDS.WU_PREP, wuPrep],
164
201
  [COMMANDS.WU_DONE, wuDone],
165
202
  [COMMANDS.WU_BLOCK, wuBlock],
166
203
  [COMMANDS.WU_UNBLOCK, wuUnblock],
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file backlog-sync.ts
3
+ * @description Validates backlog.md is in sync with WU YAML files (WU-1111)
4
+ */
5
+ export interface BacklogSyncResult {
6
+ valid: boolean;
7
+ errors: string[];
8
+ warnings: string[];
9
+ wuCount: number;
10
+ backlogCount: number;
11
+ }
12
+ export declare function validateBacklogSync(options?: {
13
+ cwd?: string;
14
+ }): Promise<BacklogSyncResult>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @file backlog-sync.ts
3
+ * @description Validates backlog.md is in sync with WU YAML files (WU-1111)
4
+ */
5
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { FILE_SYSTEM } from '../wu-constants.js';
8
+ import { createWuPaths } from '../wu-paths.js';
9
+ function extractWUIDsFromBacklog(content) {
10
+ const wuIds = [];
11
+ const pattern = /WU-\d+/gi;
12
+ let match;
13
+ while ((match = pattern.exec(content)) !== null) {
14
+ const wuId = match[0].toUpperCase();
15
+ if (!wuIds.includes(wuId)) {
16
+ wuIds.push(wuId);
17
+ }
18
+ }
19
+ return wuIds;
20
+ }
21
+ function getWUIDsFromFiles(wuDir) {
22
+ if (!existsSync(wuDir)) {
23
+ return [];
24
+ }
25
+ return readdirSync(wuDir)
26
+ .filter((f) => f.endsWith('.yaml'))
27
+ .map((f) => f.replace('.yaml', '').toUpperCase());
28
+ }
29
+ export async function validateBacklogSync(options = {}) {
30
+ const { cwd = process.cwd() } = options;
31
+ const errors = [];
32
+ const warnings = [];
33
+ const paths = createWuPaths({ projectRoot: cwd });
34
+ const backlogPath = path.join(cwd, paths.BACKLOG());
35
+ const wuDir = path.join(cwd, paths.WU_DIR());
36
+ if (!existsSync(backlogPath)) {
37
+ errors.push(`Backlog file not found: ${backlogPath}`);
38
+ return { valid: false, errors, warnings, wuCount: 0, backlogCount: 0 };
39
+ }
40
+ const wuIdsFromFiles = getWUIDsFromFiles(wuDir);
41
+ const backlogContent = readFileSync(backlogPath, {
42
+ encoding: FILE_SYSTEM.UTF8,
43
+ });
44
+ const wuIdsFromBacklog = extractWUIDsFromBacklog(backlogContent);
45
+ for (const wuId of wuIdsFromFiles) {
46
+ if (!wuIdsFromBacklog.includes(wuId)) {
47
+ errors.push(`${wuId} not found in backlog.md (exists as ${wuId}.yaml)`);
48
+ }
49
+ }
50
+ for (const wuId of wuIdsFromBacklog) {
51
+ if (!wuIdsFromFiles.includes(wuId)) {
52
+ warnings.push(`${wuId} referenced in backlog.md but ${wuId}.yaml not found`);
53
+ }
54
+ }
55
+ return {
56
+ valid: errors.length === 0,
57
+ errors,
58
+ warnings,
59
+ wuCount: wuIdsFromFiles.length,
60
+ backlogCount: wuIdsFromBacklog.length,
61
+ };
62
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @file supabase-docs-linter.ts
3
+ * @description Runs Supabase docs linter when available (optional in consumer repos)
4
+ */
5
+ export interface SupabaseDocsLinterResult {
6
+ ok: boolean;
7
+ skipped: boolean;
8
+ message?: string;
9
+ errors?: string[];
10
+ }
11
+ export interface SupabaseDocsLinterOptions {
12
+ cwd?: string;
13
+ logger?: {
14
+ log: (message: string) => void;
15
+ warn?: (message: string) => void;
16
+ };
17
+ }
18
+ export declare function runSupabaseDocsLinter(options?: SupabaseDocsLinterOptions): Promise<SupabaseDocsLinterResult>;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @file supabase-docs-linter.ts
3
+ * @description Runs Supabase docs linter when available (optional in consumer repos)
4
+ */
5
+ import { existsSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
8
+ export async function runSupabaseDocsLinter(options = {}) {
9
+ const { cwd = process.cwd(), logger = console } = options;
10
+ const linterPath = path.join(cwd, 'packages', 'linters', 'supabase-docs-linter.js');
11
+ if (!existsSync(linterPath)) {
12
+ return {
13
+ ok: true,
14
+ skipped: true,
15
+ message: 'Supabase docs linter not found; skipping.',
16
+ };
17
+ }
18
+ const moduleUrl = pathToFileURL(linterPath).href;
19
+ const module = await import(moduleUrl);
20
+ const runFn = module.runSupabaseDocsLinter ?? module.default;
21
+ if (typeof runFn !== 'function') {
22
+ return {
23
+ ok: false,
24
+ skipped: false,
25
+ errors: ['Supabase docs linter does not export runSupabaseDocsLinter.'],
26
+ };
27
+ }
28
+ const result = await runFn({ cwd, logger });
29
+ if (result && typeof result === 'object' && 'ok' in result) {
30
+ return {
31
+ ok: Boolean(result.ok),
32
+ skipped: Boolean(result.skipped),
33
+ message: result.message,
34
+ errors: result.errors,
35
+ };
36
+ }
37
+ return {
38
+ ok: true,
39
+ skipped: false,
40
+ message: 'Supabase docs linter completed.',
41
+ };
42
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @file wu-tasks.ts
3
+ * @description WU YAML validation helpers (shared by CLI and preflight)
4
+ */
5
+ export interface ValidationResult {
6
+ valid: boolean;
7
+ warnings: string[];
8
+ errors: string[];
9
+ }
10
+ export interface ValidationSummary {
11
+ totalValid: number;
12
+ totalInvalid: number;
13
+ totalWarnings: number;
14
+ results: Array<{
15
+ wuId: string;
16
+ } & ValidationResult>;
17
+ }
18
+ export declare function validateSingleWU(wuPath: string, options?: {
19
+ strict?: boolean;
20
+ }): ValidationResult;
21
+ export declare function validateAllWUs(options?: {
22
+ strict?: boolean;
23
+ doneOnly?: boolean;
24
+ }): ValidationSummary;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @file wu-tasks.ts
3
+ * @description WU YAML validation helpers (shared by CLI and preflight)
4
+ */
5
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
6
+ import { WU_PATHS } from '../wu-paths.js';
7
+ import { parseYAML } from '../wu-yaml.js';
8
+ import { validateWU, validateWUCompleteness } from '../wu-schema.js';
9
+ import { FILE_SYSTEM } from '../wu-constants.js';
10
+ export function validateSingleWU(wuPath, options = {}) {
11
+ const { strict = false } = options;
12
+ const errors = [];
13
+ const warnings = [];
14
+ if (!existsSync(wuPath)) {
15
+ errors.push(`WU file not found: ${wuPath}`);
16
+ return { valid: false, warnings, errors };
17
+ }
18
+ let doc;
19
+ try {
20
+ const text = readFileSync(wuPath, { encoding: FILE_SYSTEM.UTF8 });
21
+ doc = parseYAML(text);
22
+ }
23
+ catch (e) {
24
+ errors.push(`Failed to parse YAML: ${e.message}`);
25
+ return { valid: false, warnings, errors };
26
+ }
27
+ const schemaResult = validateWU(doc);
28
+ if (!schemaResult.success) {
29
+ const schemaErrors = schemaResult.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`);
30
+ errors.push(...schemaErrors);
31
+ return { valid: false, warnings, errors };
32
+ }
33
+ const completenessResult = validateWUCompleteness(schemaResult.data);
34
+ warnings.push(...completenessResult.warnings);
35
+ if (strict && warnings.length > 0) {
36
+ errors.push(...warnings.map((w) => `[STRICT] ${w}`));
37
+ return { valid: false, warnings: [], errors };
38
+ }
39
+ return { valid: true, warnings, errors };
40
+ }
41
+ export function validateAllWUs(options = {}) {
42
+ const { strict = false, doneOnly = false } = options;
43
+ const wuDir = WU_PATHS.WU_DIR();
44
+ if (!existsSync(wuDir)) {
45
+ return {
46
+ totalValid: 0,
47
+ totalInvalid: 1,
48
+ totalWarnings: 0,
49
+ results: [
50
+ {
51
+ wuId: 'DIRECTORY',
52
+ valid: false,
53
+ warnings: [],
54
+ errors: [`WU directory not found: ${wuDir}`],
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ const files = readdirSync(wuDir).filter((f) => f.endsWith('.yaml'));
60
+ const results = [];
61
+ let totalValid = 0;
62
+ let totalInvalid = 0;
63
+ let totalWarnings = 0;
64
+ for (const file of files) {
65
+ const wuPath = `${wuDir}/${file}`;
66
+ const wuId = file.replace('.yaml', '');
67
+ if (doneOnly) {
68
+ try {
69
+ const text = readFileSync(wuPath, { encoding: FILE_SYSTEM.UTF8 });
70
+ const doc = parseYAML(text);
71
+ if (doc.status !== 'done') {
72
+ continue;
73
+ }
74
+ }
75
+ catch {
76
+ // If we can't read, still validate to catch the error
77
+ }
78
+ }
79
+ const result = validateSingleWU(wuPath, { strict });
80
+ results.push({ wuId, ...result });
81
+ if (result.valid) {
82
+ totalValid++;
83
+ totalWarnings += result.warnings.length;
84
+ }
85
+ else {
86
+ totalInvalid++;
87
+ }
88
+ }
89
+ return { totalValid, totalInvalid, totalWarnings, results };
90
+ }
@@ -10,7 +10,7 @@
10
10
  * - Reports last activity timestamp
11
11
  * - Identifies potentially abandoned WUs
12
12
  *
13
- * @see {@link tools/lib/__tests__/worktree-scanner.test.mjs} - Tests
13
+ * @see {@link packages/@lumenflow/cli/src/lib/__tests__/worktree-scanner.test.ts} - Tests
14
14
  */
15
15
  /**
16
16
  * @typedef {object} WorktreeInfo
@@ -10,7 +10,7 @@
10
10
  * - Reports last activity timestamp
11
11
  * - Identifies potentially abandoned WUs
12
12
  *
13
- * @see {@link tools/lib/__tests__/worktree-scanner.test.mjs} - Tests
13
+ * @see {@link packages/@lumenflow/cli/src/lib/__tests__/worktree-scanner.test.ts} - Tests
14
14
  */
15
15
  import { exec } from 'node:child_process';
16
16
  import { promisify } from 'node:util';
@@ -10,7 +10,7 @@
10
10
  * auto-create the node_modules symlink pointing to the main repo's
11
11
  * node_modules directory, including nested package node_modules.
12
12
  *
13
- * @module tools/lib/worktree-symlink.mjs
13
+ * @module tools/lib/worktree-symlink.ts
14
14
  */
15
15
  /**
16
16
  * List of nested package/app paths that have their own node_modules
@@ -55,7 +55,7 @@ export declare function hasWorktreePathSymlinks(nodeModulesPath: any): {
55
55
  * @returns {{created: boolean, skipped: boolean, refused?: boolean, reason?: string, error?: Error}}
56
56
  *
57
57
  * @example
58
- * // In wu-claim.mjs after worktree creation:
58
+ * // In wu-claim.ts after worktree creation:
59
59
  * symlinkNodeModules('/path/to/worktrees/operations-tooling-wu-1443');
60
60
  */
61
61
  export declare function symlinkNodeModules(worktreePath: any, logger?: Console, mainRepoPath?: any): {
@@ -90,7 +90,7 @@ export declare function symlinkNodeModules(worktreePath: any, logger?: Console,
90
90
  * @returns {{created: number, skipped: number, errors: Error[]}}
91
91
  *
92
92
  * @example
93
- * // In wu-claim.mjs after worktree creation:
93
+ * // In wu-claim.ts after worktree creation:
94
94
  * symlinkNestedNodeModules(
95
95
  * '/path/to/worktrees/operations-tooling-wu-1579',
96
96
  * '/path/to/main-repo'
@@ -10,7 +10,7 @@
10
10
  * auto-create the node_modules symlink pointing to the main repo's
11
11
  * node_modules directory, including nested package node_modules.
12
12
  *
13
- * @module tools/lib/worktree-symlink.mjs
13
+ * @module tools/lib/worktree-symlink.ts
14
14
  */
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
@@ -202,7 +202,7 @@ function checkMainNodeModulesHealth(mainRepoPath, logger) {
202
202
  * @returns {{created: boolean, skipped: boolean, refused?: boolean, reason?: string, error?: Error}}
203
203
  *
204
204
  * @example
205
- * // In wu-claim.mjs after worktree creation:
205
+ * // In wu-claim.ts after worktree creation:
206
206
  * symlinkNodeModules('/path/to/worktrees/operations-tooling-wu-1443');
207
207
  */
208
208
  export function symlinkNodeModules(worktreePath, logger = console, mainRepoPath = null) {
@@ -310,7 +310,7 @@ function handleExistingNestedNodeModules(targetNodeModules, pkgPath, logger, err
310
310
  * @returns {{created: number, skipped: number, errors: Error[]}}
311
311
  *
312
312
  * @example
313
- * // In wu-claim.mjs after worktree creation:
313
+ * // In wu-claim.ts after worktree creation:
314
314
  * symlinkNestedNodeModules(
315
315
  * '/path/to/worktrees/operations-tooling-wu-1579',
316
316
  * '/path/to/main-repo'
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Backlog.md Update Utilities
3
3
  *
4
- * Centralized backlog.md update functions (extracted from wu-done.mjs)
4
+ * Centralized backlog.md update functions (extracted from wu-done.ts)
5
5
  * Refactored to use BacklogManager (WU-1212) for AST-based manipulation
6
6
  *
7
7
  * Used by both main wu:done flow AND recovery mode (DRY principle)