@lumenflow/cli 3.8.7 → 3.9.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.
- package/README.md +11 -15
- package/dist/cli-entry-point.js +7 -0
- package/dist/cli-entry-point.js.map +1 -1
- package/dist/gate-defaults.js +5 -0
- package/dist/gate-defaults.js.map +1 -1
- package/dist/gates-runners.js +77 -1
- package/dist/gates-runners.js.map +1 -1
- package/dist/guard-main-branch.js +3 -2
- package/dist/guard-main-branch.js.map +1 -1
- package/dist/hooks/enforcement-checks.js +3 -2
- package/dist/hooks/enforcement-checks.js.map +1 -1
- package/dist/hooks/path-utils.js +2 -1
- package/dist/hooks/path-utils.js.map +1 -1
- package/dist/init-detection.js +3 -3
- package/dist/init-detection.js.map +1 -1
- package/dist/lane-create.js +223 -0
- package/dist/lane-create.js.map +1 -0
- package/dist/mem-signal.js +1 -1
- package/dist/mem-signal.js.map +1 -1
- package/dist/public-manifest.js +7 -0
- package/dist/public-manifest.js.map +1 -1
- package/dist/signal-middleware.js +192 -0
- package/dist/signal-middleware.js.map +1 -0
- package/dist/state-doctor.js +135 -0
- package/dist/state-doctor.js.map +1 -1
- package/dist/state-emit.js +72 -10
- package/dist/state-emit.js.map +1 -1
- package/dist/validate.js +2 -2
- package/dist/validate.js.map +1 -1
- package/dist/wu-claim.js +75 -10
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-done-already-merged.js +2 -7
- package/dist/wu-done-already-merged.js.map +1 -1
- package/dist/wu-done-gates.js +296 -0
- package/dist/wu-done-gates.js.map +1 -0
- package/dist/wu-done-memory-telemetry.js +145 -0
- package/dist/wu-done-memory-telemetry.js.map +1 -0
- package/dist/wu-done-mode-execution.js +136 -0
- package/dist/wu-done-mode-execution.js.map +1 -0
- package/dist/wu-done-policies.js +206 -1
- package/dist/wu-done-policies.js.map +1 -1
- package/dist/wu-done-preflight.js +207 -0
- package/dist/wu-done-preflight.js.map +1 -0
- package/dist/wu-done.js +48 -888
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-edit-validators.js +30 -1
- package/dist/wu-edit-validators.js.map +1 -1
- package/dist/wu-edit.js +7 -2
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-prep.js +17 -1
- package/dist/wu-prep.js.map +1 -1
- package/dist/wu-spawn-prompt-builders.js +65 -10
- package/dist/wu-spawn-prompt-builders.js.map +1 -1
- package/package.json +9 -8
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/package.json +9 -2
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/package.json +1 -1
- package/templates/core/.lumenflow/constraints.md.template +12 -14
- package/templates/core/LUMENFLOW.md.template +7 -4
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +2 -2
- package/templates/core/ai/onboarding/docs-generation.md.template +1 -1
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +40 -0
- package/templates/core/ai/onboarding/initiative-orchestration.md.template +402 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +129 -56
- package/templates/core/ai/onboarding/release-process.md.template +102 -39
- package/templates/core/ai/onboarding/starting-prompt.md.template +1 -0
- package/templates/core/ai/onboarding/test-ratchet.md.template +2 -8
- package/templates/vendors/claude/.claude/CLAUDE.md.template +1 -1
- package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +2 -2
- package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +3 -3
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +1 -1
- package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +1 -1
- package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +4 -4
- package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +4 -4
- package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -5
- package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +4 -4
- package/templates/vendors/cline/.clinerules.template +1 -1
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +22 -2
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +22 -2
package/dist/wu-done.js
CHANGED
|
@@ -42,70 +42,56 @@ import { wuDoneMachine, WU_DONE_EVENTS } from '@lumenflow/core/wu-done-machine';
|
|
|
42
42
|
// The guard runs in executeWorktreeCompletion() before metadata transaction
|
|
43
43
|
// See: packages/@lumenflow/core/src/wu-done-validation.ts
|
|
44
44
|
import { execSync } from 'node:child_process';
|
|
45
|
-
import prettyMs from 'pretty-ms';
|
|
46
45
|
import { runGates } from './gates.js';
|
|
46
|
+
import { executeGates } from './wu-done-gates.js';
|
|
47
47
|
// WU-2102: Import scoped test resolver for wu:done gate fallback
|
|
48
48
|
import { resolveScopedUnitTestsForPrep } from './wu-prep.js';
|
|
49
|
-
import { resolveWuDonePreCommitGateDecision } from '@lumenflow/core/gates-agent-mode';
|
|
50
49
|
import { buildClaimRepairCommand } from './wu-claim-repair-guidance.js';
|
|
51
50
|
import { resolveStateDir, resolveWuEventsRelativePath } from './state-path-resolvers.js';
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
51
|
+
import { appendClaimSessionOverrideAuditEvent, auditOwnershipOverride, checkBacklogConsistencyForWU, checkOwnership, computeBranchOnlyFallback, runWuDoneStagedValidation, } from './wu-done-preflight.js';
|
|
52
|
+
import { getGitForCwd } from '@lumenflow/core/git-adapter';
|
|
53
|
+
import { die, getErrorMessage } from '@lumenflow/core/error-handler';
|
|
54
54
|
// WU-1223: Location detection for worktree check
|
|
55
55
|
import { resolveLocation } from '@lumenflow/core/context/location-resolver';
|
|
56
|
-
import { existsSync, readFileSync, mkdirSync, appendFileSync, unlinkSync,
|
|
56
|
+
import { existsSync, readFileSync, mkdirSync, appendFileSync, unlinkSync, readdirSync, } from 'node:fs';
|
|
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
60
|
import { rollbackFiles } from '@lumenflow/core/rollback-utils';
|
|
61
|
-
import { validateInputs, detectModeAndPaths, defaultBranchFrom, runCleanup, validateSpecCompleteness,
|
|
61
|
+
import { validateInputs, detectModeAndPaths, defaultBranchFrom, runCleanup, validateSpecCompleteness,
|
|
62
62
|
// WU-1805: Preflight code_paths validation before gates
|
|
63
63
|
executePreflightCodePathValidation, buildPreflightCodePathErrorMessage,
|
|
64
|
-
// WU-2308: Pre-commit hooks with worktree context
|
|
65
|
-
validateAllPreCommitHooks,
|
|
66
64
|
// WU-2310: Type vs code_paths preflight validation
|
|
67
65
|
validateTypeVsCodePathsPreflight, buildTypeVsCodePathsErrorMessage, } from '@lumenflow/core/wu-done-validators';
|
|
68
66
|
import { formatPreflightWarnings } from '@lumenflow/core/wu-preflight-validators';
|
|
69
67
|
// WU-1825: validateCodePathsExist moved to unified code-path-validator
|
|
70
68
|
import { validateCodePathsExist } from '@lumenflow/core/code-path-validator';
|
|
71
|
-
import { BRANCHES, PATTERNS, DEFAULTS, LOG_PREFIX, EMOJI, GIT, SESSION, WU_STATUS,
|
|
69
|
+
import { BRANCHES, PATTERNS, DEFAULTS, LOG_PREFIX, EMOJI, GIT, SESSION, WU_STATUS, FILE_SYSTEM, EXIT_CODES, STRING_LITERALS, MICRO_WORKTREE_OPERATIONS, TELEMETRY_STEPS, ENV_VARS, getWUStatusDisplay,
|
|
72
70
|
// WU-1223: Location types for worktree detection
|
|
73
71
|
CONTEXT_VALIDATION, } from '@lumenflow/core/wu-constants';
|
|
74
72
|
import { getDocsOnlyPrefixes, DOCS_ONLY_ROOT_FILES } from '@lumenflow/core';
|
|
75
|
-
import {
|
|
73
|
+
import { printStatusPreview } from '@lumenflow/core/wu-done-ui';
|
|
76
74
|
import { ensureOnMain } from '@lumenflow/core/wu-helpers';
|
|
77
75
|
import { WU_PATHS } from '@lumenflow/core/wu-paths';
|
|
78
76
|
import { getConfig, clearConfigCache } from '@lumenflow/core/config';
|
|
79
77
|
import { writeWU, appendNote, parseYAML } from '@lumenflow/core/wu-yaml';
|
|
80
78
|
import { PLACEHOLDER_SENTINEL, validateWU, validateDoneWU, validateApprovalGates, } from '@lumenflow/core/wu-schema';
|
|
81
|
-
import {
|
|
82
|
-
import { executeBranchOnlyCompletion,
|
|
83
|
-
// WU-1492: Import branch-pr completion path
|
|
84
|
-
executeBranchPRCompletion, } from '@lumenflow/core/wu-done-branch-only';
|
|
85
|
-
import { executeWorktreeCompletion, autoRebaseBranch } from '@lumenflow/core/wu-done-worktree';
|
|
86
|
-
// WU-1746: Already-merged worktree resilience
|
|
87
|
-
// WU-2248: Removed executeAlreadyMergedCompletion (wrote directly to local main).
|
|
88
|
-
// Both code paths now use executeAlreadyMergedFinalizeFromModule (micro-worktree isolation).
|
|
89
|
-
import { detectAlreadyMergedNoWorktree } from '@lumenflow/core/wu-done-merged-worktree';
|
|
79
|
+
import { autoRebaseBranch } from '@lumenflow/core/wu-done-worktree';
|
|
90
80
|
// WU-2211: --already-merged finalize-only mode
|
|
91
81
|
import { verifyCodePathsOnMainHead, executeAlreadyMergedFinalize as executeAlreadyMergedFinalizeFromModule, } from './wu-done-already-merged.js';
|
|
82
|
+
import { executeModeSpecificCompletion } from './wu-done-mode-execution.js';
|
|
92
83
|
import { checkWUConsistency } from '@lumenflow/core/wu-consistency-checker';
|
|
93
84
|
// WU-1542: Use blocking mode compliance check (replaces non-blocking checkMandatoryAgentsCompliance)
|
|
94
85
|
import { checkMandatoryAgentsComplianceBlocking } from '@lumenflow/core/orchestration-rules';
|
|
95
86
|
import { endSessionForWU, getCurrentSessionForWU } from '@lumenflow/agent/auto-session';
|
|
96
87
|
import { runBackgroundProcessCheck } from '@lumenflow/core/process-detector';
|
|
97
88
|
import { WUStateStore } from '@lumenflow/core/wu-state-store';
|
|
98
|
-
// WU-1588: INIT-007 memory layer integration
|
|
99
|
-
import { createCheckpoint } from '@lumenflow/memory/checkpoint';
|
|
100
|
-
import { createSignal, loadSignals } from '@lumenflow/memory/signal';
|
|
101
89
|
// WU-1763: Memory store for loading discoveries (lifecycle nudges)
|
|
102
|
-
import { loadMemory
|
|
103
|
-
// WU-1943: Checkpoint warning helper
|
|
104
|
-
import { hasSessionCheckpoints } from '@lumenflow/core/wu-done-worktree';
|
|
90
|
+
import { loadMemory } from '@lumenflow/memory/store';
|
|
105
91
|
// WU-1603: Atomic lane locking - release lock on WU completion
|
|
106
92
|
import { releaseLaneLock } from '@lumenflow/core/lane-lock';
|
|
107
93
|
// WU-1747: Checkpoint and lock for concurrent load resilience
|
|
108
|
-
import {
|
|
94
|
+
import { canSkipGates, clearCheckpoint } from '@lumenflow/core/wu-checkpoint';
|
|
109
95
|
// WU-1946: Spawn registry for tracking sub-agent spawns
|
|
110
96
|
import { DelegationRegistryStore } from '@lumenflow/core/delegation-registry-store';
|
|
111
97
|
import { DelegationStatus } from '@lumenflow/core/delegation-registry-schema';
|
|
@@ -121,77 +107,15 @@ import { evaluateMainDirtyMutationGuard } from './hooks/dirty-guard.js';
|
|
|
121
107
|
// WU-1474: Decay policy invocation during completion lifecycle
|
|
122
108
|
import { runDecayOnDone } from './wu-done-decay.js';
|
|
123
109
|
import { validateClaimSessionOwnership } from './wu-done-ownership.js';
|
|
110
|
+
import { broadcastCompletionSignal, checkInboxForRecentSignals, createPreGatesCheckpoint, emitTelemetry, enforceCheckpointGateForDone, resolveCheckpointGateMode, } from './wu-done-memory-telemetry.js';
|
|
124
111
|
import { enforceSpawnProvenanceForDone, enforceWuBriefEvidenceForDone, printExposureWarnings, validateAccessibilityOrDie, validateDocsOnlyFlag, } from './wu-done-policies.js';
|
|
125
112
|
import { detectParallelCompletions, ensureNoAutoStagedOrNoop, runTripwireCheck, validateBranchOnlyMode, validateStagedFiles, } from './wu-done-git-ops.js';
|
|
126
113
|
import { flushWuLifecycleSync } from './wu-lifecycle-sync/service.js';
|
|
127
114
|
import { WU_LIFECYCLE_COMMANDS } from './wu-lifecycle-sync/constants.js';
|
|
128
115
|
export { buildGatesCommand, buildMissingSpawnPickupEvidenceMessage, buildMissingSpawnProvenanceMessage, buildMissingWuBriefEvidenceMessage, enforceSpawnProvenanceForDone, enforceWuBriefEvidenceForDone, hasSpawnPickupEvidence, printExposureWarnings, shouldEnforceSpawnProvenance, shouldEnforceWuBriefEvidence, validateAccessibilityOrDie, validateDocsOnlyFlag, } from './wu-done-policies.js';
|
|
129
116
|
export { isBranchAlreadyMerged } from './wu-done-git-ops.js';
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
WU_COMPLETION: 'wu_completion',
|
|
133
|
-
};
|
|
134
|
-
const MEMORY_CHECKPOINT_NOTES = {
|
|
135
|
-
PRE_GATES: 'Pre-gates checkpoint for recovery if gates fail',
|
|
136
|
-
};
|
|
137
|
-
const MEMORY_SIGNAL_WINDOW_MS = 60 * 60 * 1000; // 1 hour for recent signals
|
|
138
|
-
export const CHECKPOINT_GATE_MODES = {
|
|
139
|
-
OFF: 'off',
|
|
140
|
-
WARN: 'warn',
|
|
141
|
-
BLOCK: 'block',
|
|
142
|
-
};
|
|
143
|
-
const CHECKPOINT_GATE_CONFIG = {
|
|
144
|
-
PATH: 'memory.enforcement.require_checkpoint_for_done',
|
|
145
|
-
COMMAND_PREFIX: 'pnpm mem:checkpoint --wu',
|
|
146
|
-
WARN_TAG: 'WU-1998',
|
|
147
|
-
};
|
|
148
|
-
function buildCheckpointGateBlockMessage(id) {
|
|
149
|
-
return (`${STRING_LITERALS.NEWLINE}${LOG_PREFIX.DONE} ${EMOJI.FAILURE} No checkpoints found for ${id} session.${STRING_LITERALS.NEWLINE}` +
|
|
150
|
-
`${LOG_PREFIX.DONE} ${CHECKPOINT_GATE_CONFIG.PATH} is set to '${CHECKPOINT_GATE_MODES.BLOCK}'.${STRING_LITERALS.NEWLINE}` +
|
|
151
|
-
`${LOG_PREFIX.DONE} Create a checkpoint before completing: ${CHECKPOINT_GATE_CONFIG.COMMAND_PREFIX} ${id}${STRING_LITERALS.NEWLINE}`);
|
|
152
|
-
}
|
|
153
|
-
function buildCheckpointGateWarnMessages(id) {
|
|
154
|
-
return [
|
|
155
|
-
`${STRING_LITERALS.NEWLINE}${LOG_PREFIX.DONE} ${EMOJI.INFO} ${CHECKPOINT_GATE_CONFIG.WARN_TAG}: No prior checkpoints recorded for ${id} in this session.`,
|
|
156
|
-
`${LOG_PREFIX.DONE} A pre-gates checkpoint will be created automatically by wu:done.`,
|
|
157
|
-
`${LOG_PREFIX.DONE} For earlier crash recovery, run '${CHECKPOINT_GATE_CONFIG.COMMAND_PREFIX} ${id}' after each acceptance criterion, before gates, or every 30 tool calls.${STRING_LITERALS.NEWLINE}`,
|
|
158
|
-
];
|
|
159
|
-
}
|
|
160
|
-
export function resolveCheckpointGateMode(mode) {
|
|
161
|
-
if (mode === CHECKPOINT_GATE_MODES.OFF) {
|
|
162
|
-
return CHECKPOINT_GATE_MODES.OFF;
|
|
163
|
-
}
|
|
164
|
-
if (mode === CHECKPOINT_GATE_MODES.BLOCK) {
|
|
165
|
-
return CHECKPOINT_GATE_MODES.BLOCK;
|
|
166
|
-
}
|
|
167
|
-
return CHECKPOINT_GATE_MODES.WARN;
|
|
168
|
-
}
|
|
169
|
-
export async function enforceCheckpointGateForDone({ id, workspacePath, mode, queryByWuFn = queryByWu, hasSessionCheckpointsFn = hasSessionCheckpoints, log = console.log, blocker = (message) => {
|
|
170
|
-
die(message);
|
|
171
|
-
}, }) {
|
|
172
|
-
if (mode === CHECKPOINT_GATE_MODES.OFF) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
let wuNodes;
|
|
176
|
-
try {
|
|
177
|
-
wuNodes = await queryByWuFn(workspacePath, id);
|
|
178
|
-
if (hasSessionCheckpointsFn(id, wuNodes)) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// Fail-open: checkpoint discovery issues should not block wu:done.
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (mode === CHECKPOINT_GATE_MODES.BLOCK) {
|
|
187
|
-
blocker(buildCheckpointGateBlockMessage(id));
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const warnMessages = buildCheckpointGateWarnMessages(id);
|
|
191
|
-
for (const message of warnMessages) {
|
|
192
|
-
log(message);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
117
|
+
export { checkBacklogConsistencyForWU, computeBranchOnlyFallback, normalizeUsername, } from './wu-done-preflight.js';
|
|
118
|
+
export { CHECKPOINT_GATE_MODES, enforceCheckpointGateForDone, resolveCheckpointGateMode, } from './wu-done-memory-telemetry.js';
|
|
195
119
|
const WU_FILE_NAME_PATTERN = /^WU-\d+\.ya?ml$/;
|
|
196
120
|
function normalizeRepoRelativePathForYaml(rawPath) {
|
|
197
121
|
return rawPath.replaceAll('\\', '/').replace(/^\.\/+/, '');
|
|
@@ -364,91 +288,6 @@ async function _assertWorktreeWUInProgressInStateStore(id, worktreePath) {
|
|
|
364
288
|
`Fix the claim/state log first, then rerun wu:done.`);
|
|
365
289
|
}
|
|
366
290
|
}
|
|
367
|
-
/**
|
|
368
|
-
* WU-1588: Create pre-gates checkpoint for recovery if gates fail.
|
|
369
|
-
* Non-blocking wrapper around mem:checkpoint - failures logged as warnings.
|
|
370
|
-
*
|
|
371
|
-
* @param {string} id - WU ID
|
|
372
|
-
* @param {string|null} worktreePath - Path to worktree
|
|
373
|
-
* @param {string} baseDir - Base directory for memory layer
|
|
374
|
-
* @returns {Promise<void>}
|
|
375
|
-
*/
|
|
376
|
-
async function createPreGatesCheckpoint(id, worktreePath, baseDir = process.cwd()) {
|
|
377
|
-
try {
|
|
378
|
-
const result = await createCheckpoint(baseDir, {
|
|
379
|
-
note: MEMORY_CHECKPOINT_NOTES.PRE_GATES,
|
|
380
|
-
wuId: id,
|
|
381
|
-
progress: `Starting gates execution for ${id}`,
|
|
382
|
-
nextSteps: worktreePath
|
|
383
|
-
? `Gates running in worktree: ${worktreePath}`
|
|
384
|
-
: 'Gates running in branch-only mode',
|
|
385
|
-
trigger: 'wu-done-pre-gates',
|
|
386
|
-
});
|
|
387
|
-
if (result.success) {
|
|
388
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Pre-gates checkpoint created (${result.checkpoint.id})`);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
catch (err) {
|
|
392
|
-
// Non-blocking: checkpoint failure should not block wu:done
|
|
393
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not create pre-gates checkpoint: ${getErrorMessage(err)}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* WU-1588: Broadcast completion signal to parallel agents.
|
|
398
|
-
* Non-blocking wrapper around mem:signal - failures logged as warnings.
|
|
399
|
-
*
|
|
400
|
-
* @param {string} id - WU ID
|
|
401
|
-
* @param {string} title - WU title
|
|
402
|
-
* @param {string} baseDir - Base directory for memory layer
|
|
403
|
-
* @returns {Promise<void>}
|
|
404
|
-
*/
|
|
405
|
-
async function broadcastCompletionSignal(id, title, baseDir = process.cwd()) {
|
|
406
|
-
try {
|
|
407
|
-
const result = await createSignal(baseDir, {
|
|
408
|
-
message: `${MEMORY_SIGNAL_TYPES.WU_COMPLETION}: ${id} - ${title}`,
|
|
409
|
-
wuId: id,
|
|
410
|
-
});
|
|
411
|
-
if (result.success) {
|
|
412
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Completion signal broadcast (${result.signal.id})`);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
catch (err) {
|
|
416
|
-
// Non-blocking: signal failure should not block wu:done
|
|
417
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not broadcast completion signal: ${getErrorMessage(err)}`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* WU-1588: Check inbox for recent signals from parallel agents.
|
|
422
|
-
* Non-blocking wrapper around loadSignals - failures logged as warnings.
|
|
423
|
-
*
|
|
424
|
-
* @param {string} id - Current WU ID (for filtering)
|
|
425
|
-
* @param {string} baseDir - Base directory for memory layer
|
|
426
|
-
* @returns {Promise<void>}
|
|
427
|
-
*/
|
|
428
|
-
async function checkInboxForRecentSignals(id, baseDir = process.cwd()) {
|
|
429
|
-
try {
|
|
430
|
-
const since = new Date(Date.now() - MEMORY_SIGNAL_WINDOW_MS);
|
|
431
|
-
const signals = await loadSignals(baseDir, { since, unreadOnly: true });
|
|
432
|
-
// Filter out signals for current WU
|
|
433
|
-
const relevantSignals = signals.filter((s) => s.wu_id !== id);
|
|
434
|
-
if (relevantSignals.length > 0) {
|
|
435
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.INFO} Recent signals from parallel agents:`);
|
|
436
|
-
for (const signal of relevantSignals.slice(0, 5)) {
|
|
437
|
-
// Show at most 5
|
|
438
|
-
const timestamp = new Date(signal.created_at).toLocaleTimeString();
|
|
439
|
-
console.log(` - [${timestamp}] ${signal.message}`);
|
|
440
|
-
}
|
|
441
|
-
if (relevantSignals.length > 5) {
|
|
442
|
-
console.log(` ... and ${relevantSignals.length - 5} more`);
|
|
443
|
-
}
|
|
444
|
-
console.log(` Run 'pnpm mem:inbox' for full list\n`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
catch (err) {
|
|
448
|
-
// Non-blocking: inbox check failure should not block wu:done
|
|
449
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not check inbox for signals: ${getErrorMessage(err)}`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
291
|
/**
|
|
453
292
|
* WU-1946: Update spawn registry on WU completion.
|
|
454
293
|
* Non-blocking wrapper - failures logged as warnings.
|
|
@@ -490,64 +329,10 @@ const GIT_CONFIG_USER_NAME = 'user.name';
|
|
|
490
329
|
const GIT_CONFIG_USER_EMAIL = 'user.email';
|
|
491
330
|
// Default fallback messages
|
|
492
331
|
const DEFAULT_NO_REASON = '(no reason provided)';
|
|
493
|
-
/**
|
|
494
|
-
* WU-1234: Normalize username for ownership comparison
|
|
495
|
-
* Extracts username from email address for comparison.
|
|
496
|
-
* This allows tom@hellm.ai to match 'tom' assigned_to field.
|
|
497
|
-
*
|
|
498
|
-
* @param {string|null|undefined} value - Email address or username
|
|
499
|
-
* @returns {string} Normalized username (lowercase)
|
|
500
|
-
*/
|
|
501
|
-
export function normalizeUsername(value) {
|
|
502
|
-
if (!value)
|
|
503
|
-
return '';
|
|
504
|
-
const str = String(value).trim();
|
|
505
|
-
// Extract username from email: tom@hellm.ai -> tom
|
|
506
|
-
// WU-1281: Using string split instead of regex
|
|
507
|
-
const atIndex = str.indexOf('@');
|
|
508
|
-
const username = atIndex > 0 ? str.slice(0, atIndex) : str;
|
|
509
|
-
return username.toLowerCase();
|
|
510
|
-
}
|
|
511
332
|
// WU-1281: isDocsOnlyByPaths removed - use shouldSkipWebTests from path-classifiers.ts
|
|
512
333
|
// The validators already use shouldSkipWebTests via detectDocsOnlyByPaths wrapper.
|
|
513
334
|
// Keeping the export for backward compatibility but re-exporting the canonical function.
|
|
514
335
|
export { shouldSkipWebTests as isDocsOnlyByPaths } from '@lumenflow/core/path-classifiers';
|
|
515
|
-
/**
|
|
516
|
-
* WU-1234: Pre-flight check for backlog state consistency
|
|
517
|
-
* Fails fast if the WU appears in both Done and In Progress sections.
|
|
518
|
-
*
|
|
519
|
-
* @param {string} id - WU ID to check
|
|
520
|
-
* @param {string} backlogPath - Path to backlog.md
|
|
521
|
-
* @returns {{ valid: boolean, error: string|null }}
|
|
522
|
-
*/
|
|
523
|
-
export function checkBacklogConsistencyForWU(id, backlogPath) {
|
|
524
|
-
try {
|
|
525
|
-
const result = validateBacklogSync(backlogPath);
|
|
526
|
-
// Check if this specific WU is in both Done and In Progress
|
|
527
|
-
if (!result.valid) {
|
|
528
|
-
for (const error of result.errors) {
|
|
529
|
-
// Check if the error mentions both Done and In Progress AND mentions our WU
|
|
530
|
-
if (error.includes('Done and In Progress') && error.includes(id)) {
|
|
531
|
-
return {
|
|
532
|
-
valid: false,
|
|
533
|
-
error: `❌ BACKLOG STATE INCONSISTENCY: ${id} found in both Done and In Progress sections.\n\n` +
|
|
534
|
-
`This is an invalid state that must be fixed manually before wu:done can proceed.\n\n` +
|
|
535
|
-
`Fix options:\n` +
|
|
536
|
-
` 1. If ${id} is truly done: Remove from In Progress in backlog.md\n` +
|
|
537
|
-
` 2. If ${id} needs more work: Remove from Done in backlog.md, update WU YAML status\n\n` +
|
|
538
|
-
`After fixing backlog.md, retry: pnpm wu:done --id ${id}`,
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
return { valid: true, error: null };
|
|
544
|
-
}
|
|
545
|
-
catch (e) {
|
|
546
|
-
// If validation fails (e.g., file not found), warn but don't block
|
|
547
|
-
console.warn(`${LOG_PREFIX.DONE} Warning: Could not validate backlog consistency: ${getErrorMessage(e)}`);
|
|
548
|
-
return { valid: true, error: null };
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
336
|
/**
|
|
552
337
|
* Read commitlint header-max-length from config, fallback to DEFAULTS.MAX_COMMIT_SUBJECT
|
|
553
338
|
* WU-1281: Using centralized constant instead of hardcoded 100
|
|
@@ -565,14 +350,6 @@ function getCommitHeaderLimit() {
|
|
|
565
350
|
}
|
|
566
351
|
}
|
|
567
352
|
// ensureOnMain() moved to wu-helpers.ts (WU-1256)
|
|
568
|
-
export function emitTelemetry(event) {
|
|
569
|
-
const logPath = path.join('.lumenflow', 'flow.log');
|
|
570
|
-
const logDir = path.dirname(logPath);
|
|
571
|
-
if (!existsSync(logDir))
|
|
572
|
-
mkdirSync(logDir, { recursive: true });
|
|
573
|
-
const line = JSON.stringify({ timestamp: new Date().toISOString(), ...event });
|
|
574
|
-
appendFileSync(logPath, `${line}\n`, { encoding: FILE_SYSTEM.UTF8 });
|
|
575
|
-
}
|
|
576
353
|
async function auditSkipGates(id, reason, fixWU, worktreePath) {
|
|
577
354
|
const auditBaseDir = worktreePath || process.cwd();
|
|
578
355
|
const auditPath = path.join(auditBaseDir, '.lumenflow', 'skip-gates-audit.log');
|
|
@@ -626,117 +403,6 @@ async function auditSkipCosGates(id, reason, worktreePath) {
|
|
|
626
403
|
}
|
|
627
404
|
// WU-2308: validateAllPreCommitHooks moved to wu-done-validators.ts
|
|
628
405
|
// Now accepts worktreePath parameter to run audit from worktree context
|
|
629
|
-
/**
|
|
630
|
-
* Check if node_modules in worktree may be stale
|
|
631
|
-
* Detects when package.json differs between main and worktree, which indicates
|
|
632
|
-
* dependencies were added/removed but pnpm install may not have run in worktree.
|
|
633
|
-
* This prevents confusing typecheck failures due to missing dependencies.
|
|
634
|
-
* @param {string} worktreePath - Path to worktree
|
|
635
|
-
*/
|
|
636
|
-
function checkNodeModulesStaleness(worktreePath) {
|
|
637
|
-
try {
|
|
638
|
-
const mainPackageJson = path.resolve('package.json');
|
|
639
|
-
const worktreePackageJson = path.resolve(worktreePath, 'package.json');
|
|
640
|
-
if (!existsSync(mainPackageJson) || !existsSync(worktreePackageJson)) {
|
|
641
|
-
// No package.json to compare
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
const mainContent = readFileSync(mainPackageJson, {
|
|
645
|
-
encoding: FILE_SYSTEM.UTF8,
|
|
646
|
-
});
|
|
647
|
-
const worktreeContent = readFileSync(worktreePackageJson, {
|
|
648
|
-
encoding: FILE_SYSTEM.UTF8,
|
|
649
|
-
});
|
|
650
|
-
// Compare package.json files
|
|
651
|
-
if (mainContent !== worktreeContent) {
|
|
652
|
-
const worktreeNodeModules = path.resolve(worktreePath, 'node_modules');
|
|
653
|
-
// Check if node_modules exists and when it was last modified
|
|
654
|
-
if (existsSync(worktreeNodeModules)) {
|
|
655
|
-
const nodeModulesStat = statSync(worktreeNodeModules);
|
|
656
|
-
const packageJsonStat = statSync(worktreePackageJson);
|
|
657
|
-
// If package.json is newer than node_modules, dependencies may be stale
|
|
658
|
-
if (packageJsonStat.mtimeMs > nodeModulesStat.mtimeMs) {
|
|
659
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} WARNING: Potentially stale node_modules detected\n\n` +
|
|
660
|
-
` package.json in worktree differs from main checkout\n` +
|
|
661
|
-
` node_modules was last modified: ${nodeModulesStat.mtime.toISOString()}\n` +
|
|
662
|
-
` package.json was last modified: ${packageJsonStat.mtime.toISOString()}\n\n` +
|
|
663
|
-
` If gates fail with missing dependencies/types, run:\n` +
|
|
664
|
-
` cd ${worktreePath}\n` +
|
|
665
|
-
` pnpm install\n` +
|
|
666
|
-
` cd -\n` +
|
|
667
|
-
` pnpm wu:done --id <WU-ID>\n`);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
// node_modules doesn't exist at all
|
|
672
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} WARNING: node_modules missing in worktree\n\n` +
|
|
673
|
-
` package.json in worktree differs from main checkout\n` +
|
|
674
|
-
` but node_modules directory does not exist\n\n` +
|
|
675
|
-
` If gates fail with missing dependencies/types, run:\n` +
|
|
676
|
-
` cd ${worktreePath}\n` +
|
|
677
|
-
` pnpm install\n` +
|
|
678
|
-
` cd -\n` +
|
|
679
|
-
` pnpm wu:done --id <WU-ID>\n`);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
catch (e) {
|
|
684
|
-
// Non-critical check - just warn if it fails
|
|
685
|
-
console.warn(`${LOG_PREFIX.DONE} Could not check node_modules staleness: ${getErrorMessage(e)}`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Run gates in worktree
|
|
690
|
-
* @param {string} worktreePath - Path to worktree
|
|
691
|
-
* @param {string} id - WU ID
|
|
692
|
-
* @param {object} options - Gates options
|
|
693
|
-
* @param {boolean} options.isDocsOnly - Auto-detected docs-only from code_paths
|
|
694
|
-
* @param {boolean} options.docsOnly - Explicit --docs-only flag from CLI
|
|
695
|
-
*/
|
|
696
|
-
async function runGatesInWorktree(worktreePath, id, options = {}) {
|
|
697
|
-
const { isDocsOnly = false, docsOnly = false, scopedTestPaths } = options;
|
|
698
|
-
console.log(`\n${LOG_PREFIX.DONE} Running gates in worktree: ${worktreePath}`);
|
|
699
|
-
// Check for stale node_modules before running gates (prevents confusing failures)
|
|
700
|
-
checkNodeModulesStaleness(worktreePath);
|
|
701
|
-
// WU-1012: Use docs-only gates if explicit --docs-only flag OR auto-detected
|
|
702
|
-
const useDocsOnlyGates = docsOnly || isDocsOnly;
|
|
703
|
-
if (useDocsOnlyGates) {
|
|
704
|
-
console.log(`${LOG_PREFIX.DONE} Using docs-only gates (skipping lint/typecheck/tests)`);
|
|
705
|
-
if (docsOnly) {
|
|
706
|
-
console.log(`${LOG_PREFIX.DONE} (explicit --docs-only flag)`);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const startTime = Date.now();
|
|
710
|
-
try {
|
|
711
|
-
const ok = Boolean(await runGates({
|
|
712
|
-
cwd: worktreePath,
|
|
713
|
-
docsOnly: useDocsOnlyGates,
|
|
714
|
-
coverageMode: undefined,
|
|
715
|
-
scopedTestPaths,
|
|
716
|
-
}));
|
|
717
|
-
if (!ok) {
|
|
718
|
-
throw createError(ErrorCodes.GATES_FAILED, 'Gates failed');
|
|
719
|
-
}
|
|
720
|
-
const duration = Date.now() - startTime;
|
|
721
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Gates passed in ${prettyMs(duration)}`);
|
|
722
|
-
emitTelemetry({ script: 'wu-done', wu_id: id, step: 'gates', ok: true, duration_ms: duration });
|
|
723
|
-
return true;
|
|
724
|
-
}
|
|
725
|
-
catch {
|
|
726
|
-
const duration = Date.now() - startTime;
|
|
727
|
-
emitTelemetry({
|
|
728
|
-
script: 'wu-done',
|
|
729
|
-
wu_id: id,
|
|
730
|
-
step: 'gates',
|
|
731
|
-
ok: false,
|
|
732
|
-
duration_ms: duration,
|
|
733
|
-
});
|
|
734
|
-
// WU-1280: Prominent error summary box (visible after ~130k chars of gate output)
|
|
735
|
-
// WU-1281: Extracted to helper using pretty-ms for duration formatting
|
|
736
|
-
printGateFailureBox({ id, location: worktreePath, durationMs: duration, isWorktreeMode: true });
|
|
737
|
-
die(`Gates failed in ${worktreePath}. Fix issues in the worktree and try again.`);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
406
|
// Note: updateStatusRemoveInProgress, addToStatusCompleted, and moveWUToDoneBacklog
|
|
741
407
|
// have been extracted to tools/lib/wu-status-updater.ts and imported above (WU-1163)
|
|
742
408
|
//
|
|
@@ -955,155 +621,6 @@ function runWUValidator(doc, id, allowTodo = false, worktreePath = null) {
|
|
|
955
621
|
}
|
|
956
622
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} WU validator passed`);
|
|
957
623
|
}
|
|
958
|
-
/**
|
|
959
|
-
* GUARDRAIL 2: Enforce ownership semantics in wu:done
|
|
960
|
-
*
|
|
961
|
-
* Validates that the current user owns the WU before allowing completion.
|
|
962
|
-
* Prevents agents/humans from finishing WUs they do not own.
|
|
963
|
-
*
|
|
964
|
-
* @param {string} id - WU ID
|
|
965
|
-
* @param {object} doc - WU YAML document
|
|
966
|
-
* @param {string|null} worktreePath - Expected worktree path
|
|
967
|
-
* @param {boolean} overrideOwner - Override flag (requires reason)
|
|
968
|
-
* @param {string|null} overrideReason - Reason for override
|
|
969
|
-
* @returns {{valid: boolean, error: string|null, auditEntry: object|null}}
|
|
970
|
-
*/
|
|
971
|
-
async function appendClaimSessionOverrideAuditEvent({ wuId, claimedSessionId, activeSessionId, reason, worktreePath, }) {
|
|
972
|
-
const stateDir = resolveStateDir(worktreePath);
|
|
973
|
-
const stateStore = new WUStateStore(stateDir);
|
|
974
|
-
await stateStore.load();
|
|
975
|
-
await stateStore.checkpoint(wuId, `[wu:done] force ownership override claimed_session=${claimedSessionId} active_session=${activeSessionId || 'none'}`, {
|
|
976
|
-
progress: 'wu:done ownership override',
|
|
977
|
-
nextSteps: reason,
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
async function checkOwnership(id, doc, worktreePath, overrideOwner = false, overrideReason = null) {
|
|
981
|
-
// Missing worktree means WU was not claimed properly (unless escape hatch applies)
|
|
982
|
-
if (!worktreePath || !existsSync(worktreePath)) {
|
|
983
|
-
return {
|
|
984
|
-
valid: false,
|
|
985
|
-
error: `Missing worktree for ${id}.\n\n` +
|
|
986
|
-
`Expected worktree at: ${worktreePath || 'unknown'}\n\n` +
|
|
987
|
-
`Worktrees are required for proper WU completion in Worktree mode.\n` +
|
|
988
|
-
`If the worktree was removed, recreate it and retry, or use --skip-gates with justification.`,
|
|
989
|
-
auditEntry: null,
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
// Get assigned owner from WU YAML - read directly from worktree to ensure we get the lane branch version
|
|
993
|
-
let assignedTo = doc.assigned_to || null;
|
|
994
|
-
if (!assignedTo && worktreePath) {
|
|
995
|
-
// Fallback: Read directly from worktree YAML if not present in doc (fixes WU-1106)
|
|
996
|
-
const wtWUPath = path.join(worktreePath, WU_PATHS.WU(id));
|
|
997
|
-
if (existsSync(wtWUPath)) {
|
|
998
|
-
try {
|
|
999
|
-
const text = readFileSync(wtWUPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
1000
|
-
const wtDoc = parseYAML(text);
|
|
1001
|
-
assignedTo = wtDoc?.assigned_to || null;
|
|
1002
|
-
if (assignedTo) {
|
|
1003
|
-
console.log(`${LOG_PREFIX.DONE} Note: Read assigned_to from worktree YAML (not found in main)`);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
catch (err) {
|
|
1007
|
-
console.warn(`${LOG_PREFIX.DONE} Warning: Failed to read assigned_to from worktree: ${getErrorMessage(err)}`);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
if (!assignedTo) {
|
|
1012
|
-
return {
|
|
1013
|
-
valid: false,
|
|
1014
|
-
error: `WU ${id} has no assigned_to field.\n\n` +
|
|
1015
|
-
`This WU was claimed before ownership tracking was implemented.\n` +
|
|
1016
|
-
`To complete this WU:\n` +
|
|
1017
|
-
` 1. Add assigned_to: <your-email> to ${id}.yaml\n` +
|
|
1018
|
-
` 2. Commit the change\n` +
|
|
1019
|
-
` 3. Re-run: pnpm wu:done --id ${id}`,
|
|
1020
|
-
auditEntry: null,
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
// Get current user identity
|
|
1024
|
-
let currentUser;
|
|
1025
|
-
try {
|
|
1026
|
-
currentUser = (await getGitForCwd().getConfigValue(GIT_CONFIG_USER_EMAIL)).trim();
|
|
1027
|
-
}
|
|
1028
|
-
catch {
|
|
1029
|
-
// Fallback to environment variable
|
|
1030
|
-
currentUser = process.env.GIT_USER || process.env.USER || null;
|
|
1031
|
-
}
|
|
1032
|
-
if (!currentUser) {
|
|
1033
|
-
return {
|
|
1034
|
-
valid: false,
|
|
1035
|
-
error: `Cannot determine current user identity.\n\n` +
|
|
1036
|
-
`Set git user.email or GIT_USER environment variable.`,
|
|
1037
|
-
auditEntry: null,
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
// WU-1234: Normalize usernames for comparison (allows email vs username match)
|
|
1041
|
-
// e.g., tom@hellm.ai matches 'tom' assigned_to field
|
|
1042
|
-
const normalizedAssigned = normalizeUsername(assignedTo);
|
|
1043
|
-
const normalizedCurrent = normalizeUsername(currentUser);
|
|
1044
|
-
const isOwner = normalizedAssigned === normalizedCurrent;
|
|
1045
|
-
if (isOwner) {
|
|
1046
|
-
// Owner is completing their own WU - allow
|
|
1047
|
-
if (assignedTo !== currentUser) {
|
|
1048
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Ownership match via normalization: "${assignedTo}" == "${currentUser}"`);
|
|
1049
|
-
}
|
|
1050
|
-
return { valid: true, error: null, auditEntry: null };
|
|
1051
|
-
}
|
|
1052
|
-
// Not the owner - check for override
|
|
1053
|
-
if (overrideOwner) {
|
|
1054
|
-
if (!overrideReason) {
|
|
1055
|
-
return {
|
|
1056
|
-
valid: false,
|
|
1057
|
-
error: `--override-owner requires --reason "<why you're completing someone else's WU>"`,
|
|
1058
|
-
auditEntry: null,
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
// Create audit entry
|
|
1062
|
-
const auditEntry = {
|
|
1063
|
-
timestamp: new Date().toISOString(),
|
|
1064
|
-
wu_id: id,
|
|
1065
|
-
assigned_to: assignedTo,
|
|
1066
|
-
completed_by: currentUser,
|
|
1067
|
-
reason: overrideReason,
|
|
1068
|
-
git_commit: (await getGitForCwd().getCommitHash()).trim(),
|
|
1069
|
-
};
|
|
1070
|
-
console.log(`\n⚠️ --override-owner: Completing WU assigned to someone else`);
|
|
1071
|
-
console.log(` Assigned to: ${assignedTo}`);
|
|
1072
|
-
console.log(` Completed by: ${currentUser}`);
|
|
1073
|
-
console.log(` Reason: ${overrideReason}\n`);
|
|
1074
|
-
return { valid: true, error: null, auditEntry };
|
|
1075
|
-
}
|
|
1076
|
-
// Not the owner and no override - block
|
|
1077
|
-
return {
|
|
1078
|
-
valid: false,
|
|
1079
|
-
error: `\n❌ OWNERSHIP VIOLATION: ${id} is assigned to someone else\n\n` +
|
|
1080
|
-
` Assigned to: ${assignedTo}\n` +
|
|
1081
|
-
` Current user: ${currentUser}\n\n` +
|
|
1082
|
-
` You cannot complete WUs you do not own.\n\n` +
|
|
1083
|
-
` 📋 Options:\n` +
|
|
1084
|
-
` 1. Contact ${assignedTo} to complete the WU\n` +
|
|
1085
|
-
` 2. Reassign the WU to yourself in ${id}.yaml (requires approval)\n` +
|
|
1086
|
-
` 3. Add co_assigned field for pairing (requires approval)\n\n` +
|
|
1087
|
-
` ⚠️ To override (use with extreme caution):\n` +
|
|
1088
|
-
` pnpm wu:done --id ${id} --override-owner --reason "<why>"\n\n` +
|
|
1089
|
-
` AGENTS: NEVER use --override-owner without explicit instruction.\n` +
|
|
1090
|
-
` Language protocol: "pick up WU-${id.replace('WU-', '')}" = READ ONLY.\n`,
|
|
1091
|
-
auditEntry: null,
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Log ownership override to audit trail
|
|
1096
|
-
* @param {object} auditEntry - Audit entry to log
|
|
1097
|
-
*/
|
|
1098
|
-
function auditOwnershipOverride(auditEntry) {
|
|
1099
|
-
const auditPath = path.join('.lumenflow', 'ownership-override-audit.log');
|
|
1100
|
-
const auditDir = path.dirname(auditPath);
|
|
1101
|
-
if (!existsSync(auditDir))
|
|
1102
|
-
mkdirSync(auditDir, { recursive: true });
|
|
1103
|
-
const line = JSON.stringify(auditEntry);
|
|
1104
|
-
appendFileSync(auditPath, `${line}\n`, { encoding: FILE_SYSTEM.UTF8 });
|
|
1105
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.MEMO} Ownership override logged to ${auditPath}`);
|
|
1106
|
-
}
|
|
1107
624
|
/**
|
|
1108
625
|
* Execute pre-flight checks before gates
|
|
1109
626
|
* Extracted from main() to reduce complexity (WU-1215 Phase 2 Extraction #3)
|
|
@@ -1431,222 +948,6 @@ async function executePreFlightChecks({ id, args, isBranchOnly, isDocsOnly, docM
|
|
|
1431
948
|
}
|
|
1432
949
|
return { title, docForValidation };
|
|
1433
950
|
}
|
|
1434
|
-
async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath, branchName, scopedTestPaths, }) {
|
|
1435
|
-
const gateResult = {
|
|
1436
|
-
fullGatesRanInCurrentRun: false,
|
|
1437
|
-
skippedByCheckpoint: false,
|
|
1438
|
-
checkpointId: null,
|
|
1439
|
-
};
|
|
1440
|
-
// WU-1747: Check if gates can be skipped based on valid checkpoint
|
|
1441
|
-
// This allows resuming wu:done without re-running gates if nothing changed
|
|
1442
|
-
// WU-2102: Look for checkpoint in worktree (where wu:prep writes it)
|
|
1443
|
-
const skipResult = canSkipGates(id, {
|
|
1444
|
-
currentHeadSha: undefined,
|
|
1445
|
-
baseDir: worktreePath || undefined,
|
|
1446
|
-
});
|
|
1447
|
-
if (skipResult.canSkip) {
|
|
1448
|
-
gateResult.skippedByCheckpoint = true;
|
|
1449
|
-
gateResult.checkpointId = skipResult.checkpoint.checkpointId;
|
|
1450
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} ${CHECKPOINT_MESSAGES.SKIPPING_GATES_VALID}`);
|
|
1451
|
-
console.log(`${LOG_PREFIX.DONE} ${CHECKPOINT_MESSAGES.CHECKPOINT_LABEL}: ${skipResult.checkpoint.checkpointId}`);
|
|
1452
|
-
console.log(`${LOG_PREFIX.DONE} ${CHECKPOINT_MESSAGES.GATES_PASSED_AT}: ${skipResult.checkpoint.gatesPassedAt}`);
|
|
1453
|
-
emitTelemetry({
|
|
1454
|
-
script: TELEMETRY_STEPS.GATES,
|
|
1455
|
-
wu_id: id,
|
|
1456
|
-
step: TELEMETRY_STEPS.GATES,
|
|
1457
|
-
skipped: true,
|
|
1458
|
-
reason: SKIP_GATES_REASONS.CHECKPOINT_VALID,
|
|
1459
|
-
checkpoint_id: skipResult.checkpoint.checkpointId,
|
|
1460
|
-
});
|
|
1461
|
-
return gateResult; // Skip gates entirely
|
|
1462
|
-
}
|
|
1463
|
-
// WU-1747: Create checkpoint before gates for resumption on failure
|
|
1464
|
-
if (worktreePath && branchName) {
|
|
1465
|
-
try {
|
|
1466
|
-
await createWU1747Checkpoint({ wuId: id, worktreePath, branchName }, { gatesPassed: false });
|
|
1467
|
-
}
|
|
1468
|
-
catch (err) {
|
|
1469
|
-
// Non-blocking: checkpoint failure should not block wu:done
|
|
1470
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} ${CHECKPOINT_MESSAGES.COULD_NOT_CREATE}: ${getErrorMessage(err)}`);
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
// WU-1588: Create pre-gates checkpoint for recovery if gates fail
|
|
1474
|
-
// Non-blocking: failures handled internally by createPreGatesCheckpoint
|
|
1475
|
-
// WU-1749 Bug 5: Pass worktreePath as baseDir to write to worktree's wu-events.jsonl, not main's
|
|
1476
|
-
await createPreGatesCheckpoint(id, worktreePath, worktreePath || process.cwd());
|
|
1477
|
-
// P0 EMERGENCY FIX: Restore wu-events.jsonl after checkpoint creation
|
|
1478
|
-
// WU-1748 added checkpoint persistence to wu-events.jsonl but doesn't commit it,
|
|
1479
|
-
// leaving unstaged changes that cause "git rebase" to fail with "You have unstaged changes"
|
|
1480
|
-
// This restores the file to HEAD state - checkpoint data is preserved in memory store
|
|
1481
|
-
if (worktreePath) {
|
|
1482
|
-
try {
|
|
1483
|
-
execSync(`git -C "${worktreePath}" restore "${resolveWuEventsRelativePath(worktreePath)}"`);
|
|
1484
|
-
}
|
|
1485
|
-
catch {
|
|
1486
|
-
// Non-fatal: file might not exist or already clean
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
// Step 0a: Run invariants check (WU-2252: NON-BYPASSABLE, runs even with --skip-gates)
|
|
1490
|
-
// This ensures repo invariants are never violated, regardless of skip-gates flag
|
|
1491
|
-
// WU-2253: Run against worktreePath (when present) to catch violations that only exist in the worktree
|
|
1492
|
-
// WU-2425: Pass wuId to scope WU-specific invariants to just the completing WU
|
|
1493
|
-
const invariantsBaseDir = worktreePath || process.cwd();
|
|
1494
|
-
console.log(`\n${LOG_PREFIX.DONE} Running invariants check (non-bypassable)...`);
|
|
1495
|
-
console.log(`${LOG_PREFIX.DONE} Checking invariants in: ${invariantsBaseDir}`);
|
|
1496
|
-
const { runInvariants } = await import('@lumenflow/core/invariants-runner');
|
|
1497
|
-
const invariantsResult = runInvariants({ baseDir: invariantsBaseDir, silent: false, wuId: id });
|
|
1498
|
-
if (!invariantsResult.success) {
|
|
1499
|
-
emitTelemetry({
|
|
1500
|
-
script: 'wu-done',
|
|
1501
|
-
wu_id: id,
|
|
1502
|
-
step: 'invariants',
|
|
1503
|
-
ok: false,
|
|
1504
|
-
});
|
|
1505
|
-
die(`Invariants check failed. Fix violations before completing WU.\n\n${invariantsResult.formatted}`);
|
|
1506
|
-
}
|
|
1507
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Invariants check passed`);
|
|
1508
|
-
emitTelemetry({
|
|
1509
|
-
script: 'wu-done',
|
|
1510
|
-
wu_id: id,
|
|
1511
|
-
step: 'invariants',
|
|
1512
|
-
ok: true,
|
|
1513
|
-
});
|
|
1514
|
-
// Step 0b: Run gates BEFORE merge (or skip with audit trail)
|
|
1515
|
-
if (args.skipGates) {
|
|
1516
|
-
console.log(`\n${EMOJI.WARNING} ${EMOJI.WARNING} ${EMOJI.WARNING} SKIP-GATES MODE ACTIVE ${EMOJI.WARNING} ${EMOJI.WARNING} ${EMOJI.WARNING}\n`);
|
|
1517
|
-
console.log(`${LOG_PREFIX.DONE} Skipping gates check as requested`);
|
|
1518
|
-
console.log(`${LOG_PREFIX.DONE} Reason: ${args.reason}`);
|
|
1519
|
-
console.log(`${LOG_PREFIX.DONE} Fix WU: ${args.fixWu}`);
|
|
1520
|
-
console.log(`${LOG_PREFIX.DONE} Worktree: ${worktreePath || 'Branch-Only mode (no worktree)'}`);
|
|
1521
|
-
await auditSkipGates(id, args.reason, args.fixWu, worktreePath);
|
|
1522
|
-
console.log('\n⚠️ Ensure test failures are truly pre-existing!\n');
|
|
1523
|
-
emitTelemetry({
|
|
1524
|
-
script: 'wu-done',
|
|
1525
|
-
wu_id: id,
|
|
1526
|
-
step: 'gates',
|
|
1527
|
-
skipped: true,
|
|
1528
|
-
reason: args.reason,
|
|
1529
|
-
fix_wu: args.fixWu,
|
|
1530
|
-
});
|
|
1531
|
-
}
|
|
1532
|
-
else if (isBranchOnly) {
|
|
1533
|
-
// Branch-Only mode: run gates in-place (current directory on lane branch)
|
|
1534
|
-
console.log(`\n${LOG_PREFIX.DONE} Running gates in Branch-Only mode (in-place on lane branch)`);
|
|
1535
|
-
// WU-1012: Use docs-only gates if explicit --docs-only flag OR auto-detected
|
|
1536
|
-
const useDocsOnlyGates = Boolean(args.docsOnly) || Boolean(isDocsOnly);
|
|
1537
|
-
if (useDocsOnlyGates) {
|
|
1538
|
-
console.log(`${LOG_PREFIX.DONE} Using docs-only gates (skipping lint/typecheck/tests)`);
|
|
1539
|
-
if (args.docsOnly) {
|
|
1540
|
-
console.log(`${LOG_PREFIX.DONE} (explicit --docs-only flag)`);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
const startTime = Date.now();
|
|
1544
|
-
try {
|
|
1545
|
-
const ok = Boolean(await runGates({ docsOnly: useDocsOnlyGates }));
|
|
1546
|
-
if (!ok) {
|
|
1547
|
-
throw createError(ErrorCodes.GATES_FAILED, 'Gates failed');
|
|
1548
|
-
}
|
|
1549
|
-
const duration = Date.now() - startTime;
|
|
1550
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Gates passed in ${prettyMs(duration)}`);
|
|
1551
|
-
emitTelemetry({
|
|
1552
|
-
script: 'wu-done',
|
|
1553
|
-
wu_id: id,
|
|
1554
|
-
step: 'gates',
|
|
1555
|
-
ok: true,
|
|
1556
|
-
duration_ms: duration,
|
|
1557
|
-
});
|
|
1558
|
-
}
|
|
1559
|
-
catch {
|
|
1560
|
-
const duration = Date.now() - startTime;
|
|
1561
|
-
emitTelemetry({
|
|
1562
|
-
script: 'wu-done',
|
|
1563
|
-
wu_id: id,
|
|
1564
|
-
step: 'gates',
|
|
1565
|
-
ok: false,
|
|
1566
|
-
duration_ms: duration,
|
|
1567
|
-
});
|
|
1568
|
-
// WU-1280: Prominent error summary box (Branch-Only mode)
|
|
1569
|
-
// WU-1281: Extracted to helper using pretty-ms for duration formatting
|
|
1570
|
-
printGateFailureBox({
|
|
1571
|
-
id,
|
|
1572
|
-
location: 'Branch-Only',
|
|
1573
|
-
durationMs: duration,
|
|
1574
|
-
isWorktreeMode: false,
|
|
1575
|
-
});
|
|
1576
|
-
die(`Gates failed in Branch-Only mode. Fix issues and try again.`);
|
|
1577
|
-
}
|
|
1578
|
-
gateResult.fullGatesRanInCurrentRun = true;
|
|
1579
|
-
}
|
|
1580
|
-
else if (worktreePath && existsSync(worktreePath)) {
|
|
1581
|
-
// Worktree mode: run gates in the dedicated worktree
|
|
1582
|
-
// WU-1012: Pass both auto-detected and explicit docs-only flags
|
|
1583
|
-
// WU-2102: Forward scopedTestPaths so wu:done uses scoped tests when no checkpoint skip
|
|
1584
|
-
await runGatesInWorktree(worktreePath, id, {
|
|
1585
|
-
isDocsOnly,
|
|
1586
|
-
docsOnly: Boolean(args.docsOnly),
|
|
1587
|
-
scopedTestPaths,
|
|
1588
|
-
});
|
|
1589
|
-
gateResult.fullGatesRanInCurrentRun = true;
|
|
1590
|
-
}
|
|
1591
|
-
else {
|
|
1592
|
-
die(`Worktree not found (${worktreePath || 'unknown'}). Gates must run in the lane worktree.\n` +
|
|
1593
|
-
`If the worktree was removed, recreate it and retry, or rerun with --branch-only when the lane branch exists.\n` +
|
|
1594
|
-
`Use --skip-gates only with justification.`);
|
|
1595
|
-
}
|
|
1596
|
-
// Step 0.75: Run COS governance gates (WU-614, COS v1.3 §7)
|
|
1597
|
-
if (!args.skipCosGates) {
|
|
1598
|
-
console.log(`\n${LOG_PREFIX.DONE} Running COS governance gates...`);
|
|
1599
|
-
const startTime = Date.now();
|
|
1600
|
-
try {
|
|
1601
|
-
execSync(`${PKG_MANAGER} ${SCRIPTS.COS_GATES} ${CLI_FLAGS.WU} ${id}`, {
|
|
1602
|
-
stdio: 'inherit',
|
|
1603
|
-
});
|
|
1604
|
-
const duration = Date.now() - startTime;
|
|
1605
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} COS gates passed in ${prettyMs(duration)}`);
|
|
1606
|
-
emitTelemetry({
|
|
1607
|
-
script: 'wu-done',
|
|
1608
|
-
wu_id: id,
|
|
1609
|
-
step: 'cos-gates',
|
|
1610
|
-
ok: true,
|
|
1611
|
-
duration_ms: duration,
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
catch {
|
|
1615
|
-
const duration = Date.now() - startTime;
|
|
1616
|
-
emitTelemetry({
|
|
1617
|
-
script: 'wu-done',
|
|
1618
|
-
wu_id: id,
|
|
1619
|
-
step: 'cos-gates',
|
|
1620
|
-
ok: false,
|
|
1621
|
-
duration_ms: duration,
|
|
1622
|
-
});
|
|
1623
|
-
console.error(`\n${LOG_PREFIX.DONE} ${EMOJI.FAILURE} COS governance gates failed`);
|
|
1624
|
-
console.error('\nTo fix:');
|
|
1625
|
-
console.error(' 1. Add required evidence to governance.evidence field in WU YAML');
|
|
1626
|
-
console.error(' 2. See: https://lumenflow.dev/reference/evidence-format/');
|
|
1627
|
-
console.error('\nEmergency bypass (creates audit trail):');
|
|
1628
|
-
// WU-1852: Reference --skip-gates (the actual CLI flag), not the non-existent --skip-cos-gates
|
|
1629
|
-
console.error(` pnpm wu:done --id ${id} --skip-gates --reason "COS evidence pending" --fix-wu WU-XXXX`);
|
|
1630
|
-
die('Abort: WU not completed. Fix governance evidence and retry pnpm wu:done.');
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
else {
|
|
1634
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} Skipping COS governance gates as requested`);
|
|
1635
|
-
console.log(`${LOG_PREFIX.DONE} Reason: ${args.reason || DEFAULT_NO_REASON}`);
|
|
1636
|
-
await auditSkipCosGates(id, args.reason, worktreePath);
|
|
1637
|
-
emitTelemetry({
|
|
1638
|
-
script: 'wu-done',
|
|
1639
|
-
wu_id: id,
|
|
1640
|
-
step: 'cos-gates',
|
|
1641
|
-
skipped: true,
|
|
1642
|
-
reason: args.reason,
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1645
|
-
// WU-1747: Mark checkpoint as gates passed for resumption on failure
|
|
1646
|
-
// This allows subsequent wu:done attempts to skip gates if nothing changed
|
|
1647
|
-
markGatesPassed(id, { baseDir: worktreePath || undefined });
|
|
1648
|
-
return gateResult;
|
|
1649
|
-
}
|
|
1650
951
|
/**
|
|
1651
952
|
* Print State HUD for visibility
|
|
1652
953
|
* Extracted from main() to reduce complexity (WU-1215 Phase 2 Extraction #4)
|
|
@@ -1658,13 +959,6 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1658
959
|
* @param {string|null} params.derivedWorktree - Derived worktree path
|
|
1659
960
|
* @param {string} params.STAMPS_DIR - Stamps directory path
|
|
1660
961
|
*/
|
|
1661
|
-
export function computeBranchOnlyFallback({ isBranchOnly, branchOnlyRequested, worktreeExists, derivedWorktree, }) {
|
|
1662
|
-
const allowFallback = Boolean(branchOnlyRequested) && !isBranchOnly && !worktreeExists && Boolean(derivedWorktree);
|
|
1663
|
-
return {
|
|
1664
|
-
allowFallback,
|
|
1665
|
-
effectiveBranchOnly: isBranchOnly || allowFallback,
|
|
1666
|
-
};
|
|
1667
|
-
}
|
|
1668
962
|
export function getYamlStatusForDisplay(status) {
|
|
1669
963
|
return getWUStatusDisplay(status);
|
|
1670
964
|
}
|
|
@@ -1896,6 +1190,11 @@ export async function main() {
|
|
|
1896
1190
|
isDocsOnly,
|
|
1897
1191
|
worktreePath,
|
|
1898
1192
|
scopedTestPaths: scopedTestPathsForDone,
|
|
1193
|
+
}, {
|
|
1194
|
+
auditSkipGates,
|
|
1195
|
+
auditSkipCosGates,
|
|
1196
|
+
createPreGatesCheckpoint,
|
|
1197
|
+
emitTelemetry,
|
|
1899
1198
|
});
|
|
1900
1199
|
}
|
|
1901
1200
|
catch (gateErr) {
|
|
@@ -1923,183 +1222,44 @@ export async function main() {
|
|
|
1923
1222
|
derivedWorktree: effectiveDerivedWorktree,
|
|
1924
1223
|
STAMPS_DIR,
|
|
1925
1224
|
});
|
|
1926
|
-
// Step 0.5: Pre-flight
|
|
1927
|
-
|
|
1928
|
-
|
|
1225
|
+
// Step 0.5 + 0.6: Pre-flight staged validation policy and tasks:validate guard.
|
|
1226
|
+
await runWuDoneStagedValidation({
|
|
1227
|
+
id,
|
|
1228
|
+
worktreePath,
|
|
1229
|
+
gateResult: {
|
|
1230
|
+
fullGatesRanInCurrentRun: gateExecutionResult.fullGatesRanInCurrentRun,
|
|
1231
|
+
skippedByCheckpoint: gateExecutionResult.skippedByCheckpoint,
|
|
1232
|
+
checkpointId: gateExecutionResult.checkpointId,
|
|
1233
|
+
},
|
|
1929
1234
|
skipGates: Boolean(args.skipGates),
|
|
1930
|
-
|
|
1931
|
-
skippedByCheckpoint: gateExecutionResult.skippedByCheckpoint,
|
|
1932
|
-
checkpointId: gateExecutionResult.checkpointId,
|
|
1235
|
+
runGatesFn: ({ cwd }) => runGates({ cwd, docsOnly: false }),
|
|
1933
1236
|
});
|
|
1934
|
-
|
|
1935
|
-
//
|
|
1936
|
-
|
|
1937
|
-
const hookResult = await validateAllPreCommitHooks(id, worktreePath, {
|
|
1938
|
-
runGates: ({ cwd }) => runGates({ cwd, docsOnly: false }),
|
|
1939
|
-
});
|
|
1940
|
-
if (!hookResult.valid) {
|
|
1941
|
-
die('Pre-flight validation failed. Fix hook issues and try again.');
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
// Step 0.6: WU-1781 - Run tasks:validate preflight BEFORE any merge/push operations
|
|
1945
|
-
// This prevents deadlocks where validation fails after merge, leaving local main ahead of origin
|
|
1946
|
-
// Specifically catches stamp-status mismatches from legacy WUs that would block pre-push hooks
|
|
1947
|
-
const tasksValidationResult = runPreflightTasksValidation(id);
|
|
1948
|
-
if (!tasksValidationResult.valid) {
|
|
1949
|
-
const errorMessage = buildPreflightErrorMessage(id, tasksValidationResult.errors);
|
|
1950
|
-
console.error(errorMessage);
|
|
1951
|
-
die('Preflight tasks:validate failed. See errors above for fix options.');
|
|
1952
|
-
}
|
|
1953
|
-
// Step 1: Execute mode-specific completion workflow (WU-1215: extracted to mode modules)
|
|
1954
|
-
// Worktree mode: Update metadata in worktree → commit → merge to main
|
|
1955
|
-
// Branch-Only mode: Merge to main → update metadata on main → commit
|
|
1956
|
-
// WU-1811: Track cleanupSafe flag to conditionally skip worktree removal on failure
|
|
1957
|
-
let completionResult = { cleanupSafe: true }; // Default to safe for no-auto mode
|
|
1237
|
+
// Step 1: Execute mode-specific completion workflow (WU-2167)
|
|
1238
|
+
// Main remains orchestration-only; execution details live in wu-done-mode-execution.ts.
|
|
1239
|
+
let completionResult = { cleanupSafe: true };
|
|
1958
1240
|
if (!args.noAuto) {
|
|
1959
|
-
|
|
1960
|
-
// WU-1369: Worktree mode uses atomic transaction pattern (no recordTransactionState/rollbackTransaction)
|
|
1961
|
-
// Branch-only mode still uses the old rollback mechanism
|
|
1962
|
-
const baseContext = {
|
|
1241
|
+
completionResult = await executeModeSpecificCompletion({
|
|
1963
1242
|
id,
|
|
1964
1243
|
args,
|
|
1965
1244
|
docMain,
|
|
1966
1245
|
title,
|
|
1967
1246
|
isDocsOnly,
|
|
1968
1247
|
maxCommitLength: getCommitHeaderLimit(),
|
|
1248
|
+
isBranchPR,
|
|
1249
|
+
effectiveBranchOnly,
|
|
1250
|
+
worktreePath,
|
|
1251
|
+
resolvedWorktreePath,
|
|
1252
|
+
pipelineActor: {
|
|
1253
|
+
send: (event) => pipelineActor.send(event),
|
|
1254
|
+
stop: () => pipelineActor.stop(),
|
|
1255
|
+
getSnapshot: () => pipelineActor.getSnapshot(),
|
|
1256
|
+
},
|
|
1969
1257
|
validateStagedFiles,
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
const laneBranch = defaultBranchFrom(docMain);
|
|
1976
|
-
const branchPRContext = {
|
|
1977
|
-
...baseContext,
|
|
1978
|
-
laneBranch,
|
|
1979
|
-
};
|
|
1980
|
-
completionResult = await executeBranchPRCompletion(branchPRContext);
|
|
1981
|
-
}
|
|
1982
|
-
else if (effectiveBranchOnly) {
|
|
1983
|
-
// Branch-Only mode: merge first, then update metadata on main
|
|
1984
|
-
// NOTE: Branch-only still uses old rollback mechanism
|
|
1985
|
-
const branchOnlyContext = {
|
|
1986
|
-
...baseContext,
|
|
1987
|
-
recordTransactionState,
|
|
1988
|
-
rollbackTransaction,
|
|
1989
|
-
};
|
|
1990
|
-
completionResult = await executeBranchOnlyCompletion(branchOnlyContext);
|
|
1991
|
-
}
|
|
1992
|
-
else {
|
|
1993
|
-
// Worktree mode: update in worktree, commit, then merge or create PR
|
|
1994
|
-
// WU-1369: Uses atomic transaction pattern
|
|
1995
|
-
// WU-1541: Create worktree-aware validateStagedFiles to avoid process.chdir dependency
|
|
1996
|
-
if (!worktreePath) {
|
|
1997
|
-
// WU-1746: Before dying, check if branch is already merged to main
|
|
1998
|
-
// This handles the case where worktree was manually deleted after branch was merged
|
|
1999
|
-
const laneBranch = defaultBranchFrom(docMain);
|
|
2000
|
-
const mergedDetection = await detectAlreadyMergedNoWorktree({
|
|
2001
|
-
wuId: id,
|
|
2002
|
-
laneBranch: laneBranch || '',
|
|
2003
|
-
worktreePath: resolvedWorktreePath,
|
|
2004
|
-
});
|
|
2005
|
-
if (mergedDetection.merged && !mergedDetection.worktreeExists) {
|
|
2006
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} WU-1746: Worktree missing but branch already merged to main`);
|
|
2007
|
-
// WU-2248: Use micro-worktree isolation (same as --already-merged flag path)
|
|
2008
|
-
// Previously called executeAlreadyMergedCompletion which wrote directly to local main.
|
|
2009
|
-
const mergedTitle = title || String(docMain.title || id);
|
|
2010
|
-
const mergedResult = await executeAlreadyMergedFinalizeFromModule({
|
|
2011
|
-
id,
|
|
2012
|
-
title: mergedTitle,
|
|
2013
|
-
lane: String(docMain.lane || ''),
|
|
2014
|
-
doc: docMain,
|
|
2015
|
-
});
|
|
2016
|
-
completionResult = {
|
|
2017
|
-
success: mergedResult.success,
|
|
2018
|
-
committed: true,
|
|
2019
|
-
pushed: true,
|
|
2020
|
-
merged: true,
|
|
2021
|
-
cleanupSafe: true,
|
|
2022
|
-
};
|
|
2023
|
-
}
|
|
2024
|
-
else {
|
|
2025
|
-
die(`Missing worktree path for ${id} completion in worktree mode`);
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
else {
|
|
2029
|
-
const worktreeGitForValidation = createGitForPath(worktreePath);
|
|
2030
|
-
const worktreeContext = {
|
|
2031
|
-
...baseContext,
|
|
2032
|
-
worktreePath,
|
|
2033
|
-
validateStagedFiles: (wuId, docsOnly, options) => validateStagedFiles(wuId, docsOnly, worktreeGitForValidation, options),
|
|
2034
|
-
};
|
|
2035
|
-
completionResult = await executeWorktreeCompletion(worktreeContext);
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
// WU-1663: Mode-specific completion succeeded - send pipeline events.
|
|
2039
|
-
// The completion modules handle commit, merge, and push internally.
|
|
2040
|
-
// We send the corresponding pipeline events based on the completion result.
|
|
2041
|
-
pipelineActor.send({ type: WU_DONE_EVENTS.COMMIT_COMPLETE });
|
|
2042
|
-
pipelineActor.send({ type: WU_DONE_EVENTS.MERGE_COMPLETE });
|
|
2043
|
-
pipelineActor.send({ type: WU_DONE_EVENTS.PUSH_COMPLETE });
|
|
2044
|
-
// Handle recovery mode (zombie state cleanup completed)
|
|
2045
|
-
if ('recovered' in completionResult && completionResult.recovered) {
|
|
2046
|
-
// P0 FIX: Release lane lock before early exit
|
|
2047
|
-
try {
|
|
2048
|
-
const lane = docMain.lane;
|
|
2049
|
-
if (lane)
|
|
2050
|
-
releaseLaneLock(lane, { wuId: id });
|
|
2051
|
-
}
|
|
2052
|
-
catch {
|
|
2053
|
-
// Intentionally ignore lock release errors during cleanup
|
|
2054
|
-
}
|
|
2055
|
-
pipelineActor.stop();
|
|
2056
|
-
process.exit(EXIT_CODES.SUCCESS);
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
catch (err) {
|
|
2060
|
-
// WU-1663: Mode execution failed - determine which stage failed
|
|
2061
|
-
// based on completion result flags and send appropriate failure event.
|
|
2062
|
-
const failureStage = completionResult.committed === false
|
|
2063
|
-
? WU_DONE_EVENTS.COMMIT_FAILED
|
|
2064
|
-
: completionResult.merged === false
|
|
2065
|
-
? WU_DONE_EVENTS.MERGE_FAILED
|
|
2066
|
-
: completionResult.pushed === false
|
|
2067
|
-
? WU_DONE_EVENTS.PUSH_FAILED
|
|
2068
|
-
: WU_DONE_EVENTS.COMMIT_FAILED; // Default to commit as earliest possible failure
|
|
2069
|
-
pipelineActor.send({
|
|
2070
|
-
type: failureStage,
|
|
2071
|
-
error: getErrorMessage(err),
|
|
2072
|
-
});
|
|
2073
|
-
// WU-1663: Log pipeline state for diagnostics
|
|
2074
|
-
const failedSnapshot = pipelineActor.getSnapshot();
|
|
2075
|
-
console.error(`${LOG_PREFIX.DONE} Pipeline state: ${failedSnapshot.value} (failedAt: ${failedSnapshot.context.failedAt})`);
|
|
2076
|
-
pipelineActor.stop();
|
|
2077
|
-
// P0 FIX: Release lane lock before error exit
|
|
2078
|
-
try {
|
|
2079
|
-
const lane = docMain.lane;
|
|
2080
|
-
if (lane)
|
|
2081
|
-
releaseLaneLock(lane, { wuId: id });
|
|
2082
|
-
}
|
|
2083
|
-
catch {
|
|
2084
|
-
// Intentionally ignore lock release errors during error handling
|
|
2085
|
-
}
|
|
2086
|
-
console.error(`\n${LOG_PREFIX.DONE} ${EMOJI.FAILURE} Mode execution failed: ${getErrorMessage(err)}`);
|
|
2087
|
-
console.error(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Next step: resolve the reported error and retry: pnpm wu:done --id ${id}`);
|
|
2088
|
-
// WU-1811: Check if cleanup is safe before removing worktree
|
|
2089
|
-
// If cleanupSafe is false (or undefined), preserve worktree for recovery
|
|
2090
|
-
const cleanupSafe = typeof err === 'object' &&
|
|
2091
|
-
err !== null &&
|
|
2092
|
-
'cleanupSafe' in err &&
|
|
2093
|
-
typeof err.cleanupSafe === 'boolean'
|
|
2094
|
-
? err.cleanupSafe
|
|
2095
|
-
: undefined;
|
|
2096
|
-
if (cleanupSafe === false) {
|
|
2097
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-1811: Worktree preserved - rerun wu:done to recover`);
|
|
2098
|
-
}
|
|
2099
|
-
// Mode modules handle rollback internally, we just need to exit
|
|
2100
|
-
// Exit code 1 = recoverable (rebase/fix and retry)
|
|
2101
|
-
process.exit(EXIT_CODES.ERROR);
|
|
2102
|
-
}
|
|
1258
|
+
defaultBranchFrom: (doc) => defaultBranchFrom(doc),
|
|
1259
|
+
executeAlreadyMergedFinalize: executeAlreadyMergedFinalizeFromModule,
|
|
1260
|
+
recordTransactionState,
|
|
1261
|
+
rollbackTransaction,
|
|
1262
|
+
});
|
|
2103
1263
|
}
|
|
2104
1264
|
else {
|
|
2105
1265
|
await ensureNoAutoStagedOrNoop([WU_PATH, STATUS_PATH, BACKLOG_PATH, STAMPS_DIR]);
|