@lumenflow/cli 1.1.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.
- package/dist/gates.js +102 -39
- package/dist/init.js +241 -195
- package/dist/initiative-create.js +3 -7
- package/dist/initiative-edit.js +3 -3
- package/dist/wu-claim.js +295 -109
- package/dist/wu-cleanup.js +49 -3
- package/dist/wu-create.js +195 -121
- package/dist/wu-done.js +44 -13
- package/dist/wu-edit.js +152 -61
- package/dist/wu-infer-lane.js +2 -2
- package/dist/wu-spawn.js +77 -158
- package/package.json +14 -14
package/dist/wu-create.js
CHANGED
|
@@ -40,7 +40,7 @@ import { inferSubLane } from '@lumenflow/core/dist/lane-inference.js';
|
|
|
40
40
|
import { parseBacklogFrontmatter } from '@lumenflow/core/dist/backlog-parser.js';
|
|
41
41
|
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
42
42
|
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
43
|
-
import {
|
|
43
|
+
import { validateWU } from '@lumenflow/core/dist/wu-schema.js';
|
|
44
44
|
import { COMMIT_FORMATS, FILE_SYSTEM, STRING_LITERALS, READINESS_UI, } from '@lumenflow/core/dist/wu-constants.js';
|
|
45
45
|
// WU-1593: Use centralized validateWUIDFormat (DRY)
|
|
46
46
|
import { ensureOnMain, validateWUIDFormat } from '@lumenflow/core/dist/wu-helpers.js';
|
|
@@ -52,6 +52,8 @@ import { validateSpecCompleteness } from '@lumenflow/core/dist/wu-done-validator
|
|
|
52
52
|
import { readWU } from '@lumenflow/core/dist/wu-yaml.js';
|
|
53
53
|
// WU-2253: Import WU spec linter for acceptance/code_paths validation
|
|
54
54
|
import { lintWUSpec, formatLintErrors } from '@lumenflow/core/dist/wu-lint.js';
|
|
55
|
+
// WU-1025: Import placeholder validator for inline content validation
|
|
56
|
+
import { validateNoPlaceholders, buildPlaceholderErrorMessage, } from '@lumenflow/core/dist/wu-validator.js';
|
|
55
57
|
/** Log prefix for console output */
|
|
56
58
|
const LOG_PREFIX = '[wu:create]';
|
|
57
59
|
/** Micro-worktree operation name */
|
|
@@ -175,67 +177,29 @@ function displayReadinessSummary(id) {
|
|
|
175
177
|
console.warn(`${LOG_PREFIX} ⚠️ Could not validate readiness: ${err.message}`);
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* @param {Object} opts - Additional options
|
|
188
|
-
* @returns {string} Relative path to created YAML file
|
|
189
|
-
*/
|
|
190
|
-
function createWUYamlInWorktree(worktreePath, id, lane, title, priority, type, opts = {}) {
|
|
191
|
-
const wuRelativePath = WU_PATHS.WU(id);
|
|
192
|
-
const wuAbsolutePath = join(worktreePath, wuRelativePath);
|
|
193
|
-
const wuDir = join(worktreePath, WU_PATHS.WU_DIR());
|
|
194
|
-
mkdirSync(wuDir, { recursive: true });
|
|
195
|
-
// WU-1428: Use todayISO() for consistent YYYY-MM-DD format (library-first)
|
|
196
|
-
const today = todayISO();
|
|
197
|
-
// Parse initiative system fields from opts (WU-1247) and assigned_to (WU-1368)
|
|
198
|
-
const { initiative, phase, blockedBy, blocks, labels, assignedTo } = opts;
|
|
199
|
-
// WU-1364: Parse full spec inline options
|
|
200
|
-
const { description: inlineDescription, acceptance: inlineAcceptance, codePaths, testPathsManual, testPathsUnit, testPathsE2e, } = opts;
|
|
201
|
-
// WU-1998: Parse exposure field options
|
|
202
|
-
const { exposure, userJourney, uiPairingWus } = opts;
|
|
203
|
-
// WU-2320: Parse spec_refs option
|
|
204
|
-
const { specRefs } = opts;
|
|
205
|
-
// Helper to parse comma-separated strings into arrays (DRY)
|
|
206
|
-
const parseCommaSeparated = (value) => value
|
|
207
|
-
? value
|
|
208
|
-
.split(',')
|
|
209
|
-
.map((s) => s.trim())
|
|
210
|
-
.filter(Boolean)
|
|
211
|
-
: [];
|
|
212
|
-
// WU-1364: Build description (inline or placeholder)
|
|
213
|
-
const description = inlineDescription
|
|
214
|
-
? inlineDescription
|
|
215
|
-
: `${PLACEHOLDER_SENTINEL} Describe the work to be done.\n\nContext: ...\nProblem: ...\nSolution: ...\n`;
|
|
216
|
-
// WU-1364: Build acceptance (inline array or placeholder)
|
|
217
|
-
const acceptance = inlineAcceptance && inlineAcceptance.length > 0
|
|
218
|
-
? inlineAcceptance
|
|
219
|
-
: [
|
|
220
|
-
`${PLACEHOLDER_SENTINEL} Define acceptance criteria`,
|
|
221
|
-
'pnpm format, lint, typecheck → PASS',
|
|
222
|
-
];
|
|
223
|
-
// WU-1364: Build code_paths from inline flag
|
|
180
|
+
// Helper to parse comma-separated strings into arrays (DRY)
|
|
181
|
+
const parseCommaSeparated = (value) => value
|
|
182
|
+
? value
|
|
183
|
+
.split(',')
|
|
184
|
+
.map((s) => s.trim())
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
: [];
|
|
187
|
+
function buildWUContent({ id, lane, title, priority, type, created, opts, }) {
|
|
188
|
+
const { description, acceptance, codePaths, testPathsManual, testPathsUnit, testPathsE2e, initiative, phase, blockedBy, blocks, labels, assignedTo, exposure, userJourney, uiPairingWus, specRefs, } = opts;
|
|
224
189
|
const code_paths = parseCommaSeparated(codePaths);
|
|
225
|
-
// WU-1364: Build tests object from inline flags
|
|
226
190
|
const tests = {
|
|
227
191
|
manual: parseCommaSeparated(testPathsManual),
|
|
228
192
|
unit: parseCommaSeparated(testPathsUnit),
|
|
229
193
|
e2e: parseCommaSeparated(testPathsE2e),
|
|
230
194
|
};
|
|
231
|
-
|
|
195
|
+
return {
|
|
232
196
|
id,
|
|
233
197
|
title,
|
|
234
198
|
lane,
|
|
235
199
|
type,
|
|
236
200
|
status: 'ready',
|
|
237
201
|
priority,
|
|
238
|
-
created
|
|
202
|
+
created,
|
|
239
203
|
description,
|
|
240
204
|
acceptance,
|
|
241
205
|
code_paths,
|
|
@@ -245,24 +209,107 @@ function createWUYamlInWorktree(worktreePath, id, lane, title, priority, type, o
|
|
|
245
209
|
risks: [],
|
|
246
210
|
notes: '',
|
|
247
211
|
requires_review: false,
|
|
248
|
-
// Initiative system fields - only include if provided (WU-1247)
|
|
249
212
|
...(initiative && { initiative }),
|
|
250
213
|
...(phase && { phase: parseInt(phase, 10) }),
|
|
251
214
|
...(blockedBy && { blocked_by: blockedBy.split(',').map((s) => s.trim()) }),
|
|
252
215
|
...(blocks && { blocks: blocks.split(',').map((s) => s.trim()) }),
|
|
253
216
|
...(labels && { labels: labels.split(',').map((s) => s.trim()) }),
|
|
254
|
-
// WU-1368: Default assigned_to from git config user.email
|
|
255
217
|
...(assignedTo && { assigned_to: assignedTo }),
|
|
256
|
-
// WU-1998: Exposure field options - only include if provided
|
|
257
218
|
...(exposure && { exposure }),
|
|
258
219
|
...(userJourney && { user_journey: userJourney }),
|
|
259
220
|
...(uiPairingWus && { ui_pairing_wus: parseCommaSeparated(uiPairingWus) }),
|
|
260
|
-
// WU-2320: Spec references - only include if provided
|
|
261
221
|
...(specRefs && { spec_refs: parseCommaSeparated(specRefs) }),
|
|
262
222
|
};
|
|
263
|
-
|
|
223
|
+
}
|
|
224
|
+
export function validateCreateSpec({ id, lane, title, priority, type, opts, }) {
|
|
225
|
+
const errors = [];
|
|
226
|
+
const effectiveType = type || DEFAULT_TYPE;
|
|
227
|
+
if (!opts.description) {
|
|
228
|
+
errors.push('--description is required');
|
|
229
|
+
}
|
|
230
|
+
if (!opts.acceptance || opts.acceptance.length === 0) {
|
|
231
|
+
errors.push('--acceptance is required (repeatable)');
|
|
232
|
+
}
|
|
233
|
+
if (!opts.exposure) {
|
|
234
|
+
errors.push('--exposure is required');
|
|
235
|
+
}
|
|
236
|
+
const hasTestPaths = opts.testPathsManual || opts.testPathsUnit || opts.testPathsE2e;
|
|
237
|
+
if (effectiveType !== 'documentation' && effectiveType !== 'process') {
|
|
238
|
+
if (!opts.codePaths) {
|
|
239
|
+
errors.push('--code-paths is required for non-documentation WUs');
|
|
240
|
+
}
|
|
241
|
+
if (!hasTestPaths) {
|
|
242
|
+
errors.push('At least one test path flag is required (--test-paths-manual, --test-paths-unit, or --test-paths-e2e)');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (effectiveType === 'feature' && !opts.specRefs) {
|
|
246
|
+
errors.push('--spec-refs is required for type: feature WUs');
|
|
247
|
+
}
|
|
248
|
+
if (errors.length > 0) {
|
|
249
|
+
return { valid: false, errors };
|
|
250
|
+
}
|
|
251
|
+
const placeholderResult = validateNoPlaceholders({
|
|
252
|
+
description: opts.description,
|
|
253
|
+
acceptance: opts.acceptance,
|
|
254
|
+
});
|
|
255
|
+
if (!placeholderResult.valid) {
|
|
256
|
+
return {
|
|
257
|
+
valid: false,
|
|
258
|
+
errors: [buildPlaceholderErrorMessage('wu:create', placeholderResult)],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const today = todayISO();
|
|
262
|
+
const wuContent = buildWUContent({
|
|
263
|
+
id,
|
|
264
|
+
lane,
|
|
265
|
+
title,
|
|
266
|
+
priority,
|
|
267
|
+
type: effectiveType,
|
|
268
|
+
created: today,
|
|
269
|
+
opts,
|
|
270
|
+
});
|
|
271
|
+
const schemaResult = validateWU(wuContent);
|
|
272
|
+
if (!schemaResult.success) {
|
|
273
|
+
const schemaErrors = schemaResult.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`);
|
|
274
|
+
return { valid: false, errors: schemaErrors };
|
|
275
|
+
}
|
|
276
|
+
const completeness = validateSpecCompleteness(wuContent, id);
|
|
277
|
+
if (!completeness.valid) {
|
|
278
|
+
return { valid: false, errors: completeness.errors };
|
|
279
|
+
}
|
|
280
|
+
return { valid: true, errors: [] };
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Create WU YAML file in micro-worktree
|
|
284
|
+
*
|
|
285
|
+
* @param {string} worktreePath - Path to micro-worktree
|
|
286
|
+
* @param {string} id - WU ID
|
|
287
|
+
* @param {string} lane - WU lane
|
|
288
|
+
* @param {string} title - WU title
|
|
289
|
+
* @param {string} priority - WU priority
|
|
290
|
+
* @param {string} type - WU type
|
|
291
|
+
* @param {Object} opts - Additional options
|
|
292
|
+
* @returns {string} Relative path to created YAML file
|
|
293
|
+
*/
|
|
294
|
+
function createWUYamlInWorktree(worktreePath, id, lane, title, priority, type, opts = {}) {
|
|
295
|
+
const wuRelativePath = WU_PATHS.WU(id);
|
|
296
|
+
const wuAbsolutePath = join(worktreePath, wuRelativePath);
|
|
297
|
+
const wuDir = join(worktreePath, WU_PATHS.WU_DIR());
|
|
298
|
+
mkdirSync(wuDir, { recursive: true });
|
|
299
|
+
// WU-1428: Use todayISO() for consistent YYYY-MM-DD format (library-first)
|
|
300
|
+
const today = todayISO();
|
|
301
|
+
const wuContent = buildWUContent({
|
|
302
|
+
id,
|
|
303
|
+
lane,
|
|
304
|
+
title,
|
|
305
|
+
priority,
|
|
306
|
+
type,
|
|
307
|
+
created: today,
|
|
308
|
+
opts,
|
|
309
|
+
});
|
|
310
|
+
// WU-1539: Validate WU structure before writing (fail-fast, no placeholders)
|
|
264
311
|
// WU-1750: Zod transforms normalize embedded newlines in arrays and strings
|
|
265
|
-
const validationResult =
|
|
312
|
+
const validationResult = validateWU(wuContent);
|
|
266
313
|
if (!validationResult.success) {
|
|
267
314
|
const errors = validationResult.error.issues
|
|
268
315
|
.map((issue) => ` • ${issue.path.join('.')}: ${issue.message}`)
|
|
@@ -270,6 +317,14 @@ function createWUYamlInWorktree(worktreePath, id, lane, title, priority, type, o
|
|
|
270
317
|
die(`${LOG_PREFIX} ❌ WU YAML validation failed:\n\n${errors}\n\n` +
|
|
271
318
|
`Fix the issues above and retry.`);
|
|
272
319
|
}
|
|
320
|
+
const completenessResult = validateSpecCompleteness(wuContent, id);
|
|
321
|
+
if (!completenessResult.valid) {
|
|
322
|
+
const errorList = completenessResult.errors
|
|
323
|
+
.map((error) => ` • ${error}`)
|
|
324
|
+
.join(STRING_LITERALS.NEWLINE);
|
|
325
|
+
die(`${LOG_PREFIX} ❌ WU SPEC INCOMPLETE:\n\n${errorList}\n\n` +
|
|
326
|
+
`Provide the missing fields and retry.`);
|
|
327
|
+
}
|
|
273
328
|
// WU-2253: Validate acceptance/code_paths consistency and invariants compliance
|
|
274
329
|
// This blocks WU creation if acceptance references paths not in code_paths
|
|
275
330
|
// or if code_paths conflicts with tools/invariants.yml
|
|
@@ -429,76 +484,95 @@ async function main() {
|
|
|
429
484
|
if (!assignedTo) {
|
|
430
485
|
console.warn(`${LOG_PREFIX} ⚠️ No assigned_to set - WU will need manual assignment`);
|
|
431
486
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
487
|
+
const createSpecValidation = validateCreateSpec({
|
|
488
|
+
id: args.id,
|
|
489
|
+
lane: args.lane,
|
|
490
|
+
title: args.title,
|
|
491
|
+
priority: args.priority || DEFAULT_PRIORITY,
|
|
492
|
+
type: args.type || DEFAULT_TYPE,
|
|
493
|
+
opts: {
|
|
494
|
+
description: args.description,
|
|
495
|
+
acceptance: args.acceptance,
|
|
496
|
+
codePaths: args.codePaths,
|
|
497
|
+
testPathsManual: args.testPathsManual,
|
|
498
|
+
testPathsUnit: args.testPathsUnit,
|
|
499
|
+
testPathsE2e: args.testPathsE2e,
|
|
500
|
+
exposure: args.exposure,
|
|
501
|
+
userJourney: args.userJourney,
|
|
502
|
+
uiPairingWus: args.uiPairingWus,
|
|
503
|
+
specRefs: args.specRefs,
|
|
504
|
+
initiative: args.initiative,
|
|
505
|
+
phase: args.phase,
|
|
506
|
+
blockedBy: args.blockedBy,
|
|
507
|
+
blocks: args.blocks,
|
|
508
|
+
labels: args.labels,
|
|
509
|
+
assignedTo,
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
if (!createSpecValidation.valid) {
|
|
513
|
+
const errorList = createSpecValidation.errors
|
|
514
|
+
.map((error) => ` • ${error}`)
|
|
515
|
+
.join(STRING_LITERALS.NEWLINE);
|
|
516
|
+
die(`${LOG_PREFIX} ❌ Spec validation failed:\n\n${errorList}`);
|
|
456
517
|
}
|
|
518
|
+
console.log(`${LOG_PREFIX} ✅ Spec validation passed`);
|
|
457
519
|
// Transaction: micro-worktree isolation (WU-1439)
|
|
458
520
|
try {
|
|
459
521
|
const priority = args.priority || DEFAULT_PRIORITY;
|
|
460
522
|
const type = args.type || DEFAULT_TYPE;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
commitMessage,
|
|
498
|
-
files
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
523
|
+
const previousWuTool = process.env.LUMENFLOW_WU_TOOL;
|
|
524
|
+
process.env.LUMENFLOW_WU_TOOL = OPERATION_NAME;
|
|
525
|
+
try {
|
|
526
|
+
await withMicroWorktree({
|
|
527
|
+
operation: OPERATION_NAME,
|
|
528
|
+
id: args.id,
|
|
529
|
+
logPrefix: LOG_PREFIX,
|
|
530
|
+
execute: async ({ worktreePath }) => {
|
|
531
|
+
// Create WU YAML in micro-worktree
|
|
532
|
+
const wuPath = createWUYamlInWorktree(worktreePath, args.id, args.lane, args.title, priority, type, {
|
|
533
|
+
// Initiative system fields (WU-1247)
|
|
534
|
+
initiative: args.initiative,
|
|
535
|
+
phase: args.phase,
|
|
536
|
+
blockedBy: args.blockedBy,
|
|
537
|
+
blocks: args.blocks,
|
|
538
|
+
labels: args.labels,
|
|
539
|
+
// WU-1368: Assigned to
|
|
540
|
+
assignedTo,
|
|
541
|
+
// WU-1364: Full spec inline options
|
|
542
|
+
description: args.description,
|
|
543
|
+
acceptance: args.acceptance,
|
|
544
|
+
codePaths: args.codePaths,
|
|
545
|
+
testPathsManual: args.testPathsManual,
|
|
546
|
+
testPathsUnit: args.testPathsUnit,
|
|
547
|
+
testPathsE2e: args.testPathsE2e,
|
|
548
|
+
// WU-1998: Exposure field options
|
|
549
|
+
exposure: args.exposure,
|
|
550
|
+
userJourney: args.userJourney,
|
|
551
|
+
uiPairingWus: args.uiPairingWus,
|
|
552
|
+
// WU-2320: Spec references
|
|
553
|
+
specRefs: args.specRefs,
|
|
554
|
+
});
|
|
555
|
+
// Update backlog.md in micro-worktree
|
|
556
|
+
const backlogPath = updateBacklogInWorktree(worktreePath, args.id, args.lane, args.title);
|
|
557
|
+
// Build commit message
|
|
558
|
+
const shortTitle = truncateTitle(args.title);
|
|
559
|
+
const commitMessage = COMMIT_FORMATS.CREATE(args.id, shortTitle);
|
|
560
|
+
// Return commit message and files to commit
|
|
561
|
+
return {
|
|
562
|
+
commitMessage,
|
|
563
|
+
files: [wuPath, backlogPath],
|
|
564
|
+
};
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
finally {
|
|
569
|
+
if (previousWuTool === undefined) {
|
|
570
|
+
delete process.env.LUMENFLOW_WU_TOOL;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
process.env.LUMENFLOW_WU_TOOL = previousWuTool;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
502
576
|
console.log(`\n${LOG_PREFIX} ✅ Transaction complete!`);
|
|
503
577
|
console.log(`\nWU ${args.id} created successfully:`);
|
|
504
578
|
console.log(` File: ${WU_PATHS.WU(args.id)}`);
|
package/dist/wu-done.js
CHANGED
|
@@ -147,7 +147,7 @@ async function validateClaimMetadataBeforeGates(id, worktreePath, yamlStatus) {
|
|
|
147
147
|
` pnpm wu:repair-claim --id ${id}\n\n` +
|
|
148
148
|
`After repair, retry:\n` +
|
|
149
149
|
` pnpm wu:done --id ${id}\n\n` +
|
|
150
|
-
`See:
|
|
150
|
+
`See: docs/04-operations/_frameworks/lumenflow/agent/onboarding/troubleshooting-wu-done.md for more recovery options.`);
|
|
151
151
|
}
|
|
152
152
|
export function printExposureWarnings(wu, options = {}) {
|
|
153
153
|
// Validate exposure
|
|
@@ -1793,7 +1793,8 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1793
1793
|
}
|
|
1794
1794
|
else {
|
|
1795
1795
|
die(`Worktree not found (${worktreePath || 'unknown'}). Gates must run in the lane worktree.\n` +
|
|
1796
|
-
`If the worktree was removed, recreate it and retry, or
|
|
1796
|
+
`If the worktree was removed, recreate it and retry, or rerun with --branch-only when the lane branch exists.\n` +
|
|
1797
|
+
`Use --skip-gates only with justification.`);
|
|
1797
1798
|
}
|
|
1798
1799
|
// Step 0.75: Run COS governance gates (WU-614, COS v1.3 §7)
|
|
1799
1800
|
if (!args.skipCosGates) {
|
|
@@ -1858,6 +1859,13 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1858
1859
|
* @param {string|null} params.derivedWorktree - Derived worktree path
|
|
1859
1860
|
* @param {string} params.STAMPS_DIR - Stamps directory path
|
|
1860
1861
|
*/
|
|
1862
|
+
export function computeBranchOnlyFallback({ isBranchOnly, branchOnlyRequested, worktreeExists, derivedWorktree, }) {
|
|
1863
|
+
const allowFallback = Boolean(branchOnlyRequested) && !isBranchOnly && !worktreeExists && Boolean(derivedWorktree);
|
|
1864
|
+
return {
|
|
1865
|
+
allowFallback,
|
|
1866
|
+
effectiveBranchOnly: isBranchOnly || allowFallback,
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1861
1869
|
function printStateHUD({ id, docMain, isBranchOnly, isDocsOnly, derivedWorktree, STAMPS_DIR }) {
|
|
1862
1870
|
const stampExists = existsSync(path.join(STAMPS_DIR, `${id}.done`)) ? 'yes' : 'no';
|
|
1863
1871
|
const yamlStatus = docMain.status || 'unknown';
|
|
@@ -1869,6 +1877,8 @@ function printStateHUD({ id, docMain, isBranchOnly, isDocsOnly, derivedWorktree,
|
|
|
1869
1877
|
}
|
|
1870
1878
|
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
1871
1879
|
async function main() {
|
|
1880
|
+
// Allow pre-push hook to recognize wu:done automation (WU-1030)
|
|
1881
|
+
process.env.LUMENFLOW_WU_TOOL = 'wu-done';
|
|
1872
1882
|
// Validate CLI arguments and WU ID format (extracted to wu-done-validators.mjs)
|
|
1873
1883
|
const { args, id } = validateInputs(process.argv);
|
|
1874
1884
|
// Detect workspace mode and calculate paths (WU-1215: extracted to validators module)
|
|
@@ -1876,25 +1886,39 @@ async function main() {
|
|
|
1876
1886
|
const { WU_PATH, STATUS_PATH, BACKLOG_PATH, STAMPS_DIR, docMain, isBranchOnly, derivedWorktree, docForValidation: initialDocForValidation, isDocsOnly, } = pathInfo;
|
|
1877
1887
|
// Capture main checkout path once. process.cwd() may drift later during recovery flows.
|
|
1878
1888
|
const mainCheckoutPath = process.cwd();
|
|
1889
|
+
// Resolve worktree path early so we can detect missing worktree before pre-flight checks
|
|
1890
|
+
const resolvedWorktreePath = derivedWorktree && !isBranchOnly
|
|
1891
|
+
? path.isAbsolute(derivedWorktree)
|
|
1892
|
+
? derivedWorktree
|
|
1893
|
+
: path.resolve(mainCheckoutPath, derivedWorktree)
|
|
1894
|
+
: null;
|
|
1895
|
+
const worktreeExists = resolvedWorktreePath ? existsSync(resolvedWorktreePath) : false;
|
|
1896
|
+
const { allowFallback: allowBranchOnlyFallback, effectiveBranchOnly } = computeBranchOnlyFallback({
|
|
1897
|
+
isBranchOnly,
|
|
1898
|
+
branchOnlyRequested: args.branchOnly,
|
|
1899
|
+
worktreeExists,
|
|
1900
|
+
derivedWorktree,
|
|
1901
|
+
});
|
|
1902
|
+
if (allowBranchOnlyFallback) {
|
|
1903
|
+
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Worktree missing (${resolvedWorktreePath}). Proceeding in branch-only mode because --branch-only was provided.`);
|
|
1904
|
+
}
|
|
1905
|
+
const effectiveDerivedWorktree = effectiveBranchOnly ? null : derivedWorktree;
|
|
1906
|
+
const effectiveWorktreePath = effectiveBranchOnly ? null : resolvedWorktreePath;
|
|
1879
1907
|
// Pre-flight checks (WU-1215: extracted to executePreFlightChecks function)
|
|
1880
1908
|
const preFlightResult = await executePreFlightChecks({
|
|
1881
1909
|
id,
|
|
1882
1910
|
args,
|
|
1883
|
-
isBranchOnly,
|
|
1911
|
+
isBranchOnly: effectiveBranchOnly,
|
|
1884
1912
|
isDocsOnly,
|
|
1885
1913
|
docMain,
|
|
1886
1914
|
docForValidation: initialDocForValidation,
|
|
1887
|
-
derivedWorktree,
|
|
1915
|
+
derivedWorktree: effectiveDerivedWorktree,
|
|
1888
1916
|
});
|
|
1889
1917
|
const title = preFlightResult.title;
|
|
1890
1918
|
// Note: docForValidation is returned but not used after pre-flight checks
|
|
1891
1919
|
// The metadata transaction uses docForUpdate instead
|
|
1892
1920
|
// Step 0: Run gates (WU-1215: extracted to executeGates function)
|
|
1893
|
-
const worktreePath =
|
|
1894
|
-
? path.isAbsolute(derivedWorktree)
|
|
1895
|
-
? derivedWorktree
|
|
1896
|
-
: path.resolve(mainCheckoutPath, derivedWorktree)
|
|
1897
|
-
: null;
|
|
1921
|
+
const worktreePath = effectiveWorktreePath;
|
|
1898
1922
|
// WU-1943: Check if any checkpoints exist for this WU session
|
|
1899
1923
|
// Warn (don't block) if no checkpoints - agent should have been checkpointing periodically
|
|
1900
1924
|
try {
|
|
@@ -1908,9 +1932,16 @@ async function main() {
|
|
|
1908
1932
|
catch {
|
|
1909
1933
|
// Non-blocking: checkpoint check failure should not block wu:done
|
|
1910
1934
|
}
|
|
1911
|
-
await executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath });
|
|
1935
|
+
await executeGates({ id, args, isBranchOnly: effectiveBranchOnly, isDocsOnly, worktreePath });
|
|
1912
1936
|
// Print State HUD for visibility (WU-1215: extracted to printStateHUD function)
|
|
1913
|
-
printStateHUD({
|
|
1937
|
+
printStateHUD({
|
|
1938
|
+
id,
|
|
1939
|
+
docMain,
|
|
1940
|
+
isBranchOnly: effectiveBranchOnly,
|
|
1941
|
+
isDocsOnly,
|
|
1942
|
+
derivedWorktree: effectiveDerivedWorktree,
|
|
1943
|
+
STAMPS_DIR,
|
|
1944
|
+
});
|
|
1914
1945
|
// Step 0.5: Pre-flight validation - run ALL pre-commit hooks BEFORE merge
|
|
1915
1946
|
// This prevents partial completion states where merge succeeds but commit fails
|
|
1916
1947
|
// Validates all 8 gates: secrets, file size, ESLint, Prettier, TypeScript, audit, architecture, tasks
|
|
@@ -1947,7 +1978,7 @@ async function main() {
|
|
|
1947
1978
|
validateStagedFiles,
|
|
1948
1979
|
};
|
|
1949
1980
|
try {
|
|
1950
|
-
if (
|
|
1981
|
+
if (effectiveBranchOnly) {
|
|
1951
1982
|
// Branch-Only mode: merge first, then update metadata on main
|
|
1952
1983
|
// NOTE: Branch-only still uses old rollback mechanism
|
|
1953
1984
|
const branchOnlyContext = {
|
|
@@ -2165,7 +2196,7 @@ async function detectChangedDocPaths(worktreePath, baseBranch) {
|
|
|
2165
2196
|
// Get files changed in this branch vs base
|
|
2166
2197
|
const diff = await git.raw(['diff', '--name-only', baseBranch]);
|
|
2167
2198
|
const changedFiles = diff.split('\n').filter(Boolean);
|
|
2168
|
-
// Filter to docs:
|
|
2199
|
+
// Filter to docs: docs/04-operations/_frameworks/lumenflow/agent/onboarding/, docs/, CLAUDE.md, README.md, *.md in root
|
|
2169
2200
|
const docPatterns = [
|
|
2170
2201
|
/^ai\/onboarding\//,
|
|
2171
2202
|
/^docs\//,
|