@lumenflow/cli 3.5.0 → 3.6.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/config-get.js +61 -20
- package/dist/config-get.js.map +1 -1
- package/dist/config-set.js +469 -70
- package/dist/config-set.js.map +1 -1
- package/dist/init-templates.js +12 -0
- package/dist/init-templates.js.map +1 -1
- package/dist/release.js +193 -223
- package/dist/release.js.map +1 -1
- package/dist/workspace-init.js +35 -3
- package/dist/workspace-init.js.map +1 -1
- package/dist/wu-claim.js +3 -2
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-create.js +74 -10
- package/dist/wu-create.js.map +1 -1
- package/dist/wu-delete.js +3 -3
- package/dist/wu-delete.js.map +1 -1
- package/dist/wu-done-already-merged.js +154 -0
- package/dist/wu-done-already-merged.js.map +1 -0
- package/dist/wu-done-git-ops.js +288 -0
- package/dist/wu-done-git-ops.js.map +1 -0
- package/dist/wu-done-policies.js +266 -0
- package/dist/wu-done-policies.js.map +1 -0
- package/dist/wu-done.js +64 -645
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-edit.js +2 -2
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-repair.js +26 -8
- package/dist/wu-repair.js.map +1 -1
- package/package.json +7 -7
- package/packs/software-delivery/manifest-schema.ts +2 -0
- package/packs/software-delivery/manifest.ts +1 -0
- package/packs/software-delivery/manifest.yaml +1 -0
package/dist/wu-done.js
CHANGED
|
@@ -57,8 +57,6 @@ import { existsSync, readFileSync, mkdirSync, appendFileSync, unlinkSync, statSy
|
|
|
57
57
|
import path from 'node:path';
|
|
58
58
|
// WU-1825: Import from unified code-path-validator (consolidates 3 validators)
|
|
59
59
|
import { validateWUCodePaths } from '@lumenflow/core/code-path-validator';
|
|
60
|
-
import { validateDocsOnly, getAllowedPathsDescription } from '@lumenflow/core/docs-path-validator';
|
|
61
|
-
import { scanLogForViolations, rotateLog } from '@lumenflow/core/commands-logger';
|
|
62
60
|
import { rollbackFiles } from '@lumenflow/core/rollback-utils';
|
|
63
61
|
import { validateInputs, detectModeAndPaths, defaultBranchFrom, runCleanup, validateSpecCompleteness, runPreflightTasksValidation, buildPreflightErrorMessage,
|
|
64
62
|
// WU-1805: Preflight code_paths validation before gates
|
|
@@ -70,10 +68,9 @@ validateTypeVsCodePathsPreflight, buildTypeVsCodePathsErrorMessage, } from '@lum
|
|
|
70
68
|
import { formatPreflightWarnings } from '@lumenflow/core/wu-preflight-validators';
|
|
71
69
|
// WU-1825: validateCodePathsExist moved to unified code-path-validator
|
|
72
70
|
import { validateCodePathsExist } from '@lumenflow/core/code-path-validator';
|
|
73
|
-
import { BRANCHES,
|
|
71
|
+
import { BRANCHES, PATTERNS, DEFAULTS, LOG_PREFIX, EMOJI, GIT, SESSION, WU_STATUS, PKG_MANAGER, SCRIPTS, CLI_FLAGS, FILE_SYSTEM, EXIT_CODES, STRING_LITERALS, MICRO_WORKTREE_OPERATIONS, TELEMETRY_STEPS, SKIP_GATES_REASONS, CHECKPOINT_MESSAGES, ENV_VARS, getWUStatusDisplay,
|
|
74
72
|
// WU-1223: Location types for worktree detection
|
|
75
73
|
CONTEXT_VALIDATION, } from '@lumenflow/core/wu-constants';
|
|
76
|
-
import { isDocumentationType } from '@lumenflow/core/wu-type-helpers';
|
|
77
74
|
import { getDocsOnlyPrefixes, DOCS_ONLY_ROOT_FILES } from '@lumenflow/core';
|
|
78
75
|
import { printGateFailureBox, printStatusPreview } from '@lumenflow/core/wu-done-ui';
|
|
79
76
|
import { ensureOnMain } from '@lumenflow/core/wu-helpers';
|
|
@@ -88,12 +85,14 @@ executeBranchPRCompletion, } from '@lumenflow/core/wu-done-branch-only';
|
|
|
88
85
|
import { executeWorktreeCompletion, autoRebaseBranch } from '@lumenflow/core/wu-done-worktree';
|
|
89
86
|
// WU-1746: Already-merged worktree resilience
|
|
90
87
|
import { detectAlreadyMergedNoWorktree, executeAlreadyMergedCompletion, } from '@lumenflow/core/wu-done-merged-worktree';
|
|
88
|
+
// WU-2211: --already-merged finalize-only mode
|
|
89
|
+
import { verifyCodePathsOnMainHead, executeAlreadyMergedFinalize as executeAlreadyMergedFinalizeFromModule, } from './wu-done-already-merged.js';
|
|
91
90
|
import { checkWUConsistency } from '@lumenflow/core/wu-consistency-checker';
|
|
92
91
|
// WU-1542: Use blocking mode compliance check (replaces non-blocking checkMandatoryAgentsCompliance)
|
|
93
92
|
import { checkMandatoryAgentsComplianceBlocking } from '@lumenflow/core/orchestration-rules';
|
|
94
93
|
import { endSessionForWU } from '@lumenflow/agent/auto-session';
|
|
95
94
|
import { runBackgroundProcessCheck } from '@lumenflow/core/process-detector';
|
|
96
|
-
import { WUStateStore
|
|
95
|
+
import { WUStateStore } from '@lumenflow/core/wu-state-store';
|
|
97
96
|
// WU-1588: INIT-007 memory layer integration
|
|
98
97
|
import { createCheckpoint } from '@lumenflow/memory/checkpoint';
|
|
99
98
|
import { createSignal, loadSignals } from '@lumenflow/memory/signal';
|
|
@@ -108,9 +107,6 @@ import { createPreGatesCheckpoint as createWU1747Checkpoint, markGatesPassed, ca
|
|
|
108
107
|
// WU-1946: Spawn registry for tracking sub-agent spawns
|
|
109
108
|
import { DelegationRegistryStore } from '@lumenflow/core/delegation-registry-store';
|
|
110
109
|
import { DelegationStatus } from '@lumenflow/core/delegation-registry-schema';
|
|
111
|
-
// WU-1999: Exposure validation for UI pairing
|
|
112
|
-
// WU-2022: Feature accessibility validation (blocking)
|
|
113
|
-
import { validateExposure, validateFeatureAccessibility } from '@lumenflow/core/wu-validation';
|
|
114
110
|
import { ensureCleanWorktree } from './wu-done-check.js';
|
|
115
111
|
// WU-1366: Auto cleanup after wu:done success
|
|
116
112
|
// WU-1533: commitCleanupChanges auto-commits dirty state files after cleanup
|
|
@@ -122,6 +118,10 @@ import { markCompletedWUSignalsAsRead } from './hooks/enforcement-generator.js';
|
|
|
122
118
|
import { evaluateMainDirtyMutationGuard } from './hooks/dirty-guard.js';
|
|
123
119
|
// WU-1474: Decay policy invocation during completion lifecycle
|
|
124
120
|
import { runDecayOnDone } from './wu-done-decay.js';
|
|
121
|
+
import { enforceSpawnProvenanceForDone, enforceWuBriefEvidenceForDone, printExposureWarnings, validateAccessibilityOrDie, validateDocsOnlyFlag, } from './wu-done-policies.js';
|
|
122
|
+
import { detectParallelCompletions, ensureNoAutoStagedOrNoop, runTripwireCheck, validateBranchOnlyMode, validateStagedFiles, } from './wu-done-git-ops.js';
|
|
123
|
+
export { buildGatesCommand, buildMissingSpawnPickupEvidenceMessage, buildMissingSpawnProvenanceMessage, buildMissingWuBriefEvidenceMessage, enforceSpawnProvenanceForDone, enforceWuBriefEvidenceForDone, hasSpawnPickupEvidence, printExposureWarnings, shouldEnforceSpawnProvenance, shouldEnforceWuBriefEvidence, validateAccessibilityOrDie, validateDocsOnlyFlag, } from './wu-done-policies.js';
|
|
124
|
+
export { isBranchAlreadyMerged } from './wu-done-git-ops.js';
|
|
125
125
|
// WU-1588: Memory layer constants
|
|
126
126
|
const MEMORY_SIGNAL_TYPES = {
|
|
127
127
|
WU_COMPLETION: 'wu_completion',
|
|
@@ -253,101 +253,6 @@ async function validateClaimMetadataBeforeGates(id, worktreePath, yamlStatus) {
|
|
|
253
253
|
` pnpm wu:done --id ${id}\n\n` +
|
|
254
254
|
`See: https://lumenflow.dev/reference/troubleshooting-wu-done/ for more recovery options.`);
|
|
255
255
|
}
|
|
256
|
-
export function printExposureWarnings(wu, options = {}) {
|
|
257
|
-
// Validate exposure
|
|
258
|
-
const result = validateExposure(wu, { skipExposureCheck: options.skipExposureCheck });
|
|
259
|
-
// Print warnings if present
|
|
260
|
-
if (result.warnings.length > 0) {
|
|
261
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-1999: Exposure validation warnings:`);
|
|
262
|
-
for (const warning of result.warnings) {
|
|
263
|
-
console.log(`${LOG_PREFIX.DONE} ${warning}`);
|
|
264
|
-
}
|
|
265
|
-
console.log(`${LOG_PREFIX.DONE} These are non-blocking warnings. ` +
|
|
266
|
-
`To skip, use --skip-exposure-check flag.\n`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
export function validateAccessibilityOrDie(wu, options = {}) {
|
|
270
|
-
const result = validateFeatureAccessibility(wu, {
|
|
271
|
-
skipAccessibilityCheck: options.skipAccessibilityCheck,
|
|
272
|
-
});
|
|
273
|
-
if (!result.valid) {
|
|
274
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.FAILURE} WU-2022: Feature accessibility validation failed`);
|
|
275
|
-
die(`❌ FEATURE ACCESSIBILITY VALIDATION FAILED (WU-2022)\n\n` +
|
|
276
|
-
`Cannot complete wu:done - UI feature accessibility not verified.\n\n` +
|
|
277
|
-
`${result.errors.join('\n\n')}\n\n` +
|
|
278
|
-
`This gate prevents "orphaned code" - features that exist but users cannot access.`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
export function validateDocsOnlyFlag(wu, args) {
|
|
282
|
-
// If --docs-only flag is not used, no validation needed
|
|
283
|
-
if (!args.docsOnly) {
|
|
284
|
-
return { valid: true, errors: [] };
|
|
285
|
-
}
|
|
286
|
-
const wuId = wu.id || 'unknown';
|
|
287
|
-
const exposure = wu.exposure;
|
|
288
|
-
const type = wu.type;
|
|
289
|
-
const codePaths = wu.code_paths;
|
|
290
|
-
// Check 1: exposure is 'documentation'
|
|
291
|
-
if (exposure === WU_EXPOSURE.DOCUMENTATION) {
|
|
292
|
-
return { valid: true, errors: [] };
|
|
293
|
-
}
|
|
294
|
-
// Check 2: type is 'documentation'
|
|
295
|
-
if (isDocumentationType(type)) {
|
|
296
|
-
return { valid: true, errors: [] };
|
|
297
|
-
}
|
|
298
|
-
// Check 3: all code_paths are documentation paths
|
|
299
|
-
const docsOnlyPrefixes = getDocsOnlyPrefixes().map((prefix) => prefix.toLowerCase());
|
|
300
|
-
const isDocsPath = (p) => {
|
|
301
|
-
const path = p.trim().toLowerCase();
|
|
302
|
-
// Check docs prefixes
|
|
303
|
-
for (const prefix of docsOnlyPrefixes) {
|
|
304
|
-
if (path.startsWith(prefix))
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
// Check markdown files
|
|
308
|
-
if (path.endsWith('.md'))
|
|
309
|
-
return true;
|
|
310
|
-
// Check root file patterns
|
|
311
|
-
for (const pattern of DOCS_ONLY_ROOT_FILES) {
|
|
312
|
-
if (path.startsWith(pattern))
|
|
313
|
-
return true;
|
|
314
|
-
}
|
|
315
|
-
return false;
|
|
316
|
-
};
|
|
317
|
-
if (codePaths && Array.isArray(codePaths) && codePaths.length > 0) {
|
|
318
|
-
const allDocsOnly = codePaths.every((p) => typeof p === 'string' && isDocsPath(p));
|
|
319
|
-
if (allDocsOnly) {
|
|
320
|
-
return { valid: true, errors: [] };
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
// Validation failed - provide clear error message
|
|
324
|
-
const currentExposure = exposure || 'not set';
|
|
325
|
-
const currentType = type || 'not set';
|
|
326
|
-
return {
|
|
327
|
-
valid: false,
|
|
328
|
-
errors: [
|
|
329
|
-
`--docs-only flag used on ${wuId} but WU is not documentation-focused.\n\n` +
|
|
330
|
-
`Current exposure: ${currentExposure}\n` +
|
|
331
|
-
`Current type: ${currentType}\n\n` +
|
|
332
|
-
`--docs-only requires one of:\n` +
|
|
333
|
-
` 1. exposure: documentation\n` +
|
|
334
|
-
` 2. type: documentation\n` +
|
|
335
|
-
` 3. All code_paths under configured docs prefixes (${docsOnlyPrefixes.join(', ')}), or *.md files\n\n` +
|
|
336
|
-
`To fix, either:\n` +
|
|
337
|
-
` - Remove --docs-only flag and run full gates\n` +
|
|
338
|
-
` - Change WU exposure to 'documentation' if this is truly a docs-only change`,
|
|
339
|
-
],
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
export function buildGatesCommand(options) {
|
|
343
|
-
const { docsOnly = false, isDocsOnly = false } = options;
|
|
344
|
-
// Use docs-only gates if either explicit flag or auto-detected
|
|
345
|
-
const shouldUseDocsOnly = docsOnly || isDocsOnly;
|
|
346
|
-
if (shouldUseDocsOnly) {
|
|
347
|
-
return `${PKG_MANAGER} ${SCRIPTS.GATES} -- ${CLI_FLAGS.DOCS_ONLY}`;
|
|
348
|
-
}
|
|
349
|
-
return `${PKG_MANAGER} ${SCRIPTS.GATES}`;
|
|
350
|
-
}
|
|
351
256
|
async function _assertWorktreeWUInProgressInStateStore(id, worktreePath) {
|
|
352
257
|
const resolvedWorktreePath = path.resolve(worktreePath);
|
|
353
258
|
const stateDir = resolveStateDir(resolvedWorktreePath);
|
|
@@ -456,159 +361,6 @@ async function checkInboxForRecentSignals(id, baseDir = process.cwd()) {
|
|
|
456
361
|
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not check inbox for signals: ${getErrorMessage(err)}`);
|
|
457
362
|
}
|
|
458
363
|
}
|
|
459
|
-
/**
|
|
460
|
-
* Enforce wu:brief evidence for feature and bug WUs.
|
|
461
|
-
*/
|
|
462
|
-
export function shouldEnforceWuBriefEvidence(doc) {
|
|
463
|
-
return doc.type === WU_TYPES.FEATURE || doc.type === WU_TYPES.BUG;
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Build remediation guidance when wu:brief evidence is missing.
|
|
467
|
-
*/
|
|
468
|
-
export function buildMissingWuBriefEvidenceMessage(id) {
|
|
469
|
-
return (`Missing wu:brief evidence for ${id}.\n\n` +
|
|
470
|
-
`Completion policy requires an auditable wu:brief execution record for feature/bug WUs.\n\n` +
|
|
471
|
-
`Fix options:\n` +
|
|
472
|
-
` 1. Run wu:brief in the claimed workspace:\n` +
|
|
473
|
-
` pnpm wu:brief --id ${id}\n` +
|
|
474
|
-
` 2. Retry completion:\n` +
|
|
475
|
-
` pnpm wu:done --id ${id}\n` +
|
|
476
|
-
` 3. Legacy/manual override (audited):\n` +
|
|
477
|
-
` pnpm wu:done --id ${id} --force`);
|
|
478
|
-
}
|
|
479
|
-
function buildWuBriefEvidenceReadFailureMessage(id, stateDir, error) {
|
|
480
|
-
return (`Could not verify wu:brief evidence for ${id}.\n\n` +
|
|
481
|
-
`State path: ${stateDir}\n` +
|
|
482
|
-
`Error: ${getErrorMessage(error)}\n\n` +
|
|
483
|
-
`Fix options:\n` +
|
|
484
|
-
` 1. Repair/restore state store, then rerun wu:done\n` +
|
|
485
|
-
` 2. Use --force for audited override when recovery is not possible`);
|
|
486
|
-
}
|
|
487
|
-
export async function enforceWuBriefEvidenceForDone(id, doc, options = {}) {
|
|
488
|
-
if (!shouldEnforceWuBriefEvidence(doc)) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
const baseDir = options.baseDir ?? process.cwd();
|
|
492
|
-
const force = options.force === true;
|
|
493
|
-
const stateDir = resolveStateDir(baseDir);
|
|
494
|
-
const getBriefEvidenceFn = options.getBriefEvidenceFn ?? getLatestWuBriefEvidence;
|
|
495
|
-
const blocker = options.blocker ?? ((message) => die(message));
|
|
496
|
-
const warn = options.warn ?? console.warn;
|
|
497
|
-
let evidence;
|
|
498
|
-
try {
|
|
499
|
-
evidence = await getBriefEvidenceFn(stateDir, id);
|
|
500
|
-
}
|
|
501
|
-
catch (error) {
|
|
502
|
-
if (!force) {
|
|
503
|
-
blocker(buildWuBriefEvidenceReadFailureMessage(id, stateDir, error));
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-2132: brief evidence verification failed for ${id}, override accepted via --force`);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
if (evidence) {
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
if (!force) {
|
|
513
|
-
blocker(buildMissingWuBriefEvidenceMessage(id));
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-2132: brief evidence override accepted for ${id} via --force`);
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Returns true when completion should enforce spawn provenance.
|
|
520
|
-
* Initiative-linked WUs are expected to carry machine-verifiable spawn lineage.
|
|
521
|
-
*/
|
|
522
|
-
export function shouldEnforceSpawnProvenance(doc) {
|
|
523
|
-
return typeof doc?.initiative === 'string' && doc.initiative.trim().length > 0;
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Build actionable remediation guidance for missing spawn provenance.
|
|
527
|
-
*/
|
|
528
|
-
export function buildMissingSpawnProvenanceMessage(id, initiativeId) {
|
|
529
|
-
return (`Missing spawn provenance for initiative-governed WU ${id} (${initiativeId}).\n\n` +
|
|
530
|
-
`This completion path enforces auditable delegation lineage for initiative work.\n\n` +
|
|
531
|
-
`Fix options:\n` +
|
|
532
|
-
` 1. Re-run with --force for an audited override (legacy/manual workflow)\n` +
|
|
533
|
-
` 2. Register spawn lineage before completion (preferred):\n` +
|
|
534
|
-
` pnpm wu:delegate --id ${id} --parent-wu WU-XXXX --client codex-cli\n\n` +
|
|
535
|
-
`Then retry: pnpm wu:done --id ${id}`);
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Build actionable remediation guidance for intent-only spawn provenance
|
|
539
|
-
* (delegation intent exists but claim-time pickup evidence is missing).
|
|
540
|
-
*/
|
|
541
|
-
export function buildMissingSpawnPickupEvidenceMessage(id, initiativeId) {
|
|
542
|
-
return (`Missing pickup evidence for initiative-governed WU ${id} (${initiativeId}).\n\n` +
|
|
543
|
-
`Delegation intent exists, but this WU has no claim-time pickup handshake.\n` +
|
|
544
|
-
`Completion policy requires both intent and pickup evidence.\n\n` +
|
|
545
|
-
`Fix options:\n` +
|
|
546
|
-
` 1. Re-run with --force for an audited override (legacy/manual claim)\n` +
|
|
547
|
-
` 2. Ensure future delegated work is picked up via wu:claim (records handshake automatically)\n\n` +
|
|
548
|
-
`Then retry: pnpm wu:done --id ${id}`);
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Returns true when spawn provenance includes claim-time pickup evidence.
|
|
552
|
-
*/
|
|
553
|
-
export function hasSpawnPickupEvidence(spawnEntry) {
|
|
554
|
-
const pickedUpAt = typeof spawnEntry?.pickedUpAt === 'string' && spawnEntry.pickedUpAt.trim().length > 0
|
|
555
|
-
? spawnEntry.pickedUpAt
|
|
556
|
-
: '';
|
|
557
|
-
const pickedUpBy = typeof spawnEntry?.pickedUpBy === 'string' && spawnEntry.pickedUpBy.trim().length > 0
|
|
558
|
-
? spawnEntry.pickedUpBy
|
|
559
|
-
: '';
|
|
560
|
-
return pickedUpAt.length > 0 && pickedUpBy.length > 0;
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Record forced spawn-provenance bypass in memory signals for auditability.
|
|
564
|
-
*/
|
|
565
|
-
async function recordSpawnProvenanceOverride(id, doc, baseDir = process.cwd()) {
|
|
566
|
-
try {
|
|
567
|
-
const initiativeId = typeof doc?.initiative === 'string' ? doc.initiative.trim() : 'unknown';
|
|
568
|
-
const lane = typeof doc?.lane === 'string' ? doc.lane : undefined;
|
|
569
|
-
const result = await createSignal(baseDir, {
|
|
570
|
-
message: `spawn-provenance override used for ${id} in ${initiativeId} via --force`,
|
|
571
|
-
wuId: id,
|
|
572
|
-
lane,
|
|
573
|
-
});
|
|
574
|
-
if (result.success) {
|
|
575
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Spawn-provenance override recorded (${result.signal.id})`);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
catch (err) {
|
|
579
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not record spawn-provenance override: ${getErrorMessage(err)}`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Enforce spawn provenance policy for initiative-governed WUs before completion.
|
|
584
|
-
*/
|
|
585
|
-
export async function enforceSpawnProvenanceForDone(id, doc, options = {}) {
|
|
586
|
-
if (!shouldEnforceSpawnProvenance(doc)) {
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
const initiativeId = typeof doc.initiative === 'string' && doc.initiative.trim() ? doc.initiative.trim() : 'unknown';
|
|
590
|
-
const baseDir = options.baseDir ?? process.cwd();
|
|
591
|
-
const force = options.force === true;
|
|
592
|
-
const store = new DelegationRegistryStore(resolveStateDir(baseDir));
|
|
593
|
-
await store.load();
|
|
594
|
-
const spawnEntry = store.getByTarget(id);
|
|
595
|
-
if (!spawnEntry) {
|
|
596
|
-
if (!force) {
|
|
597
|
-
die(buildMissingSpawnProvenanceMessage(id, initiativeId));
|
|
598
|
-
}
|
|
599
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-1599: spawn provenance override accepted for ${id} (${initiativeId}) via --force`);
|
|
600
|
-
await recordSpawnProvenanceOverride(id, doc, baseDir);
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
if (hasSpawnPickupEvidence(spawnEntry)) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
if (!force) {
|
|
607
|
-
die(buildMissingSpawnPickupEvidenceMessage(id, initiativeId));
|
|
608
|
-
}
|
|
609
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-1605: pickup evidence override accepted for ${id} (${initiativeId}) via --force`);
|
|
610
|
-
await recordSpawnProvenanceOverride(id, doc, baseDir);
|
|
611
|
-
}
|
|
612
364
|
/**
|
|
613
365
|
* WU-1946: Update spawn registry on WU completion.
|
|
614
366
|
* Non-blocking wrapper - failures logged as warnings.
|
|
@@ -668,38 +420,6 @@ export function normalizeUsername(value) {
|
|
|
668
420
|
const username = atIndex > 0 ? str.slice(0, atIndex) : str;
|
|
669
421
|
return username.toLowerCase();
|
|
670
422
|
}
|
|
671
|
-
/**
|
|
672
|
-
* WU-1234: Detect if branch is already merged to main
|
|
673
|
-
* Checks if branch tip is an ancestor of main HEAD (i.e., already merged).
|
|
674
|
-
* This prevents merge loops when code was merged via emergency fix or manual merge.
|
|
675
|
-
*
|
|
676
|
-
* @param {string} branch - Lane branch name
|
|
677
|
-
* @returns {Promise<boolean>} True if branch is already merged to main
|
|
678
|
-
*/
|
|
679
|
-
export async function isBranchAlreadyMerged(branch) {
|
|
680
|
-
try {
|
|
681
|
-
const gitAdapter = getGitForCwd();
|
|
682
|
-
const branchTip = (await gitAdapter.getCommitHash(branch)).trim();
|
|
683
|
-
const mergeBase = (await gitAdapter.mergeBase(BRANCHES.MAIN, branch)).trim();
|
|
684
|
-
const mainHead = (await gitAdapter.getCommitHash(BRANCHES.MAIN)).trim();
|
|
685
|
-
// Branch is already merged if:
|
|
686
|
-
// 1. Branch tip equals merge-base (branch has been rebased/merged onto main)
|
|
687
|
-
// 2. Branch tip is an ancestor of main HEAD
|
|
688
|
-
if (branchTip === mergeBase) {
|
|
689
|
-
// Emergency fix Session 2: Use GIT.SHA_SHORT_LENGTH constant
|
|
690
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Branch ${branch} is already merged to main\n` +
|
|
691
|
-
` Branch tip: ${branchTip.substring(0, GIT.SHA_SHORT_LENGTH)}\n` +
|
|
692
|
-
` Merge-base: ${mergeBase.substring(0, GIT.SHA_SHORT_LENGTH)}\n` +
|
|
693
|
-
` Main HEAD: ${mainHead.substring(0, GIT.SHA_SHORT_LENGTH)}`);
|
|
694
|
-
return true;
|
|
695
|
-
}
|
|
696
|
-
return false;
|
|
697
|
-
}
|
|
698
|
-
catch (e) {
|
|
699
|
-
console.warn(`${LOG_PREFIX.DONE} Could not check if branch is already merged: ${getErrorMessage(e)}`);
|
|
700
|
-
return false;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
423
|
// WU-1281: isDocsOnlyByPaths removed - use shouldSkipWebTests from path-classifiers.ts
|
|
704
424
|
// The validators already use shouldSkipWebTests via detectDocsOnlyByPaths wrapper.
|
|
705
425
|
// Keeping the export for backward compatibility but re-exporting the canonical function.
|
|
@@ -757,278 +477,6 @@ function getCommitHeaderLimit() {
|
|
|
757
477
|
}
|
|
758
478
|
}
|
|
759
479
|
// ensureOnMain() moved to wu-helpers.ts (WU-1256)
|
|
760
|
-
/**
|
|
761
|
-
* Ensure working tree is clean before wu:done operations.
|
|
762
|
-
*
|
|
763
|
-
* Prevents multi-agent data loss: If uncommitted files exist in main
|
|
764
|
-
* checkout, wu:done operations may fail mid-workflow. Agents then
|
|
765
|
-
* automatically "clean up" by running git reset/clean, destroying
|
|
766
|
-
* other agents' uncommitted work.
|
|
767
|
-
*
|
|
768
|
-
* This check HALTS wu:done immediately and guides the agent to verify
|
|
769
|
-
* ownership before proceeding.
|
|
770
|
-
*
|
|
771
|
-
* Context: WU-635 (multi-agent coordination)
|
|
772
|
-
* See: CLAUDE.md §2.2
|
|
773
|
-
*/
|
|
774
|
-
async function _ensureCleanWorkingTree() {
|
|
775
|
-
const status = await getGitForCwd().getStatus();
|
|
776
|
-
if (status.trim()) {
|
|
777
|
-
die(`Working tree is not clean. Cannot proceed with wu:done.\n\n` +
|
|
778
|
-
`Uncommitted changes in main checkout:\n${status}\n\n` +
|
|
779
|
-
`⚠️ CRITICAL: These may be another agent's work!\n\n` +
|
|
780
|
-
`Before proceeding:\n` +
|
|
781
|
-
`1. Check if these are YOUR changes (forgot to commit in main)\n` +
|
|
782
|
-
` → If yes: Commit them now, then retry wu:done\n\n` +
|
|
783
|
-
`2. Check if these are ANOTHER AGENT's changes\n` +
|
|
784
|
-
` → If yes: STOP. Coordinate with user before proceeding\n` +
|
|
785
|
-
` → NEVER remove another agent's uncommitted work\n\n` +
|
|
786
|
-
`Multi-agent coordination: See CLAUDE.md §2.2\n\n` +
|
|
787
|
-
`Common causes:\n` +
|
|
788
|
-
` - You forgot to commit changes before claiming a different WU\n` +
|
|
789
|
-
` - Another agent is actively working in main checkout\n` +
|
|
790
|
-
` - Leftover changes from previous session`);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Extract completed WU IDs from git log output.
|
|
795
|
-
* @param {string} logOutput - Git log output (one commit per line)
|
|
796
|
-
* @param {string} currentId - Current WU ID to exclude
|
|
797
|
-
* @returns {string[]} Array of completed WU IDs
|
|
798
|
-
*/
|
|
799
|
-
function extractCompletedWUIds(logOutput, currentId) {
|
|
800
|
-
const wuPattern = /wu\((wu-\d+)\):/gi;
|
|
801
|
-
const seenIds = new Set();
|
|
802
|
-
const completedWUs = [];
|
|
803
|
-
for (const line of logOutput.split(STRING_LITERALS.NEWLINE)) {
|
|
804
|
-
// Only process "done" commits
|
|
805
|
-
if (!line.toLowerCase().includes('done'))
|
|
806
|
-
continue;
|
|
807
|
-
let match;
|
|
808
|
-
while ((match = wuPattern.exec(line)) !== null) {
|
|
809
|
-
const wuId = match[1].toUpperCase();
|
|
810
|
-
// Skip current WU and duplicates
|
|
811
|
-
if (wuId !== currentId && !seenIds.has(wuId)) {
|
|
812
|
-
seenIds.add(wuId);
|
|
813
|
-
completedWUs.push(wuId);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
return completedWUs;
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* Build warning message for parallel completions.
|
|
821
|
-
*/
|
|
822
|
-
function buildParallelWarning(id, completedWUs, baselineSha, currentSha) {
|
|
823
|
-
const wuList = completedWUs.map((wu) => ` • ${wu}`).join(STRING_LITERALS.NEWLINE);
|
|
824
|
-
return `
|
|
825
|
-
${EMOJI.WARNING} PARALLEL COMPLETIONS DETECTED ${EMOJI.WARNING}
|
|
826
|
-
|
|
827
|
-
The following WUs were completed and merged to main since you claimed ${id}:
|
|
828
|
-
|
|
829
|
-
${wuList}
|
|
830
|
-
|
|
831
|
-
This may cause rebase conflicts when wu:done attempts to merge.
|
|
832
|
-
|
|
833
|
-
Options:
|
|
834
|
-
1. Proceed anyway - rebase will attempt to resolve conflicts
|
|
835
|
-
2. Abort and manually rebase: git fetch origin main && git rebase origin/main
|
|
836
|
-
3. Check if other completed WUs touched the same files
|
|
837
|
-
|
|
838
|
-
Baseline: ${baselineSha.substring(0, 8)}
|
|
839
|
-
Current: ${currentSha.substring(0, 8)}
|
|
840
|
-
`;
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* WU-1382: Detect parallel WU completions since claim time.
|
|
844
|
-
*
|
|
845
|
-
* When multiple agents work in parallel, one may complete a WU and merge to main
|
|
846
|
-
* while another is still working. This function detects such completions early,
|
|
847
|
-
* before wu:done attempts the merge, allowing the agent to decide whether to
|
|
848
|
-
* proceed (with potential rebase conflicts) or abort.
|
|
849
|
-
*
|
|
850
|
-
* @param {string} id - Current WU ID
|
|
851
|
-
* @param {object} doc - WU YAML document (from worktree or main)
|
|
852
|
-
* @returns {Promise<{hasParallelCompletions: boolean, completedWUs: string[], warning: string|null}>}
|
|
853
|
-
*/
|
|
854
|
-
async function detectParallelCompletions(id, doc) {
|
|
855
|
-
const noParallel = {
|
|
856
|
-
hasParallelCompletions: false,
|
|
857
|
-
completedWUs: [],
|
|
858
|
-
warning: null,
|
|
859
|
-
};
|
|
860
|
-
const baselineSha = doc.baseline_main_sha;
|
|
861
|
-
// If no baseline recorded (legacy WU), skip detection
|
|
862
|
-
if (!baselineSha) {
|
|
863
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} No baseline_main_sha recorded (legacy WU) - skipping parallel detection`);
|
|
864
|
-
return noParallel;
|
|
865
|
-
}
|
|
866
|
-
try {
|
|
867
|
-
const gitAdapter = getGitForCwd();
|
|
868
|
-
await gitAdapter.fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
869
|
-
const currentSha = (await gitAdapter.getCommitHash(`${REMOTES.ORIGIN}/${BRANCHES.MAIN}`)).trim();
|
|
870
|
-
if (currentSha === baselineSha) {
|
|
871
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} No parallel completions detected (main unchanged since claim)`);
|
|
872
|
-
return noParallel;
|
|
873
|
-
}
|
|
874
|
-
const logOutput = await gitAdapter.raw([
|
|
875
|
-
'log',
|
|
876
|
-
'--oneline',
|
|
877
|
-
'--grep=^wu(wu-',
|
|
878
|
-
`${baselineSha}..${REMOTES.ORIGIN}/${BRANCHES.MAIN}`,
|
|
879
|
-
]);
|
|
880
|
-
if (!logOutput?.trim()) {
|
|
881
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Main advanced since claim but no WU completions detected`);
|
|
882
|
-
return noParallel;
|
|
883
|
-
}
|
|
884
|
-
const completedWUs = extractCompletedWUIds(logOutput, id);
|
|
885
|
-
if (completedWUs.length === 0) {
|
|
886
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Main advanced since claim but no other WU completions`);
|
|
887
|
-
return noParallel;
|
|
888
|
-
}
|
|
889
|
-
const warning = buildParallelWarning(id, completedWUs, baselineSha, currentSha);
|
|
890
|
-
return { hasParallelCompletions: true, completedWUs, warning };
|
|
891
|
-
}
|
|
892
|
-
catch (err) {
|
|
893
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not detect parallel completions: ${getErrorMessage(err)}`);
|
|
894
|
-
return noParallel;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Ensure main branch is up-to-date with origin before merge operations.
|
|
899
|
-
*
|
|
900
|
-
* Prevents coordination failures when Agent A pushes to main while Agent B
|
|
901
|
-
* is working. Without this check, Agent B's wu:done would fail with cryptic
|
|
902
|
-
* fast-forward errors when trying to merge.
|
|
903
|
-
*
|
|
904
|
-
* Context: WU-705 (fix agent coordination failures)
|
|
905
|
-
* See: CLAUDE.md §2.7
|
|
906
|
-
*/
|
|
907
|
-
async function ensureMainUpToDate() {
|
|
908
|
-
console.log(`${LOG_PREFIX.DONE} Checking if main is up-to-date with origin...`);
|
|
909
|
-
try {
|
|
910
|
-
// Fetch latest without merging
|
|
911
|
-
const gitAdapter = getGitForCwd();
|
|
912
|
-
await gitAdapter.fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
913
|
-
const localMain = await gitAdapter.getCommitHash(BRANCHES.MAIN);
|
|
914
|
-
const remoteMain = await gitAdapter.getCommitHash(`${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
|
|
915
|
-
if (localMain !== remoteMain) {
|
|
916
|
-
const behind = await gitAdapter.revList([
|
|
917
|
-
'--count',
|
|
918
|
-
`${BRANCHES.MAIN}..${REMOTES.ORIGIN}/${BRANCHES.MAIN}`,
|
|
919
|
-
]);
|
|
920
|
-
const ahead = await gitAdapter.revList([
|
|
921
|
-
'--count',
|
|
922
|
-
`${REMOTES.ORIGIN}/${BRANCHES.MAIN}..${BRANCHES.MAIN}`,
|
|
923
|
-
]);
|
|
924
|
-
die(`Main branch is out of sync with ${REMOTES.ORIGIN}.\n\n` +
|
|
925
|
-
`Local ${BRANCHES.MAIN} is ${behind} commits behind and ${ahead} commits ahead of ${REMOTES.ORIGIN}/${BRANCHES.MAIN}.\n\n` +
|
|
926
|
-
`Update main before running wu:done:\n` +
|
|
927
|
-
` git pull origin main\n` +
|
|
928
|
-
` # Then retry:\n` +
|
|
929
|
-
` pnpm wu:done --id ${process.argv.find((a) => a.startsWith('WU-')) || 'WU-XXX'}\n\n` +
|
|
930
|
-
`This prevents fast-forward merge failures during wu:done completion.\n\n` +
|
|
931
|
-
`Why this happens:\n` +
|
|
932
|
-
` - Another agent completed a WU and pushed to main\n` +
|
|
933
|
-
` - Your main checkout is now behind origin/main\n` +
|
|
934
|
-
` - The fast-forward merge will fail without updating first\n\n` +
|
|
935
|
-
`Multi-agent coordination: See CLAUDE.md §2.7`);
|
|
936
|
-
}
|
|
937
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Main is up-to-date with origin`);
|
|
938
|
-
}
|
|
939
|
-
catch (err) {
|
|
940
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not verify main sync: ${getErrorMessage(err)}`);
|
|
941
|
-
console.warn(`${LOG_PREFIX.DONE} Proceeding anyway (network issue or no remote)`);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
/**
|
|
945
|
-
* Tripwire check: Scan commands log for violations (WU-630 detective layer)
|
|
946
|
-
*
|
|
947
|
-
* Scans .lumenflow/commands.log for destructive git commands executed during
|
|
948
|
-
* this agent session. If violations are found, aborts wu:done and displays
|
|
949
|
-
* remediation guidance.
|
|
950
|
-
*
|
|
951
|
-
* This is defense-in-depth: catches violations even if git shim was bypassed
|
|
952
|
-
* by calling /usr/bin/git directly or if PATH was not set up correctly.
|
|
953
|
-
*
|
|
954
|
-
* Context: WU-630 (detective layer, Layer 3 of 4)
|
|
955
|
-
* See: https://lumenflow.dev/reference/playbook/ §4.6
|
|
956
|
-
*/
|
|
957
|
-
function runTripwireCheck() {
|
|
958
|
-
const violations = scanLogForViolations();
|
|
959
|
-
if (violations.length === 0) {
|
|
960
|
-
return; // All clear
|
|
961
|
-
}
|
|
962
|
-
// Violations detected - format error message with remediation
|
|
963
|
-
console.error('\n⛔ VIOLATION DETECTED: Destructive Git Commands on Main\n');
|
|
964
|
-
console.error('The following forbidden git commands were executed during this session:\n');
|
|
965
|
-
violations.forEach((v, i) => {
|
|
966
|
-
console.error(` ${i + 1}. ${v.command}`);
|
|
967
|
-
console.error(` Branch: ${v.branch}`);
|
|
968
|
-
console.error(` Worktree: ${v.worktree}`);
|
|
969
|
-
console.error(` Time: ${v.timestamp}\n`);
|
|
970
|
-
});
|
|
971
|
-
console.error(`\nTotal: ${violations.length} violations\n`);
|
|
972
|
-
// Remediation guidance based on violation type
|
|
973
|
-
console.error("⚠️ CRITICAL: These commands may have destroyed other agents' work!\n");
|
|
974
|
-
console.error('Remediation Steps:\n');
|
|
975
|
-
const hasReset = violations.some((v) => v.command.includes('reset --hard'));
|
|
976
|
-
const hasStash = violations.some((v) => v.command.includes('stash'));
|
|
977
|
-
const hasClean = violations.some((v) => v.command.includes('clean'));
|
|
978
|
-
if (hasReset) {
|
|
979
|
-
console.error('📋 git reset --hard detected:');
|
|
980
|
-
console.error(' 1. Check git reflog to recover lost commits:');
|
|
981
|
-
console.error(' git reflog');
|
|
982
|
-
console.error(' git reset --hard HEAD@{N} (where N is the commit before reset)');
|
|
983
|
-
console.error(' 2. If reflog shows lost work, restore it immediately\n');
|
|
984
|
-
}
|
|
985
|
-
if (hasStash) {
|
|
986
|
-
console.error('📋 git stash detected:');
|
|
987
|
-
console.error(" 1. Check if stash contains other agents' work:");
|
|
988
|
-
console.error(' git stash list');
|
|
989
|
-
console.error(' git stash show -p stash@{0}');
|
|
990
|
-
console.error(' 2. If stash contains work, pop it back:');
|
|
991
|
-
console.error(' git stash pop\n');
|
|
992
|
-
}
|
|
993
|
-
if (hasClean) {
|
|
994
|
-
console.error('📋 git clean detected:');
|
|
995
|
-
console.error(' 1. Deleted files may not be recoverable');
|
|
996
|
-
console.error(' 2. Check git status for remaining untracked files');
|
|
997
|
-
console.error(' 3. Escalate to human if critical files were deleted\n');
|
|
998
|
-
}
|
|
999
|
-
console.error('📖 See detailed recovery steps:');
|
|
1000
|
-
console.error(' https://lumenflow.dev/reference/playbook/ §4.6\n');
|
|
1001
|
-
console.error('🚫 DO NOT proceed with wu:done until violations are remediated.\n');
|
|
1002
|
-
console.error('Fix violations first, then retry wu:done.\n');
|
|
1003
|
-
// Also rotate log (cleanup old entries)
|
|
1004
|
-
rotateLog();
|
|
1005
|
-
process.exit(EXIT_CODES.ERROR);
|
|
1006
|
-
}
|
|
1007
|
-
async function listStaged(gitAdapter) {
|
|
1008
|
-
// WU-1541: Use explicit gitAdapter if provided, otherwise fall back to getGitForCwd()
|
|
1009
|
-
// WU-1235: getGitForCwd() captures current directory (legacy behavior)
|
|
1010
|
-
const gitCwd = gitAdapter ?? getGitForCwd();
|
|
1011
|
-
const raw = await gitCwd.raw(['diff', '--cached', '--name-only']);
|
|
1012
|
-
return raw ? raw.split(/\r?\n/).filter(Boolean) : [];
|
|
1013
|
-
}
|
|
1014
|
-
// In --no-auto mode, allow a safe no-op: if NONE of the expected files are staged,
|
|
1015
|
-
// treat as already-synchronised and continue. If SOME are staged and SOME missing,
|
|
1016
|
-
// still fail with guidance.
|
|
1017
|
-
async function ensureNoAutoStagedOrNoop(paths) {
|
|
1018
|
-
const staged = await listStaged();
|
|
1019
|
-
const isStaged = (p) => staged.some((name) => name === p || name.startsWith(`${p}/`));
|
|
1020
|
-
const definedPaths = paths.filter((p) => typeof p === 'string' && p.length > 0);
|
|
1021
|
-
const present = definedPaths.filter((p) => isStaged(p));
|
|
1022
|
-
if (present.length === 0) {
|
|
1023
|
-
console.log(`${LOG_PREFIX.DONE} No staged changes detected for --no-auto; treating as no-op finalisation (repo already in done state)`);
|
|
1024
|
-
return { noop: true };
|
|
1025
|
-
}
|
|
1026
|
-
const missing = definedPaths.filter((p) => !isStaged(p));
|
|
1027
|
-
if (missing.length > 0) {
|
|
1028
|
-
die(`Stage updates for: ${missing.join(', ')}`);
|
|
1029
|
-
}
|
|
1030
|
-
return { noop: false };
|
|
1031
|
-
}
|
|
1032
480
|
export function emitTelemetry(event) {
|
|
1033
481
|
const logPath = path.join('.lumenflow', 'flow.log');
|
|
1034
482
|
const logDir = path.dirname(logPath);
|
|
@@ -1201,56 +649,6 @@ async function runGatesInWorktree(worktreePath, id, options = {}) {
|
|
|
1201
649
|
die(`Gates failed in ${worktreePath}. Fix issues in the worktree and try again.`);
|
|
1202
650
|
}
|
|
1203
651
|
}
|
|
1204
|
-
async function validateStagedFiles(id, isDocsOnly = false, gitAdapter, options = {}) {
|
|
1205
|
-
// WU-1541: Accept optional gitAdapter to avoid process.chdir dependency
|
|
1206
|
-
const staged = await listStaged(gitAdapter);
|
|
1207
|
-
// WU-1311: Use config-based paths instead of hardcoded docs-layout paths
|
|
1208
|
-
const config = getConfig();
|
|
1209
|
-
const wuPath = `${config.directories.wuDir}/${id}.yaml`;
|
|
1210
|
-
// WU-1740: Include wu-events.jsonl to persist state store events
|
|
1211
|
-
const whitelist = [
|
|
1212
|
-
wuPath,
|
|
1213
|
-
config.directories.statusPath,
|
|
1214
|
-
config.directories.backlogPath,
|
|
1215
|
-
resolveWuEventsRelativePath(process.cwd()),
|
|
1216
|
-
];
|
|
1217
|
-
const metadataAllowlist = (options.metadataAllowlist ?? []).filter((file) => typeof file === 'string' && file.length > 0);
|
|
1218
|
-
const whitelistSet = new Set([...whitelist, ...metadataAllowlist]);
|
|
1219
|
-
if (isDocsOnly) {
|
|
1220
|
-
// For docs-only WUs, validate that all staged files are in allowed paths
|
|
1221
|
-
const docsResult = validateDocsOnly(staged);
|
|
1222
|
-
if (!docsResult.valid) {
|
|
1223
|
-
die(`Docs-only WU cannot modify code files:\n ${docsResult.violations.join(`${STRING_LITERALS.NEWLINE} `)}\n\n${getAllowedPathsDescription()}`);
|
|
1224
|
-
}
|
|
1225
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Docs-only path validation passed`);
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
const unexpected = staged.filter((file) => {
|
|
1229
|
-
// Whitelist exact matches
|
|
1230
|
-
if (whitelistSet.has(file))
|
|
1231
|
-
return false;
|
|
1232
|
-
// Whitelist stamps directory pattern
|
|
1233
|
-
if (file.startsWith(`${LUMENFLOW_PATHS.STAMPS_DIR}/`))
|
|
1234
|
-
return false;
|
|
1235
|
-
// WU-1072: Whitelist apps/docs/**/*.mdx for auto-generated docs from turbo docs:generate
|
|
1236
|
-
if (file.startsWith('apps/docs/') && file.endsWith('.mdx'))
|
|
1237
|
-
return false;
|
|
1238
|
-
return true;
|
|
1239
|
-
});
|
|
1240
|
-
if (unexpected.length > 0) {
|
|
1241
|
-
// WU-1311: Use config-based pattern for WU YAML detection
|
|
1242
|
-
const wuDirPattern = config.directories.wuDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1243
|
-
// eslint-disable-next-line security/detect-non-literal-regexp -- config path escaped for regex; not user input
|
|
1244
|
-
const wuYamlRegex = new RegExp(`^${wuDirPattern}/WU-\\d+\\.yaml$`);
|
|
1245
|
-
const otherWuYamlOnly = unexpected.every((f) => wuYamlRegex.test(f));
|
|
1246
|
-
if (otherWuYamlOnly) {
|
|
1247
|
-
console.warn(`${LOG_PREFIX.DONE} Warning: other WU YAMLs are staged; proceeding and committing only current WU files.`);
|
|
1248
|
-
}
|
|
1249
|
-
else {
|
|
1250
|
-
die(`Unexpected files staged (only current WU metadata, current parent initiative YAML, and .lumenflow/stamps/<id>.done allowed):\n ${unexpected.join(`${STRING_LITERALS.NEWLINE} `)}`);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
652
|
// Note: updateStatusRemoveInProgress, addToStatusCompleted, and moveWUToDoneBacklog
|
|
1255
653
|
// have been extracted to tools/lib/wu-status-updater.ts and imported above (WU-1163)
|
|
1256
654
|
//
|
|
@@ -1259,39 +657,6 @@ async function validateStagedFiles(id, isDocsOnly = false, gitAdapter, options =
|
|
|
1259
657
|
// Note: readWUPreferWorktree, detectCurrentWorktree, defaultWorktreeFrom, detectWorkspaceMode,
|
|
1260
658
|
// defaultBranchFrom, branchExists, runCleanup have been extracted to
|
|
1261
659
|
// tools/lib/wu-done-validators.ts and imported above (WU-1215)
|
|
1262
|
-
/**
|
|
1263
|
-
* Validate Branch-Only mode requirements before proceeding
|
|
1264
|
-
* @param {string} laneBranch - Expected lane branch name
|
|
1265
|
-
* @returns {{valid: boolean, error: string|null}}
|
|
1266
|
-
*/
|
|
1267
|
-
async function validateBranchOnlyMode(laneBranch) {
|
|
1268
|
-
// Check we're on the correct lane branch
|
|
1269
|
-
const gitAdapter = getGitForCwd();
|
|
1270
|
-
const currentBranch = await gitAdapter.getCurrentBranch();
|
|
1271
|
-
if (currentBranch !== laneBranch) {
|
|
1272
|
-
return {
|
|
1273
|
-
valid: false,
|
|
1274
|
-
error: `Branch-Only mode error: Not on the lane branch.\n\n` +
|
|
1275
|
-
`Expected branch: ${laneBranch}\n` +
|
|
1276
|
-
`Current branch: ${currentBranch}\n\n` +
|
|
1277
|
-
`Fix: git checkout ${laneBranch}`,
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
// Check working directory is clean
|
|
1281
|
-
const status = await gitAdapter.getStatus();
|
|
1282
|
-
if (status) {
|
|
1283
|
-
return {
|
|
1284
|
-
valid: false,
|
|
1285
|
-
error: `Branch-Only mode error: Working directory is not clean.\n\n` +
|
|
1286
|
-
`Uncommitted changes detected:\n${status}\n\n` +
|
|
1287
|
-
`Fix: Commit all changes before running wu:done\n` +
|
|
1288
|
-
` git add -A\n` +
|
|
1289
|
-
` git commit -m "wu(wu-xxx): ..."\n` +
|
|
1290
|
-
` git push origin ${laneBranch}`,
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
return { valid: true, error: null };
|
|
1294
|
-
}
|
|
1295
660
|
/**
|
|
1296
661
|
* WU-755 + WU-1230: Record transaction state for rollback
|
|
1297
662
|
* @param {string} id - WU ID
|
|
@@ -1757,8 +1122,6 @@ async function executePreFlightChecks({ id, args, isBranchOnly, isDocsOnly, docM
|
|
|
1757
1122
|
else {
|
|
1758
1123
|
// Worktree mode: must be on main
|
|
1759
1124
|
await ensureOnMain(getGitForCwd());
|
|
1760
|
-
// Prevent coordination failures by ensuring main is up-to-date
|
|
1761
|
-
await ensureMainUpToDate();
|
|
1762
1125
|
// P0 EMERGENCY FIX Part 1: Restore wu-events.jsonl BEFORE parallel completion check
|
|
1763
1126
|
// Previous wu:done runs or memory layer writes may have left this file dirty,
|
|
1764
1127
|
// which causes the auto-rebase to fail with "You have unstaged changes"
|
|
@@ -2233,6 +1596,62 @@ export async function main() {
|
|
|
2233
1596
|
const initialDocForValidation = normalizeWUDocLike(initialDocForValidationRaw);
|
|
2234
1597
|
// Capture main checkout path once. process.cwd() may drift later during recovery flows.
|
|
2235
1598
|
const mainCheckoutPath = process.cwd();
|
|
1599
|
+
// ──────────────────────────────────────────────
|
|
1600
|
+
// WU-2211: --already-merged early exit path
|
|
1601
|
+
// Skips merge phase, gates, worktree detection. Only writes metadata.
|
|
1602
|
+
// ──────────────────────────────────────────────
|
|
1603
|
+
if (args.alreadyMerged) {
|
|
1604
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} WU-2211: --already-merged mode activated`);
|
|
1605
|
+
// Safety check: verify code_paths exist on HEAD of main
|
|
1606
|
+
const codePaths = docMain.code_paths || [];
|
|
1607
|
+
const verification = await verifyCodePathsOnMainHead(codePaths);
|
|
1608
|
+
if (!verification.valid) {
|
|
1609
|
+
die(`${EMOJI.FAILURE} --already-merged safety check failed\n\n` +
|
|
1610
|
+
`${verification.error}\n\n` +
|
|
1611
|
+
`Cannot finalize ${id}: code_paths must exist on HEAD before using --already-merged.`);
|
|
1612
|
+
}
|
|
1613
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Safety check passed: all ${codePaths.length} code_paths verified on HEAD`);
|
|
1614
|
+
// Execute finalize-only path
|
|
1615
|
+
const title = String(docMain.title || id);
|
|
1616
|
+
const lane = String(docMain.lane || '');
|
|
1617
|
+
const finalizeResult = await executeAlreadyMergedFinalizeFromModule({
|
|
1618
|
+
id,
|
|
1619
|
+
title,
|
|
1620
|
+
lane,
|
|
1621
|
+
doc: docMain,
|
|
1622
|
+
});
|
|
1623
|
+
if (!finalizeResult.success) {
|
|
1624
|
+
die(`${EMOJI.FAILURE} --already-merged finalization failed\n\n` +
|
|
1625
|
+
`Errors:\n${finalizeResult.errors.map((e) => ` - ${e}`).join('\n')}\n\n` +
|
|
1626
|
+
`Partial state may remain. Rerun: pnpm wu:done --id ${id} --already-merged`);
|
|
1627
|
+
}
|
|
1628
|
+
// Release lane lock (non-blocking, same as normal wu:done)
|
|
1629
|
+
try {
|
|
1630
|
+
const lane = docMain.lane;
|
|
1631
|
+
if (lane) {
|
|
1632
|
+
const releaseResult = releaseLaneLock(lane, { wuId: id });
|
|
1633
|
+
if (releaseResult.released && !releaseResult.notFound) {
|
|
1634
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Lane lock released for "${lane}"`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
catch (err) {
|
|
1639
|
+
console.warn(`${LOG_PREFIX.DONE} Warning: Could not release lane lock: ${getErrorMessage(err)}`);
|
|
1640
|
+
}
|
|
1641
|
+
// End agent session (non-blocking)
|
|
1642
|
+
try {
|
|
1643
|
+
endSessionForWU();
|
|
1644
|
+
}
|
|
1645
|
+
catch {
|
|
1646
|
+
// Non-blocking
|
|
1647
|
+
}
|
|
1648
|
+
// Broadcast completion signal (non-blocking)
|
|
1649
|
+
await broadcastCompletionSignal(id, title);
|
|
1650
|
+
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} ${id} finalized via --already-merged`);
|
|
1651
|
+
console.log(`- WU: ${id} -- ${title}`);
|
|
1652
|
+
clearConfigCache();
|
|
1653
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
1654
|
+
}
|
|
2236
1655
|
// WU-1663: Determine prepPassed early for pipeline actor input.
|
|
2237
1656
|
// canSkipGates checks if wu:prep already ran gates successfully via checkpoint.
|
|
2238
1657
|
// This drives the isPrepPassed guard on the GATES_SKIPPED transition.
|