@lumenflow/cli 2.18.2 → 2.19.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/README.md +42 -41
- package/dist/delegation-list.js +140 -0
- package/dist/delegation-list.js.map +1 -0
- package/dist/doctor.js +35 -99
- package/dist/doctor.js.map +1 -1
- package/dist/gates-plan-resolvers.js +150 -0
- package/dist/gates-plan-resolvers.js.map +1 -0
- package/dist/gates-runners.js +533 -0
- package/dist/gates-runners.js.map +1 -0
- package/dist/gates-types.js +3 -0
- package/dist/gates-types.js.map +1 -1
- package/dist/gates-utils.js +316 -0
- package/dist/gates-utils.js.map +1 -0
- package/dist/gates.js +44 -1016
- package/dist/gates.js.map +1 -1
- package/dist/hooks/enforcement-generator.js +16 -880
- package/dist/hooks/enforcement-generator.js.map +1 -1
- package/dist/hooks/enforcement-sync.js +1 -4
- package/dist/hooks/enforcement-sync.js.map +1 -1
- package/dist/hooks/generators/auto-checkpoint.js +123 -0
- package/dist/hooks/generators/auto-checkpoint.js.map +1 -0
- package/dist/hooks/generators/enforce-worktree.js +188 -0
- package/dist/hooks/generators/enforce-worktree.js.map +1 -0
- package/dist/hooks/generators/index.js +16 -0
- package/dist/hooks/generators/index.js.map +1 -0
- package/dist/hooks/generators/pre-compact-checkpoint.js +134 -0
- package/dist/hooks/generators/pre-compact-checkpoint.js.map +1 -0
- package/dist/hooks/generators/require-wu.js +115 -0
- package/dist/hooks/generators/require-wu.js.map +1 -0
- package/dist/hooks/generators/session-start-recovery.js +101 -0
- package/dist/hooks/generators/session-start-recovery.js.map +1 -0
- package/dist/hooks/generators/signal-utils.js +52 -0
- package/dist/hooks/generators/signal-utils.js.map +1 -0
- package/dist/hooks/generators/warn-incomplete.js +65 -0
- package/dist/hooks/generators/warn-incomplete.js.map +1 -0
- package/dist/init-detection.js +228 -0
- package/dist/init-detection.js.map +1 -0
- package/dist/init-scaffolding.js +146 -0
- package/dist/init-scaffolding.js.map +1 -0
- package/dist/init-templates.js +1928 -0
- package/dist/init-templates.js.map +1 -0
- package/dist/init.js +136 -2425
- package/dist/init.js.map +1 -1
- package/dist/initiative-edit.js +42 -11
- package/dist/initiative-edit.js.map +1 -1
- package/dist/initiative-remove-wu.js +0 -0
- package/dist/initiative-status.js +29 -2
- package/dist/initiative-status.js.map +1 -1
- package/dist/mem-context.js +22 -9
- package/dist/mem-context.js.map +1 -1
- package/dist/orchestrate-init-status.js +32 -1
- package/dist/orchestrate-init-status.js.map +1 -1
- package/dist/orchestrate-monitor.js +38 -38
- package/dist/orchestrate-monitor.js.map +1 -1
- package/dist/public-manifest.js +12 -5
- package/dist/public-manifest.js.map +1 -1
- package/dist/shared-validators.js +1 -0
- package/dist/shared-validators.js.map +1 -1
- package/dist/spawn-list.js +0 -0
- package/dist/wu-claim-branch.js +121 -0
- package/dist/wu-claim-branch.js.map +1 -0
- package/dist/wu-claim-output.js +83 -0
- package/dist/wu-claim-output.js.map +1 -0
- package/dist/wu-claim-resume-handler.js +85 -0
- package/dist/wu-claim-resume-handler.js.map +1 -0
- package/dist/wu-claim-state.js +572 -0
- package/dist/wu-claim-state.js.map +1 -0
- package/dist/wu-claim-validation.js +439 -0
- package/dist/wu-claim-validation.js.map +1 -0
- package/dist/wu-claim-worktree.js +221 -0
- package/dist/wu-claim-worktree.js.map +1 -0
- package/dist/wu-claim.js +54 -1402
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-create-content.js +254 -0
- package/dist/wu-create-content.js.map +1 -0
- package/dist/wu-create-readiness.js +57 -0
- package/dist/wu-create-readiness.js.map +1 -0
- package/dist/wu-create-validation.js +149 -0
- package/dist/wu-create-validation.js.map +1 -0
- package/dist/wu-create.js +39 -441
- package/dist/wu-create.js.map +1 -1
- package/dist/wu-done.js +144 -249
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-edit-operations.js +432 -0
- package/dist/wu-edit-operations.js.map +1 -0
- package/dist/wu-edit-validators.js +280 -0
- package/dist/wu-edit-validators.js.map +1 -0
- package/dist/wu-edit.js +27 -713
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-prep.js +32 -2
- package/dist/wu-prep.js.map +1 -1
- package/dist/wu-repair.js +1 -1
- package/dist/wu-repair.js.map +1 -1
- package/dist/wu-spawn-prompt-builders.js +1123 -0
- package/dist/wu-spawn-prompt-builders.js.map +1 -0
- package/dist/wu-spawn-strategy-resolver.js +314 -0
- package/dist/wu-spawn-strategy-resolver.js.map +1 -0
- package/dist/wu-spawn.js +9 -1398
- package/dist/wu-spawn.js.map +1 -1
- package/package.json +10 -7
- package/templates/core/LUMENFLOW.md.template +29 -99
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +1 -1
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +29 -4
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +8 -8
package/dist/wu-done.js
CHANGED
|
@@ -33,12 +33,16 @@
|
|
|
33
33
|
*/
|
|
34
34
|
// WU-2542: Import from @lumenflow/core to establish shim layer dependency
|
|
35
35
|
import '@lumenflow/core';
|
|
36
|
+
// WU-1663: XState pipeline actor for state-driven orchestration
|
|
37
|
+
import { createActor } from 'xstate';
|
|
38
|
+
import { wuDoneMachine, WU_DONE_EVENTS } from '@lumenflow/core/wu-done-machine';
|
|
36
39
|
// WU-1153: wu:done guard for uncommitted code_paths is implemented in core package
|
|
37
40
|
// The guard runs in executeWorktreeCompletion() before metadata transaction
|
|
38
41
|
// See: packages/@lumenflow/core/src/wu-done-validation.ts
|
|
39
42
|
import { execSync } from 'node:child_process';
|
|
40
43
|
import prettyMs from 'pretty-ms';
|
|
41
44
|
import { runGates } from './gates.js';
|
|
45
|
+
import { resolveWuDonePreCommitGateDecision } from '@lumenflow/core/gates-agent-mode';
|
|
42
46
|
import { buildClaimRepairCommand } from './wu-claim-repair-guidance.js';
|
|
43
47
|
import { getGitForCwd, createGitForPath } from '@lumenflow/core/git-adapter';
|
|
44
48
|
import { die, getErrorMessage } from '@lumenflow/core/error-handler';
|
|
@@ -59,9 +63,7 @@ executePreflightCodePathValidation, buildPreflightCodePathErrorMessage,
|
|
|
59
63
|
// WU-2308: Pre-commit hooks with worktree context
|
|
60
64
|
validateAllPreCommitHooks,
|
|
61
65
|
// WU-2310: Type vs code_paths preflight validation
|
|
62
|
-
validateTypeVsCodePathsPreflight, buildTypeVsCodePathsErrorMessage,
|
|
63
|
-
// WU-1503: Dirty-main pre-merge guard
|
|
64
|
-
validateDirtyMain, buildDirtyMainErrorMessage, } from '@lumenflow/core/wu-done-validators';
|
|
66
|
+
validateTypeVsCodePathsPreflight, buildTypeVsCodePathsErrorMessage, } from '@lumenflow/core/wu-done-validators';
|
|
65
67
|
// WU-1825: validateCodePathsExist moved to unified code-path-validator
|
|
66
68
|
import { validateCodePathsExist } from '@lumenflow/core/code-path-validator';
|
|
67
69
|
import { BRANCHES, REMOTES, PATTERNS, DEFAULTS, LOG_PREFIX, EMOJI, GIT, SESSION, WU_STATUS, WU_EXPOSURE, PKG_MANAGER, SCRIPTS, CLI_FLAGS, FILE_SYSTEM, EXIT_CODES, STRING_LITERALS, MICRO_WORKTREE_OPERATIONS, TELEMETRY_STEPS, SKIP_GATES_REASONS, CHECKPOINT_MESSAGES, LUMENFLOW_PATHS, getWUStatusDisplay,
|
|
@@ -97,8 +99,8 @@ import { releaseLaneLock } from '@lumenflow/core/lane-lock';
|
|
|
97
99
|
// WU-1747: Checkpoint and lock for concurrent load resilience
|
|
98
100
|
import { createPreGatesCheckpoint as createWU1747Checkpoint, markGatesPassed, canSkipGates, clearCheckpoint, } from '@lumenflow/core/wu-checkpoint';
|
|
99
101
|
// WU-1946: Spawn registry for tracking sub-agent spawns
|
|
100
|
-
import {
|
|
101
|
-
import {
|
|
102
|
+
import { DelegationRegistryStore } from '@lumenflow/core/delegation-registry-store';
|
|
103
|
+
import { DelegationStatus } from '@lumenflow/core/delegation-registry-schema';
|
|
102
104
|
// WU-1999: Exposure validation for UI pairing
|
|
103
105
|
// WU-2022: Feature accessibility validation (blocking)
|
|
104
106
|
import { validateExposure, validateFeatureAccessibility } from '@lumenflow/core/wu-validation';
|
|
@@ -454,7 +456,7 @@ export async function enforceSpawnProvenanceForDone(id, doc, options = {}) {
|
|
|
454
456
|
const initiativeId = doc.initiative.trim();
|
|
455
457
|
const baseDir = options.baseDir ?? process.cwd();
|
|
456
458
|
const force = options.force === true;
|
|
457
|
-
const store = new
|
|
459
|
+
const store = new DelegationRegistryStore(path.join(baseDir, '.lumenflow', 'state'));
|
|
458
460
|
await store.load();
|
|
459
461
|
const spawnEntry = store.getByTarget(id);
|
|
460
462
|
if (!spawnEntry) {
|
|
@@ -493,7 +495,7 @@ export async function enforceSpawnProvenanceForDone(id, doc, options = {}) {
|
|
|
493
495
|
*/
|
|
494
496
|
export async function updateSpawnRegistryOnCompletion(id, baseDir = process.cwd()) {
|
|
495
497
|
try {
|
|
496
|
-
const store = new
|
|
498
|
+
const store = new DelegationRegistryStore(path.join(baseDir, '.lumenflow', 'state'));
|
|
497
499
|
await store.load();
|
|
498
500
|
const spawnEntry = store.getByTarget(id);
|
|
499
501
|
// Graceful skip if no spawn entry found (legacy WU)
|
|
@@ -502,7 +504,7 @@ export async function updateSpawnRegistryOnCompletion(id, baseDir = process.cwd(
|
|
|
502
504
|
return;
|
|
503
505
|
}
|
|
504
506
|
// Update status to completed with completedAt timestamp
|
|
505
|
-
await store.updateStatus(spawnEntry.id,
|
|
507
|
+
await store.updateStatus(spawnEntry.id, DelegationStatus.COMPLETED);
|
|
506
508
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Spawn registry updated: ${id} marked as completed`);
|
|
507
509
|
}
|
|
508
510
|
catch (err) {
|
|
@@ -655,118 +657,6 @@ async function _ensureCleanWorkingTree() {
|
|
|
655
657
|
` - Leftover changes from previous session`);
|
|
656
658
|
}
|
|
657
659
|
}
|
|
658
|
-
const INTERNAL_LIFECYCLE_DIRTY_FILES = new Set([
|
|
659
|
-
'.lumenflow/flow.log',
|
|
660
|
-
'.lumenflow/skip-gates-audit.log',
|
|
661
|
-
'.lumenflow/skip-cos-gates-audit.log',
|
|
662
|
-
]);
|
|
663
|
-
/**
|
|
664
|
-
* WU-1554: Prefixes for metadata files that wu:done manages.
|
|
665
|
-
* These files may be dirty after merge+rebase when wu:claim used push-only mode
|
|
666
|
-
* or when concurrent agents advance origin/main. Instead of blocking wu:done,
|
|
667
|
-
* these are auto-committed by the caller.
|
|
668
|
-
*/
|
|
669
|
-
const METADATA_LIFECYCLE_PREFIXES = [
|
|
670
|
-
'.lumenflow/state/',
|
|
671
|
-
'.lumenflow/stamps/',
|
|
672
|
-
'.lumenflow/archive/',
|
|
673
|
-
];
|
|
674
|
-
const PROTECTED_MAIN_LIKE_BRANCHES = new Set([BRANCHES.MAIN, BRANCHES.MASTER]);
|
|
675
|
-
export function isProtectedMainLikeBranch(branchName) {
|
|
676
|
-
if (!branchName) {
|
|
677
|
-
return false;
|
|
678
|
-
}
|
|
679
|
-
return PROTECTED_MAIN_LIKE_BRANCHES.has(branchName.trim());
|
|
680
|
-
}
|
|
681
|
-
export function shouldAutoCommitLifecycleWrites(branchName) {
|
|
682
|
-
if (!branchName || branchName.trim().length === 0) {
|
|
683
|
-
// Fail-safe: unknown branch should never trigger direct lifecycle commits.
|
|
684
|
-
return false;
|
|
685
|
-
}
|
|
686
|
-
return !isProtectedMainLikeBranch(branchName);
|
|
687
|
-
}
|
|
688
|
-
function parsePorcelainPath(line) {
|
|
689
|
-
if (line.length < 4)
|
|
690
|
-
return null;
|
|
691
|
-
const pathPart = line.slice(3).trim();
|
|
692
|
-
if (!pathPart)
|
|
693
|
-
return null;
|
|
694
|
-
const renameIndex = pathPart.indexOf(' -> ');
|
|
695
|
-
if (renameIndex !== -1) {
|
|
696
|
-
return pathPart.slice(renameIndex + 4);
|
|
697
|
-
}
|
|
698
|
-
return pathPart;
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* WU-1554: Check if a file is a metadata lifecycle file.
|
|
702
|
-
* Metadata files are managed by wu:done (wu-events.jsonl, status.md, backlog.md,
|
|
703
|
-
* WU YAML, stamps, archives) and may be dirty after merge+rebase.
|
|
704
|
-
*/
|
|
705
|
-
function isMetadataLifecycleFile(filePath, metadataDir) {
|
|
706
|
-
if (METADATA_LIFECYCLE_PREFIXES.some((prefix) => filePath.startsWith(prefix))) {
|
|
707
|
-
return true;
|
|
708
|
-
}
|
|
709
|
-
if (metadataDir && filePath.startsWith(metadataDir + '/')) {
|
|
710
|
-
return true;
|
|
711
|
-
}
|
|
712
|
-
return false;
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* WU-1084: Check for uncommitted changes on main after merge completes.
|
|
716
|
-
*
|
|
717
|
-
* This catches cases where pnpm format (or other tooling) touched files
|
|
718
|
-
* outside the WU's code_paths during worktree work. These changes survive
|
|
719
|
-
* the merge and would be silently left behind when the worktree is removed.
|
|
720
|
-
*
|
|
721
|
-
* WU-1554: Added metadataDir parameter and metadataFiles return field.
|
|
722
|
-
* Metadata files (wu-events.jsonl, status.md, backlog.md, WU YAML, stamps)
|
|
723
|
-
* may be dirty after merge+rebase when wu:claim used push-only mode.
|
|
724
|
-
* These are returned separately for auto-commit by the caller.
|
|
725
|
-
*
|
|
726
|
-
* @param gitStatus - Output from git status (porcelain format)
|
|
727
|
-
* @param wuId - The WU ID for error messaging
|
|
728
|
-
* @param metadataDir - Optional task directory prefix for metadata file detection
|
|
729
|
-
* @returns Object with isDirty flag, file categories, and optional error message
|
|
730
|
-
*/
|
|
731
|
-
export function checkPostMergeDirtyState(gitStatus, wuId, metadataDir) {
|
|
732
|
-
// WU-1522: Split before trimming to preserve leading spaces in porcelain format.
|
|
733
|
-
// Porcelain lines like ' M .lumenflow/flow.log' use the leading space as a status
|
|
734
|
-
// indicator (working tree vs staging area). Trimming the whole string first strips
|
|
735
|
-
// the leading space from the first line, corrupting parsePorcelainPath output.
|
|
736
|
-
const lines = gitStatus.split('\n').filter((line) => line.length >= 4);
|
|
737
|
-
if (lines.length === 0) {
|
|
738
|
-
return { isDirty: false, internalOnlyFiles: [], metadataFiles: [], unrelatedFiles: [] };
|
|
739
|
-
}
|
|
740
|
-
const dirtyFiles = lines
|
|
741
|
-
.map((line) => parsePorcelainPath(line))
|
|
742
|
-
.filter((value) => Boolean(value));
|
|
743
|
-
// WU-1554: Three-category classification
|
|
744
|
-
const internalOnlyFiles = dirtyFiles.filter((file) => INTERNAL_LIFECYCLE_DIRTY_FILES.has(file));
|
|
745
|
-
const metadataFiles = dirtyFiles.filter((file) => !INTERNAL_LIFECYCLE_DIRTY_FILES.has(file) && isMetadataLifecycleFile(file, metadataDir));
|
|
746
|
-
const unrelatedFiles = dirtyFiles.filter((file) => !INTERNAL_LIFECYCLE_DIRTY_FILES.has(file) && !isMetadataLifecycleFile(file, metadataDir));
|
|
747
|
-
if (unrelatedFiles.length === 0) {
|
|
748
|
-
return { isDirty: false, internalOnlyFiles, metadataFiles, unrelatedFiles: [] };
|
|
749
|
-
}
|
|
750
|
-
const displayStatus = gitStatus.trim();
|
|
751
|
-
const error = `Main branch has uncommitted changes after merge:\n\n${displayStatus}\n\n` +
|
|
752
|
-
`This indicates files were modified outside the WU's code_paths.\n` +
|
|
753
|
-
`Common cause: pnpm format touched files outside the WU scope.\n\n` +
|
|
754
|
-
`The worktree has NOT been removed to allow investigation.\n\n` +
|
|
755
|
-
`Options:\n` +
|
|
756
|
-
` 1. Review and commit the changes: git add . && git commit -m "format: fix formatting"\n` +
|
|
757
|
-
` 2. Discard if unwanted: git checkout -- .\n` +
|
|
758
|
-
` 3. Then re-run: pnpm wu:done --id ${wuId} --skip-worktree-completion`;
|
|
759
|
-
return { isDirty: true, internalOnlyFiles, metadataFiles, unrelatedFiles, error };
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Build the list of lifecycle files that are safe to restore after a failed wu:done attempt.
|
|
763
|
-
*
|
|
764
|
-
* On failure, wu:done can leave internal lifecycle logs and metadata lifecycle files dirty on main.
|
|
765
|
-
* These files are managed by lifecycle tooling and should not block re-claim/retry.
|
|
766
|
-
*/
|
|
767
|
-
export function getLifecycleRestoreTargetsOnFailure(dirtyState) {
|
|
768
|
-
return Array.from(new Set([...dirtyState.internalOnlyFiles, ...dirtyState.metadataFiles]));
|
|
769
|
-
}
|
|
770
660
|
/**
|
|
771
661
|
* Extract completed WU IDs from git log output.
|
|
772
662
|
* @param {string} logOutput - Git log output (one commit per line)
|
|
@@ -1710,42 +1600,6 @@ async function executePreFlightChecks({ id, args, isBranchOnly, isDocsOnly, docM
|
|
|
1710
1600
|
else {
|
|
1711
1601
|
// Worktree mode: must be on main
|
|
1712
1602
|
await ensureOnMain(getGitForCwd());
|
|
1713
|
-
// WU-1503: Dirty-main pre-merge guard (replaces blanket ensureCleanWorkingTree)
|
|
1714
|
-
// Distinguishes between WU-related dirty files (allowed) and unrelated dirty
|
|
1715
|
-
// files (blocked with actionable guidance). Uses --force for audited bypass.
|
|
1716
|
-
{
|
|
1717
|
-
const gitAdapter = getGitForCwd();
|
|
1718
|
-
const gitStatus = await gitAdapter.raw(['status', '--porcelain']);
|
|
1719
|
-
if (gitStatus && gitStatus.trim()) {
|
|
1720
|
-
const wuCodePaths = docForValidation.code_paths || [];
|
|
1721
|
-
const dirtyResult = validateDirtyMain(gitStatus, id, wuCodePaths);
|
|
1722
|
-
if (!dirtyResult.valid) {
|
|
1723
|
-
if (args.force) {
|
|
1724
|
-
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-1503: Dirty-main guard bypassed with --force`);
|
|
1725
|
-
console.log(`${LOG_PREFIX.DONE} Unrelated dirty files (${dirtyResult.unrelatedFiles.length}):`);
|
|
1726
|
-
for (const f of dirtyResult.unrelatedFiles) {
|
|
1727
|
-
console.log(`${LOG_PREFIX.DONE} - ${f}`);
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
else {
|
|
1731
|
-
die(buildDirtyMainErrorMessage(id, dirtyResult.unrelatedFiles));
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
else if (dirtyResult.relatedFiles.length > 0) {
|
|
1735
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} WU-1503: ${dirtyResult.relatedFiles.length} related dirty file(s) on main (allowed)`);
|
|
1736
|
-
// WU-1554: Auto-restore related dirty files so ff-only merge can proceed.
|
|
1737
|
-
// These files will be overwritten by the merge commit anyway.
|
|
1738
|
-
// Without this, git merge --ff-only refuses to overwrite dirty tracked files.
|
|
1739
|
-
try {
|
|
1740
|
-
await gitAdapter.raw(['checkout', '--', ...dirtyResult.relatedFiles]);
|
|
1741
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} WU-1554: Auto-restored ${dirtyResult.relatedFiles.length} related file(s) for clean merge`);
|
|
1742
|
-
}
|
|
1743
|
-
catch (restoreErr) {
|
|
1744
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} WU-1554: Could not auto-restore related files: ${restoreErr.message}`);
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
1603
|
// Prevent coordination failures by ensuring main is up-to-date
|
|
1750
1604
|
await ensureMainUpToDate();
|
|
1751
1605
|
// P0 EMERGENCY FIX Part 1: Restore wu-events.jsonl BEFORE parallel completion check
|
|
@@ -1939,10 +1793,17 @@ async function executePreFlightChecks({ id, args, isBranchOnly, isDocsOnly, docM
|
|
|
1939
1793
|
return { title, docForValidation };
|
|
1940
1794
|
}
|
|
1941
1795
|
async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath, branchName, }) {
|
|
1796
|
+
const gateResult = {
|
|
1797
|
+
fullGatesRanInCurrentRun: false,
|
|
1798
|
+
skippedByCheckpoint: false,
|
|
1799
|
+
checkpointId: null,
|
|
1800
|
+
};
|
|
1942
1801
|
// WU-1747: Check if gates can be skipped based on valid checkpoint
|
|
1943
1802
|
// This allows resuming wu:done without re-running gates if nothing changed
|
|
1944
1803
|
const skipResult = canSkipGates(id, { currentHeadSha: undefined });
|
|
1945
1804
|
if (skipResult.canSkip) {
|
|
1805
|
+
gateResult.skippedByCheckpoint = true;
|
|
1806
|
+
gateResult.checkpointId = skipResult.checkpoint.checkpointId;
|
|
1946
1807
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} ${CHECKPOINT_MESSAGES.SKIPPING_GATES_VALID}`);
|
|
1947
1808
|
console.log(`${LOG_PREFIX.DONE} ${CHECKPOINT_MESSAGES.CHECKPOINT_LABEL}: ${skipResult.checkpoint.checkpointId}`);
|
|
1948
1809
|
console.log(`${LOG_PREFIX.DONE} ${CHECKPOINT_MESSAGES.GATES_PASSED_AT}: ${skipResult.checkpoint.gatesPassedAt}`);
|
|
@@ -1954,7 +1815,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
1954
1815
|
reason: SKIP_GATES_REASONS.CHECKPOINT_VALID,
|
|
1955
1816
|
checkpoint_id: skipResult.checkpoint.checkpointId,
|
|
1956
1817
|
});
|
|
1957
|
-
return; // Skip gates entirely
|
|
1818
|
+
return gateResult; // Skip gates entirely
|
|
1958
1819
|
}
|
|
1959
1820
|
// WU-1747: Create checkpoint before gates for resumption on failure
|
|
1960
1821
|
if (worktreePath && branchName) {
|
|
@@ -2071,6 +1932,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
2071
1932
|
});
|
|
2072
1933
|
die(`Gates failed in Branch-Only mode. Fix issues and try again.`);
|
|
2073
1934
|
}
|
|
1935
|
+
gateResult.fullGatesRanInCurrentRun = true;
|
|
2074
1936
|
}
|
|
2075
1937
|
else if (worktreePath && existsSync(worktreePath)) {
|
|
2076
1938
|
// Worktree mode: run gates in the dedicated worktree
|
|
@@ -2079,6 +1941,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
2079
1941
|
isDocsOnly,
|
|
2080
1942
|
docsOnly: Boolean(args.docsOnly),
|
|
2081
1943
|
});
|
|
1944
|
+
gateResult.fullGatesRanInCurrentRun = true;
|
|
2082
1945
|
}
|
|
2083
1946
|
else {
|
|
2084
1947
|
die(`Worktree not found (${worktreePath || 'unknown'}). Gates must run in the lane worktree.\n` +
|
|
@@ -2136,6 +1999,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
|
|
|
2136
1999
|
// WU-1747: Mark checkpoint as gates passed for resumption on failure
|
|
2137
2000
|
// This allows subsequent wu:done attempts to skip gates if nothing changed
|
|
2138
2001
|
markGatesPassed(id);
|
|
2002
|
+
return gateResult;
|
|
2139
2003
|
}
|
|
2140
2004
|
/**
|
|
2141
2005
|
* Print State HUD for visibility
|
|
@@ -2194,6 +2058,29 @@ async function main() {
|
|
|
2194
2058
|
isBranchPR, derivedWorktree, docForValidation: initialDocForValidation, isDocsOnly, } = pathInfo;
|
|
2195
2059
|
// Capture main checkout path once. process.cwd() may drift later during recovery flows.
|
|
2196
2060
|
const mainCheckoutPath = process.cwd();
|
|
2061
|
+
// WU-1663: Determine prepPassed early for pipeline actor input.
|
|
2062
|
+
// canSkipGates checks if wu:prep already ran gates successfully via checkpoint.
|
|
2063
|
+
// This drives the isPrepPassed guard on the GATES_SKIPPED transition.
|
|
2064
|
+
const earlySkipResult = canSkipGates(id, { currentHeadSha: undefined });
|
|
2065
|
+
const prepPassed = earlySkipResult.canSkip;
|
|
2066
|
+
// WU-1663: Create XState pipeline actor for state-driven orchestration.
|
|
2067
|
+
// The actor tracks which pipeline stage we're in (validating, gating, committing, etc.)
|
|
2068
|
+
// and provides explicit state/transition contracts. Existing procedural logic continues
|
|
2069
|
+
// to do the real work; the actor provides structured state tracking alongside it.
|
|
2070
|
+
const pipelineActor = createActor(wuDoneMachine, {
|
|
2071
|
+
input: {
|
|
2072
|
+
wuId: id,
|
|
2073
|
+
worktreePath: derivedWorktree,
|
|
2074
|
+
prepPassed,
|
|
2075
|
+
},
|
|
2076
|
+
});
|
|
2077
|
+
pipelineActor.start();
|
|
2078
|
+
// WU-1663: Send START event to transition from idle -> validating
|
|
2079
|
+
pipelineActor.send({
|
|
2080
|
+
type: WU_DONE_EVENTS.START,
|
|
2081
|
+
wuId: id,
|
|
2082
|
+
worktreePath: derivedWorktree || '',
|
|
2083
|
+
});
|
|
2197
2084
|
// WU-1590: branch-pr has no worktree, treat like branch-only for path resolution and ensureOnMain skip
|
|
2198
2085
|
const isNoWorktreeMode = isBranchOnly || isBranchPR;
|
|
2199
2086
|
const resolvedWorktreePath = derivedWorktree && !isNoWorktreeMode
|
|
@@ -2220,18 +2107,32 @@ async function main() {
|
|
|
2220
2107
|
await ensureCleanWorktree(effectiveWorktreePath);
|
|
2221
2108
|
}
|
|
2222
2109
|
// Pre-flight checks (WU-1215: extracted to executePreFlightChecks function)
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2110
|
+
// WU-1663: Wrap in try/catch to send pipeline failure event before die() propagates
|
|
2111
|
+
let preFlightResult;
|
|
2112
|
+
try {
|
|
2113
|
+
preFlightResult = await executePreFlightChecks({
|
|
2114
|
+
id,
|
|
2115
|
+
args,
|
|
2116
|
+
isBranchOnly: effectiveBranchOnly,
|
|
2117
|
+
isDocsOnly,
|
|
2118
|
+
docMain,
|
|
2119
|
+
docForValidation: initialDocForValidation,
|
|
2120
|
+
derivedWorktree: effectiveDerivedWorktree,
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
catch (preFlightErr) {
|
|
2124
|
+
pipelineActor.send({
|
|
2125
|
+
type: WU_DONE_EVENTS.VALIDATION_FAILED,
|
|
2126
|
+
error: getErrorMessage(preFlightErr),
|
|
2127
|
+
});
|
|
2128
|
+
pipelineActor.stop();
|
|
2129
|
+
throw preFlightErr;
|
|
2130
|
+
}
|
|
2232
2131
|
const title = preFlightResult.title;
|
|
2233
2132
|
// Note: docForValidation is returned but not used after pre-flight checks
|
|
2234
2133
|
// The metadata transaction uses docForUpdate instead
|
|
2134
|
+
// WU-1663: Pre-flight checks passed - transition to preparing state
|
|
2135
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.VALIDATION_PASSED });
|
|
2235
2136
|
// WU-1599: Enforce auditable spawn provenance for initiative-governed WUs.
|
|
2236
2137
|
await enforceSpawnProvenanceForDone(id, docMain, {
|
|
2237
2138
|
baseDir: mainCheckoutPath,
|
|
@@ -2272,7 +2173,35 @@ async function main() {
|
|
|
2272
2173
|
}
|
|
2273
2174
|
// Otherwise silently allow - fail-open
|
|
2274
2175
|
}
|
|
2275
|
-
|
|
2176
|
+
// WU-1663: Preparation complete - transition to gating state
|
|
2177
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.PREPARATION_COMPLETE });
|
|
2178
|
+
// WU-1663: Wrap gates in try/catch to send pipeline failure event
|
|
2179
|
+
let gateExecutionResult;
|
|
2180
|
+
try {
|
|
2181
|
+
gateExecutionResult = await executeGates({
|
|
2182
|
+
id,
|
|
2183
|
+
args,
|
|
2184
|
+
isBranchOnly: effectiveBranchOnly,
|
|
2185
|
+
isDocsOnly,
|
|
2186
|
+
worktreePath,
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
catch (gateErr) {
|
|
2190
|
+
pipelineActor.send({
|
|
2191
|
+
type: WU_DONE_EVENTS.GATES_FAILED,
|
|
2192
|
+
error: getErrorMessage(gateErr),
|
|
2193
|
+
});
|
|
2194
|
+
pipelineActor.stop();
|
|
2195
|
+
throw gateErr;
|
|
2196
|
+
}
|
|
2197
|
+
// WU-1663: Gates passed - transition from gating state.
|
|
2198
|
+
// Use GATES_SKIPPED if checkpoint dedup allowed skip, GATES_PASSED otherwise.
|
|
2199
|
+
if (gateExecutionResult.skippedByCheckpoint) {
|
|
2200
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.GATES_SKIPPED });
|
|
2201
|
+
}
|
|
2202
|
+
else {
|
|
2203
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.GATES_PASSED });
|
|
2204
|
+
}
|
|
2276
2205
|
// Print State HUD for visibility (WU-1215: extracted to printStateHUD function)
|
|
2277
2206
|
printStateHUD({
|
|
2278
2207
|
id,
|
|
@@ -2282,12 +2211,17 @@ async function main() {
|
|
|
2282
2211
|
derivedWorktree: effectiveDerivedWorktree,
|
|
2283
2212
|
STAMPS_DIR,
|
|
2284
2213
|
});
|
|
2285
|
-
// Step 0.5: Pre-flight validation
|
|
2286
|
-
//
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2214
|
+
// Step 0.5: Pre-flight hook validation policy.
|
|
2215
|
+
// WU-1659: Reuse Step 0 gate attestation/checkpoint and avoid duplicate full-suite execution.
|
|
2216
|
+
const preCommitGateDecision = resolveWuDonePreCommitGateDecision({
|
|
2217
|
+
skipGates: Boolean(args.skipGates),
|
|
2218
|
+
fullGatesRanInCurrentRun: gateExecutionResult.fullGatesRanInCurrentRun,
|
|
2219
|
+
skippedByCheckpoint: gateExecutionResult.skippedByCheckpoint,
|
|
2220
|
+
checkpointId: gateExecutionResult.checkpointId,
|
|
2221
|
+
});
|
|
2222
|
+
console.log(`${LOG_PREFIX.DONE} ${preCommitGateDecision.message}`);
|
|
2223
|
+
// Fallback path remains available if gate attestation is missing for any reason.
|
|
2224
|
+
if (preCommitGateDecision.runPreCommitFullSuite) {
|
|
2291
2225
|
const hookResult = await validateAllPreCommitHooks(id, worktreePath, {
|
|
2292
2226
|
runGates: ({ cwd }) => runGates({ cwd, docsOnly: false }),
|
|
2293
2227
|
});
|
|
@@ -2295,9 +2229,6 @@ async function main() {
|
|
|
2295
2229
|
die('Pre-flight validation failed. Fix hook issues and try again.');
|
|
2296
2230
|
}
|
|
2297
2231
|
}
|
|
2298
|
-
else {
|
|
2299
|
-
console.log(`${LOG_PREFIX.DONE} Skipping pre-flight hook validation (--skip-gates)`);
|
|
2300
|
-
}
|
|
2301
2232
|
// Step 0.6: WU-1781 - Run tasks:validate preflight BEFORE any merge/push operations
|
|
2302
2233
|
// This prevents deadlocks where validation fails after merge, leaving local main ahead of origin
|
|
2303
2234
|
// Specifically catches stamp-status mismatches from legacy WUs that would block pre-push hooks
|
|
@@ -2358,6 +2289,12 @@ async function main() {
|
|
|
2358
2289
|
};
|
|
2359
2290
|
completionResult = await executeWorktreeCompletion(worktreeContext);
|
|
2360
2291
|
}
|
|
2292
|
+
// WU-1663: Mode-specific completion succeeded - send pipeline events.
|
|
2293
|
+
// The completion modules handle commit, merge, and push internally.
|
|
2294
|
+
// We send the corresponding pipeline events based on the completion result.
|
|
2295
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.COMMIT_COMPLETE });
|
|
2296
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.MERGE_COMPLETE });
|
|
2297
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.PUSH_COMPLETE });
|
|
2361
2298
|
// Handle recovery mode (zombie state cleanup completed)
|
|
2362
2299
|
if ('recovered' in completionResult && completionResult.recovered) {
|
|
2363
2300
|
// P0 FIX: Release lane lock before early exit
|
|
@@ -2369,30 +2306,28 @@ async function main() {
|
|
|
2369
2306
|
catch {
|
|
2370
2307
|
// Intentionally ignore lock release errors during cleanup
|
|
2371
2308
|
}
|
|
2309
|
+
pipelineActor.stop();
|
|
2372
2310
|
process.exit(EXIT_CODES.SUCCESS);
|
|
2373
2311
|
}
|
|
2374
2312
|
}
|
|
2375
2313
|
catch (err) {
|
|
2376
|
-
// WU-
|
|
2377
|
-
//
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not restore lifecycle files after failure: ${cleanupErr.message}`);
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2314
|
+
// WU-1663: Mode execution failed - determine which stage failed
|
|
2315
|
+
// based on completion result flags and send appropriate failure event.
|
|
2316
|
+
const failureStage = completionResult.committed === false
|
|
2317
|
+
? WU_DONE_EVENTS.COMMIT_FAILED
|
|
2318
|
+
: completionResult.merged === false
|
|
2319
|
+
? WU_DONE_EVENTS.MERGE_FAILED
|
|
2320
|
+
: completionResult.pushed === false
|
|
2321
|
+
? WU_DONE_EVENTS.PUSH_FAILED
|
|
2322
|
+
: WU_DONE_EVENTS.COMMIT_FAILED; // Default to commit as earliest possible failure
|
|
2323
|
+
pipelineActor.send({
|
|
2324
|
+
type: failureStage,
|
|
2325
|
+
error: getErrorMessage(err),
|
|
2326
|
+
});
|
|
2327
|
+
// WU-1663: Log pipeline state for diagnostics
|
|
2328
|
+
const failedSnapshot = pipelineActor.getSnapshot();
|
|
2329
|
+
console.error(`${LOG_PREFIX.DONE} Pipeline state: ${failedSnapshot.value} (failedAt: ${failedSnapshot.context.failedAt})`);
|
|
2330
|
+
pipelineActor.stop();
|
|
2396
2331
|
// P0 FIX: Release lane lock before error exit
|
|
2397
2332
|
try {
|
|
2398
2333
|
const lane = docMain.lane;
|
|
@@ -2402,6 +2337,8 @@ async function main() {
|
|
|
2402
2337
|
catch {
|
|
2403
2338
|
// Intentionally ignore lock release errors during error handling
|
|
2404
2339
|
}
|
|
2340
|
+
console.error(`\n${LOG_PREFIX.DONE} ${EMOJI.FAILURE} Mode execution failed: ${getErrorMessage(err)}`);
|
|
2341
|
+
console.error(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Next step: resolve the reported error and retry: pnpm wu:done --id ${id}`);
|
|
2405
2342
|
// WU-1811: Check if cleanup is safe before removing worktree
|
|
2406
2343
|
// If cleanupSafe is false (or undefined), preserve worktree for recovery
|
|
2407
2344
|
if (err.cleanupSafe === false) {
|
|
@@ -2415,58 +2352,6 @@ async function main() {
|
|
|
2415
2352
|
else {
|
|
2416
2353
|
await ensureNoAutoStagedOrNoop([WU_PATH, STATUS_PATH, BACKLOG_PATH, STAMPS_DIR]);
|
|
2417
2354
|
}
|
|
2418
|
-
// WU-1084: Check for uncommitted changes on main after merge
|
|
2419
|
-
// WU-1554: Pass metadataDir so metadata files are classified separately
|
|
2420
|
-
const gitMain = getGitForCwd();
|
|
2421
|
-
const currentBranch = await gitMain.getCurrentBranch();
|
|
2422
|
-
const allowLifecycleAutoCommit = shouldAutoCommitLifecycleWrites(currentBranch);
|
|
2423
|
-
const metadataDir = path.dirname(STATUS_PATH);
|
|
2424
|
-
const postMergeStatus = await gitMain.getStatus();
|
|
2425
|
-
const dirtyCheck = checkPostMergeDirtyState(postMergeStatus, id, metadataDir);
|
|
2426
|
-
if (dirtyCheck.internalOnlyFiles.length > 0) {
|
|
2427
|
-
try {
|
|
2428
|
-
await gitMain.raw(['restore', '--', ...dirtyCheck.internalOnlyFiles]);
|
|
2429
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Restored internal lifecycle log files: ${dirtyCheck.internalOnlyFiles.join(', ')}`);
|
|
2430
|
-
}
|
|
2431
|
-
catch (restoreErr) {
|
|
2432
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not auto-restore internal lifecycle files: ${restoreErr.message}`);
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
// WU-1554: Auto-commit metadata files left dirty after merge+rebase
|
|
2436
|
-
// This handles push-only claim mode and concurrent agent advancement
|
|
2437
|
-
if (dirtyCheck.metadataFiles.length > 0) {
|
|
2438
|
-
if (allowLifecycleAutoCommit) {
|
|
2439
|
-
try {
|
|
2440
|
-
await gitMain.add(dirtyCheck.metadataFiles);
|
|
2441
|
-
await gitMain.commit(`chore: post-merge metadata sync for ${id} [skip ci]`);
|
|
2442
|
-
await gitMain.raw(['pull', '--rebase', '--autostash', 'origin', currentBranch]);
|
|
2443
|
-
await gitMain.push('origin', currentBranch);
|
|
2444
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Auto-committed post-merge metadata: ${dirtyCheck.metadataFiles.join(', ')}`);
|
|
2445
|
-
}
|
|
2446
|
-
catch (commitErr) {
|
|
2447
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not auto-commit metadata files: ${commitErr.message}`);
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
else {
|
|
2451
|
-
try {
|
|
2452
|
-
await gitMain.raw(['restore', '--', ...dirtyCheck.metadataFiles]);
|
|
2453
|
-
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Protected branch ${currentBranch}: restored lifecycle metadata files instead of committing on branch`);
|
|
2454
|
-
}
|
|
2455
|
-
catch (restoreErr) {
|
|
2456
|
-
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not restore metadata lifecycle files on protected branch ${currentBranch}: ${restoreErr.message}`);
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
const postLifecycleStatus = await gitMain.getStatus();
|
|
2461
|
-
const postLifecycleDirty = checkPostMergeDirtyState(postLifecycleStatus, id, metadataDir);
|
|
2462
|
-
if (!allowLifecycleAutoCommit && postLifecycleDirty.metadataFiles.length > 0) {
|
|
2463
|
-
die(`Protected branch ${currentBranch} still has lifecycle metadata changes after wu:done:\n` +
|
|
2464
|
-
`${postLifecycleDirty.metadataFiles.join(STRING_LITERALS.NEWLINE)}\n\n` +
|
|
2465
|
-
`wu:done will not commit directly on ${currentBranch}. Resolve and retry from the WU worktree/lane branch.`);
|
|
2466
|
-
}
|
|
2467
|
-
if (postLifecycleDirty.isDirty) {
|
|
2468
|
-
die(postLifecycleDirty.error);
|
|
2469
|
-
}
|
|
2470
2355
|
// Step 6 & 7: Cleanup (remove worktree, delete branch) - WU-1215
|
|
2471
2356
|
// WU-1811: Only run cleanup if all completion steps succeeded
|
|
2472
2357
|
if (completionResult.cleanupSafe !== false) {
|
|
@@ -2539,6 +2424,12 @@ async function main() {
|
|
|
2539
2424
|
// Double fail-open: even if runDecayOnDone itself throws unexpectedly, never block wu:done
|
|
2540
2425
|
console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Decay archival error (fail-open): ${getErrorMessage(err)}`);
|
|
2541
2426
|
}
|
|
2427
|
+
// WU-1663: Cleanup complete - transition to final done state
|
|
2428
|
+
pipelineActor.send({ type: WU_DONE_EVENTS.CLEANUP_COMPLETE });
|
|
2429
|
+
// WU-1663: Log final pipeline state for diagnostics
|
|
2430
|
+
const finalSnapshot = pipelineActor.getSnapshot();
|
|
2431
|
+
console.log(`${LOG_PREFIX.DONE} Pipeline state: ${finalSnapshot.value} (WU-1663)`);
|
|
2432
|
+
pipelineActor.stop();
|
|
2542
2433
|
console.log(`\n${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Transaction COMMIT - all steps succeeded (WU-755)`);
|
|
2543
2434
|
console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Marked done, pushed, and cleaned up.`);
|
|
2544
2435
|
console.log(`- WU: ${id} — ${title}`);
|
|
@@ -2555,7 +2446,11 @@ async function main() {
|
|
|
2555
2446
|
// WU-1983: Migration deployment nudge - only if supabase paths in code_paths
|
|
2556
2447
|
const codePaths = docMain.code_paths || [];
|
|
2557
2448
|
await printMigrationDeploymentNudge(codePaths, mainCheckoutPath);
|
|
2558
|
-
|
|
2449
|
+
const currentBranch = (await getGitForCwd().getCurrentBranch()).trim();
|
|
2450
|
+
const shouldRunCleanupMutations = currentBranch.length > 0 &&
|
|
2451
|
+
currentBranch !== BRANCHES.MAIN &&
|
|
2452
|
+
currentBranch !== BRANCHES.MASTER;
|
|
2453
|
+
if (shouldRunCleanupMutations) {
|
|
2559
2454
|
// WU-1366: Auto state cleanup after successful completion
|
|
2560
2455
|
// Non-fatal: errors are logged but do not block completion
|
|
2561
2456
|
await runAutoCleanupAfterDone(mainCheckoutPath);
|