@lumenflow/core 1.0.0 → 1.3.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 (65) hide show
  1. package/dist/arg-parser.js +31 -1
  2. package/dist/backlog-generator.js +1 -1
  3. package/dist/backlog-sync-validator.js +3 -3
  4. package/dist/branch-check.d.ts +21 -0
  5. package/dist/branch-check.js +77 -0
  6. package/dist/cli/is-agent-branch.d.ts +11 -0
  7. package/dist/cli/is-agent-branch.js +15 -0
  8. package/dist/code-paths-overlap.js +2 -2
  9. package/dist/error-handler.d.ts +1 -0
  10. package/dist/error-handler.js +4 -1
  11. package/dist/git-adapter.d.ts +16 -0
  12. package/dist/git-adapter.js +23 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +2 -0
  15. package/dist/lane-checker.d.ts +36 -3
  16. package/dist/lane-checker.js +128 -17
  17. package/dist/lane-inference.js +3 -4
  18. package/dist/lumenflow-config-schema.d.ts +125 -0
  19. package/dist/lumenflow-config-schema.js +76 -0
  20. package/dist/orchestration-rules.d.ts +1 -1
  21. package/dist/orchestration-rules.js +2 -2
  22. package/dist/path-classifiers.d.ts +1 -1
  23. package/dist/path-classifiers.js +1 -1
  24. package/dist/rebase-artifact-cleanup.d.ts +17 -0
  25. package/dist/rebase-artifact-cleanup.js +49 -8
  26. package/dist/spawn-strategy.d.ts +53 -0
  27. package/dist/spawn-strategy.js +106 -0
  28. package/dist/stamp-utils.d.ts +10 -0
  29. package/dist/stamp-utils.js +17 -19
  30. package/dist/token-counter.js +2 -2
  31. package/dist/wu-consistency-checker.js +5 -5
  32. package/dist/wu-constants.d.ts +21 -3
  33. package/dist/wu-constants.js +28 -3
  34. package/dist/wu-done-branch-utils.d.ts +10 -0
  35. package/dist/wu-done-branch-utils.js +31 -0
  36. package/dist/wu-done-cleanup.d.ts +8 -0
  37. package/dist/wu-done-cleanup.js +122 -0
  38. package/dist/wu-done-docs-only.d.ts +20 -0
  39. package/dist/wu-done-docs-only.js +65 -0
  40. package/dist/wu-done-errors.d.ts +17 -0
  41. package/dist/wu-done-errors.js +24 -0
  42. package/dist/wu-done-inputs.d.ts +12 -0
  43. package/dist/wu-done-inputs.js +51 -0
  44. package/dist/wu-done-metadata.d.ts +100 -0
  45. package/dist/wu-done-metadata.js +193 -0
  46. package/dist/wu-done-paths.d.ts +69 -0
  47. package/dist/wu-done-paths.js +237 -0
  48. package/dist/wu-done-preflight.d.ts +48 -0
  49. package/dist/wu-done-preflight.js +185 -0
  50. package/dist/wu-done-validation.d.ts +82 -0
  51. package/dist/wu-done-validation.js +340 -0
  52. package/dist/wu-done-validators.d.ts +13 -409
  53. package/dist/wu-done-validators.js +9 -1225
  54. package/dist/wu-done-worktree.d.ts +0 -1
  55. package/dist/wu-done-worktree.js +12 -30
  56. package/dist/wu-schema.js +1 -3
  57. package/dist/wu-spawn-skills.d.ts +19 -0
  58. package/dist/wu-spawn-skills.js +148 -0
  59. package/dist/wu-spawn.d.ts +17 -4
  60. package/dist/wu-spawn.js +99 -176
  61. package/dist/wu-validation.d.ts +1 -0
  62. package/dist/wu-validation.js +21 -1
  63. package/dist/wu-validator.d.ts +51 -0
  64. package/dist/wu-validator.js +108 -0
  65. package/package.json +11 -8
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Core validation helpers for wu:done.
3
+ */
4
+ import path from 'node:path';
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { getGitForCwd } from './git-adapter.js';
7
+ import { parseYAML } from './wu-yaml.js';
8
+ import { BRANCHES, EMOJI, FILE_SYSTEM, GIT_COMMANDS, LOG_PREFIX, STRING_LITERALS, TEST_TYPES, VALIDATION, WU_TYPES, } from './wu-constants.js';
9
+ import { PLACEHOLDER_SENTINEL } from './wu-schema.js';
10
+ import { resolveExposureDefault } from './wu-validation.js';
11
+ import { validateAutomatedTestRequirement } from './manual-test-validator.js';
12
+ import { isDocumentationPath } from './file-classifiers.js';
13
+ export function applyExposureDefaults(doc) {
14
+ if (!doc || typeof doc !== 'object') {
15
+ return { applied: false };
16
+ }
17
+ if (typeof doc.exposure === 'string' && doc.exposure.trim().length > 0) {
18
+ return { applied: false, exposure: doc.exposure };
19
+ }
20
+ const exposureDefault = resolveExposureDefault(doc.lane);
21
+ if (!exposureDefault) {
22
+ return { applied: false };
23
+ }
24
+ doc.exposure = exposureDefault;
25
+ return { applied: true, exposure: exposureDefault };
26
+ }
27
+ export async function validateCodePathsExist(doc, id, options = {}) {
28
+ const { targetBranch = BRANCHES.MAIN, worktreePath = null } = options;
29
+ const errors = [];
30
+ const missing = [];
31
+ const codePaths = doc.code_paths || [];
32
+ // Skip validation for WUs without code_paths (docs-only, process WUs)
33
+ if (codePaths.length === 0) {
34
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} No code_paths to validate for ${id}`);
35
+ return { valid: true, errors: [], missing: [] };
36
+ }
37
+ console.log(`${LOG_PREFIX.DONE} Validating ${codePaths.length} code_paths exist...`);
38
+ // For worktree mode, check files exist in the worktree (will be merged)
39
+ // For branch-only mode or post-merge validation, check files exist on target branch
40
+ if (worktreePath && existsSync(worktreePath)) {
41
+ // Worktree mode: validate files exist in worktree
42
+ for (const filePath of codePaths) {
43
+ const fullPath = path.join(worktreePath, filePath);
44
+ if (!existsSync(fullPath)) {
45
+ missing.push(filePath);
46
+ }
47
+ }
48
+ if (missing.length > 0) {
49
+ errors.push(`code_paths validation failed - ${missing.length} file(s) not found in worktree:\n${missing
50
+ .map((p) => ` - ${p}`)
51
+ .join(STRING_LITERALS.NEWLINE)}\n\nEnsure all files listed in code_paths exist before running wu:done.`);
52
+ }
53
+ }
54
+ else {
55
+ // Branch-only or post-merge: use git ls-tree to check files on target branch
56
+ try {
57
+ const gitAdapter = getGitForCwd();
58
+ for (const filePath of codePaths) {
59
+ try {
60
+ // git ls-tree returns empty for non-existent files
61
+ const result = await gitAdapter.raw([GIT_COMMANDS.LS_TREE, targetBranch, '--', filePath]);
62
+ if (!result || result.trim() === '') {
63
+ missing.push(filePath);
64
+ }
65
+ }
66
+ catch {
67
+ // git ls-tree fails for non-existent paths
68
+ missing.push(filePath);
69
+ }
70
+ }
71
+ if (missing.length > 0) {
72
+ errors.push(`code_paths validation failed - ${missing.length} file(s) not found on ${targetBranch}:\n${missing
73
+ .map((p) => ` - ${p}`)
74
+ .join(STRING_LITERALS.NEWLINE)}\n\n❌ POTENTIAL FALSE COMPLETION DETECTED\n\n` +
75
+ `These files are listed in code_paths but do not exist on ${targetBranch}.\n` +
76
+ `This prevents creating a stamp for incomplete work.\n\n` +
77
+ `Fix options:\n` +
78
+ ` 1. Ensure all code is committed and merged to ${targetBranch}\n` +
79
+ ` 2. Update code_paths in ${id}.yaml to match actual files\n` +
80
+ ` 3. Remove files that were intentionally not created\n\n` +
81
+ `Context: WU-1351 prevents false completions from INIT-WORKFLOW-INTEGRITY`);
82
+ }
83
+ }
84
+ catch (err) {
85
+ // Non-fatal: warn but don't block if git command fails
86
+ console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not validate code_paths: ${err.message}`);
87
+ return { valid: true, errors: [], missing: [] };
88
+ }
89
+ }
90
+ if (errors.length > 0) {
91
+ return { valid: false, errors, missing };
92
+ }
93
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} All ${codePaths.length} code_paths verified`);
94
+ return { valid: true, errors: [], missing: [] };
95
+ }
96
+ /**
97
+ * Validate WU spec completeness (WU-1162, WU-1280)
98
+ *
99
+ * Ensures WU specifications are complete before allowing wu:done to proceed.
100
+ * Prevents placeholder WUs from being marked as done.
101
+ */
102
+ export function validateSpecCompleteness(doc, _id) {
103
+ const errors = [];
104
+ // Check for placeholder text in description
105
+ if (doc.description && doc.description.includes(PLACEHOLDER_SENTINEL)) {
106
+ errors.push(`Description contains ${PLACEHOLDER_SENTINEL} marker`);
107
+ }
108
+ // Handle both array and object formats for acceptance criteria
109
+ if (doc.acceptance) {
110
+ const hasPlaceholder = (value) => {
111
+ if (typeof value === 'string') {
112
+ return value.includes(PLACEHOLDER_SENTINEL);
113
+ }
114
+ if (Array.isArray(value)) {
115
+ return value.some((item) => hasPlaceholder(item));
116
+ }
117
+ if (typeof value === 'object' && value !== null) {
118
+ return Object.values(value).some((item) => hasPlaceholder(item));
119
+ }
120
+ return false;
121
+ };
122
+ if (hasPlaceholder(doc.acceptance)) {
123
+ errors.push(`Acceptance criteria contain ${PLACEHOLDER_SENTINEL} markers`);
124
+ }
125
+ }
126
+ // Check minimum description length
127
+ if (!doc.description || doc.description.trim().length < VALIDATION.MIN_DESCRIPTION_LENGTH) {
128
+ errors.push(`Description too short (${doc.description?.trim().length || 0} chars, minimum ${VALIDATION.MIN_DESCRIPTION_LENGTH})`);
129
+ }
130
+ // Check code_paths for non-documentation WUs
131
+ if (doc.type !== WU_TYPES.DOCUMENTATION && doc.type !== WU_TYPES.PROCESS) {
132
+ if (!doc.code_paths || doc.code_paths.length === 0) {
133
+ errors.push('Code paths required for non-documentation WUs');
134
+ }
135
+ // WU-1280: Check tests array for non-documentation WUs
136
+ // Support both tests: (current) and test_paths: (legacy)
137
+ const testObj = doc.tests || doc.test_paths || {};
138
+ // Helper to check if array has items
139
+ const hasItems = (arr) => Array.isArray(arr) && arr.length > 0;
140
+ const hasUnitTests = hasItems(testObj[TEST_TYPES.UNIT]);
141
+ const hasE2ETests = hasItems(testObj[TEST_TYPES.E2E]);
142
+ const hasManualTests = hasItems(testObj[TEST_TYPES.MANUAL]);
143
+ const hasIntegrationTests = hasItems(testObj[TEST_TYPES.INTEGRATION]);
144
+ if (!(hasUnitTests || hasE2ETests || hasManualTests || hasIntegrationTests)) {
145
+ errors.push('At least one test path required (unit, e2e, integration, or manual)');
146
+ }
147
+ // WU-2332: Require automated tests for code file changes
148
+ // Manual-only tests are not sufficient when code_paths contain actual code files
149
+ const automatedTestResult = validateAutomatedTestRequirement(doc);
150
+ if (!automatedTestResult.valid) {
151
+ errors.push(...automatedTestResult.errors);
152
+ }
153
+ }
154
+ return { valid: errors.length === 0, errors };
155
+ }
156
+ /**
157
+ * WU-1617: Post-mutation validation for wu:done
158
+ *
159
+ * Validates that metadata files written by tx.commit() are valid:
160
+ * 1. WU YAML has completed_at field with valid ISO datetime
161
+ * 2. WU YAML has locked: true
162
+ * 3. Stamp file exists
163
+ */
164
+ export function validatePostMutation({ id, wuPath, stampPath }) {
165
+ const errors = [];
166
+ // Check stamp file exists
167
+ if (!existsSync(stampPath)) {
168
+ errors.push(`Stamp file not created: ${stampPath}`);
169
+ }
170
+ // Read and validate WU YAML after mutation
171
+ if (!existsSync(wuPath)) {
172
+ errors.push(`WU YAML not found after mutation: ${wuPath}`);
173
+ return { valid: false, errors };
174
+ }
175
+ try {
176
+ const content = readFileSync(wuPath, { encoding: FILE_SYSTEM.ENCODING });
177
+ const doc = parseYAML(content);
178
+ // Verify completed_at exists and is valid ISO datetime
179
+ if (!doc.completed_at) {
180
+ errors.push(`Missing required field 'completed_at' in ${id}.yaml`);
181
+ }
182
+ else {
183
+ // Validate ISO datetime format (YYYY-MM-DDTHH:mm:ss.sssZ or similar)
184
+ const timestamp = new Date(doc.completed_at);
185
+ if (isNaN(timestamp.getTime())) {
186
+ errors.push(`Invalid completed_at timestamp: ${doc.completed_at}`);
187
+ }
188
+ }
189
+ // Verify locked is true
190
+ if (doc.locked !== true) {
191
+ errors.push(`Missing or invalid 'locked' field in ${id}.yaml (expected: true, got: ${doc.locked})`);
192
+ }
193
+ // Verify status is done
194
+ if (doc.status !== 'done') {
195
+ errors.push(`Invalid status in ${id}.yaml (expected: 'done', got: '${doc.status}')`);
196
+ }
197
+ }
198
+ catch (err) {
199
+ errors.push(`Failed to parse WU YAML after mutation: ${err.message}`);
200
+ }
201
+ return { valid: errors.length === 0, errors };
202
+ }
203
+ /**
204
+ * WU-2242: Validate that test_paths is required for non-doc WUs
205
+ *
206
+ * Enforces that WUs with code changes (non-documentation types with code_paths
207
+ * that contain actual code) have at least one test path specified.
208
+ */
209
+ export function validateTestPathsRequired(wu) {
210
+ // Skip validation for documentation and process WUs
211
+ if (wu.type === WU_TYPES.DOCUMENTATION || wu.type === WU_TYPES.PROCESS) {
212
+ return { valid: true };
213
+ }
214
+ // Skip if code_paths is empty or undefined
215
+ const codePaths = wu.code_paths || [];
216
+ if (codePaths.length === 0) {
217
+ return { valid: true };
218
+ }
219
+ // Skip if all code_paths are documentation paths
220
+ const hasCodeChanges = codePaths.some((p) => !isDocumentationPath(p));
221
+ if (!hasCodeChanges) {
222
+ return { valid: true };
223
+ }
224
+ // Check if tests object exists and has at least one test
225
+ const testObj = wu.tests || {};
226
+ // Helper to check if array has items
227
+ const hasItems = (arr) => Array.isArray(arr) && arr.length > 0;
228
+ const hasUnitTests = hasItems(testObj[TEST_TYPES.UNIT]);
229
+ const hasE2ETests = hasItems(testObj[TEST_TYPES.E2E]);
230
+ const hasManualTests = hasItems(testObj[TEST_TYPES.MANUAL]);
231
+ const hasIntegrationTests = hasItems(testObj[TEST_TYPES.INTEGRATION]);
232
+ // No tests at all - fail
233
+ if (!(hasUnitTests || hasE2ETests || hasManualTests || hasIntegrationTests)) {
234
+ return {
235
+ valid: false,
236
+ error: `${wu.id} requires test_paths: WU has code_paths but no tests specified. Add unit, e2e, integration, or manual tests.`,
237
+ };
238
+ }
239
+ // WU-2332: If we have tests, also check automated test requirement for code files
240
+ // Manual-only tests are not sufficient for code changes
241
+ const automatedTestResult = validateAutomatedTestRequirement(wu);
242
+ if (!automatedTestResult.valid) {
243
+ // Extract the first error line for the single-error format of this function
244
+ const errorSummary = automatedTestResult.errors[0]?.split('\n')[0] || 'Automated tests required';
245
+ return {
246
+ valid: false,
247
+ error: `${wu.id}: ${errorSummary}`,
248
+ };
249
+ }
250
+ return { valid: true };
251
+ }
252
+ /**
253
+ * WU-2310: Allowed path patterns for documentation WUs.
254
+ * Mirrors the patterns in gates-pre-commit.mjs gateDocsOnlyPathEnforcement()
255
+ * to enable early validation at preflight (before transaction starts).
256
+ *
257
+ * @constant {RegExp[]}
258
+ */
259
+ const DOCS_ONLY_ALLOWED_PATTERNS = [
260
+ /^memory-bank\//i,
261
+ /^docs\//i,
262
+ /\.md$/i,
263
+ /^\.beacon\/stamps\//i,
264
+ /^\.claude\//i,
265
+ /^ai\//i,
266
+ /^README\.md$/i,
267
+ /^CLAUDE\.md$/i,
268
+ ];
269
+ /**
270
+ * WU-2310: Check if a path is allowed for documentation WUs.
271
+ *
272
+ * @param {string} filePath - File path to check
273
+ * @returns {boolean} True if path is allowed for docs WUs
274
+ */
275
+ function isAllowedDocsPath(filePath) {
276
+ if (!filePath || typeof filePath !== 'string')
277
+ return false;
278
+ return DOCS_ONLY_ALLOWED_PATTERNS.some((pattern) => pattern.test(filePath));
279
+ }
280
+ /**
281
+ * WU-2310: Validate type vs code_paths at preflight (before transaction starts).
282
+ */
283
+ export function validateTypeVsCodePathsPreflight(wu) {
284
+ const errors = [];
285
+ const blockedPaths = [];
286
+ // Only validate documentation WUs
287
+ if (wu.type !== WU_TYPES.DOCUMENTATION) {
288
+ return { valid: true, errors: [], blockedPaths: [], abortedBeforeTransaction: false };
289
+ }
290
+ // Skip if no code_paths
291
+ const codePaths = wu.code_paths;
292
+ if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
293
+ return { valid: true, errors: [], blockedPaths: [], abortedBeforeTransaction: false };
294
+ }
295
+ // Check each code_path against allowed patterns
296
+ for (const filePath of codePaths) {
297
+ if (!isAllowedDocsPath(filePath)) {
298
+ blockedPaths.push(filePath);
299
+ }
300
+ }
301
+ if (blockedPaths.length > 0) {
302
+ const pathsList = blockedPaths.map((p) => ` - ${p}`).join('\n');
303
+ errors.push(`Documentation WU ${wu.id} has code_paths that would fail pre-commit hook:\n${pathsList}`);
304
+ return { valid: false, errors, blockedPaths, abortedBeforeTransaction: true };
305
+ }
306
+ return { valid: true, errors: [], blockedPaths: [], abortedBeforeTransaction: false };
307
+ }
308
+ /**
309
+ * WU-2310: Build error message for type vs code_paths preflight failure.
310
+ */
311
+ export function buildTypeVsCodePathsErrorMessage(id, blockedPaths) {
312
+ return `
313
+ PREFLIGHT VALIDATION FAILED (WU-2310)
314
+
315
+ WU ${id} is type: documentation but has code_paths that are not allowed:
316
+
317
+ ${blockedPaths.map((p) => ` - ${p}`).join('\n')}
318
+
319
+ This would fail at git commit time (pre-commit hook: gateDocsOnlyPathEnforcement).
320
+ Aborting BEFORE transaction to prevent inconsistent state.
321
+
322
+ Fix options:
323
+
324
+ 1. Change WU type to 'engineering' (or 'feature', 'bug', etc.):
325
+ pnpm wu:edit --id ${id} --type engineering
326
+
327
+ 2. Update code_paths to only include documentation files:
328
+ pnpm wu:edit --id ${id} --code-paths "docs/..." "*.md"
329
+
330
+ Allowed paths for documentation WUs:
331
+ - docs/
332
+ - ai/
333
+ - .claude/
334
+ - memory-bank/
335
+ - .beacon/stamps/
336
+ - *.md files
337
+
338
+ After fixing, retry: pnpm wu:done --id ${id}
339
+ `;
340
+ }