Full-suite repair, engine harness cluster (~85 red suites). Two causes: (1) graph dispatch now fails closed without options.agentStore (FN-8764/FN-8821, intended) — the shared executor test harness now provisions the workflow-routing agent-store fixture for bare TaskExecutor constructions, with explicit opt-out for the two tests asserting the fail-closed park; (2) the wave-18 'pure peel' (#3317) rebuilt executor.ts from a stale base and silently deleted shipped behaviors, restored here: FN-8864 agent-activity writers (task started/handed-off, workflow gate pass/fail, gate principal attribution via executor/workflow-gate-activity.ts), FN-8768 Plan Review group recognition, convergence primer, and modified-file review scoping, FN-6782's fire-time guard on transient resume-after-restart retries, FN-8868 session usage telemetry boundaries, recommendation-route withheld-tool guidance, and the per-instance worktree retry cap. Stale expectations updated for intended changes (FN-8823 shared-member hold, FN-9060 zero-acquire fail-closed, heartbeat tool inventory, peeled-module seams, PG harness provisioning). Verified: 23 files / 711 tests green, engine typecheck clean, merge gate green. Known follow-ups (not addressed here): step-session error routing may still bypass FN-5866 non-continuable classification (post-done-continuation-no-wedge red), scheduler mission-loop trigger gap (mission-validation-trigger-gap red), and executeWorkflowStep lost routed workflow-principal session identity threading (untested drop from the same peel). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
66 lines
8.2 KiB
TypeScript
66 lines
8.2 KiB
TypeScript
/**
|
|
* FNXC:CodeOrganization 2026-08-04-08:05:
|
|
* Pure worktree helper facades peeled from TaskExecutor (U4). Keeps executor.ts to
|
|
* impl/bags facades while pure.* worktree ownership helpers share TaskExecutorState fields.
|
|
*
|
|
* FNXC:CodeOrganization 2026-08-04-08:55:
|
|
* Also hosts impl worktree create/conflict/cleanup facades and capture helpers so
|
|
* executor.ts drops the worktree-create tail.
|
|
*/
|
|
import * as pure from "./pure-bindings.js";
|
|
import * as impl from "./impl-bindings.js";
|
|
import * as bags from "./deps-bags.js";
|
|
import * as constants from "./executor-constants.js";
|
|
import { bindHandleWorktreeConflict, bindTryCreateWorktree } from "./worktree-create-binders.js";
|
|
import { type FacadeRestArgs, type FacadeAfterFirst, type FacadeAfterSecond } from "./facade-methods.js";
|
|
import { TaskExecutorState } from "./task-executor-state.js";
|
|
|
|
export abstract class TaskExecutorWorktreePureFacades extends TaskExecutorState {
|
|
protected hasActiveWorktreeBinding(taskId: string, worktreePath: string): boolean { return pure.hasActiveWorktreeBinding(this.activeWorktrees, taskId, worktreePath); }
|
|
protected async shouldGenerateNewWorktreeName(conflictPath: string, currentTaskId: string): Promise<boolean> { return pure.shouldGenerateNewWorktreeName(this.activeWorktrees, this.store, conflictPath, currentTaskId); }
|
|
protected async findActiveWorktreeOwner(worktreePath: string, requestingTaskId: string): Promise<string | null> { return pure.findActiveWorktreeOwner(this.activeWorktrees, this.store, worktreePath, requestingTaskId); }
|
|
protected async isLiveCleanupRefusal(worktreePath: string, taskId: string): Promise<boolean> { return pure.isLiveCleanupRefusal(this.activeWorktrees, this.store, worktreePath, taskId); }
|
|
protected async cleanupStaleBranch(branch: string, taskId: string): Promise<boolean> { return pure.cleanupStaleBranch(this.rootDir, this.store, branch, taskId); }
|
|
protected async planSquashImportFromDep(...args: FacadeAfterSecond<typeof pure.planSquashImportFromDep>): ReturnType<typeof pure.planSquashImportFromDep> { return pure.planSquashImportFromDep(this.rootDir, this.store, ...args); }
|
|
/*
|
|
FNXC:WorktreeConflictRecovery 2026-08-10-20:31:
|
|
The free reconcile helper requires the executor's live-binding probe in addition to its store.
|
|
Bind that host-owned callback here so every two-argument facade caller receives the production
|
|
liveness guard instead of forwarding an undefined third argument into conflict cleanup.
|
|
*/
|
|
protected async reconcileSelfOwnedBeforeRemove(
|
|
worktreePath: string,
|
|
taskId: string,
|
|
): ReturnType<typeof pure.reconcileSelfOwnedBeforeRemove> {
|
|
return pure.reconcileSelfOwnedBeforeRemove(
|
|
this.store,
|
|
worktreePath,
|
|
taskId,
|
|
(ownerTaskId, path) => this.hasActiveWorktreeBinding(ownerTaskId, path),
|
|
);
|
|
}
|
|
protected async emitStaleLockAudit(...args: FacadeRestArgs<typeof pure.emitStaleLockAudit>): ReturnType<typeof pure.emitStaleLockAudit> { return pure.emitStaleLockAudit(bags.buildStaleLockRecoveryDeps(this), ...args); }
|
|
protected async recoverIndexLockIfStale(taskId: string, path: string, conflictInfo: { lockPath?: string; message?: string }): Promise<boolean> { return pure.recoverIndexLockIfStale(bags.buildStaleLockRecoveryDeps(this), taskId, path, conflictInfo); }
|
|
protected async recoverStaleRegistration(taskId: string, path: string, conflictInfo: { path?: string; message?: string }): Promise<boolean> { return pure.recoverExecutorStaleRegistration(bags.buildStaleLockRecoveryDeps(this), taskId, path, conflictInfo); }
|
|
protected async normalizeReclaimableWorktreePath(...args: FacadeRestArgs<typeof pure.normalizeReclaimableWorktreePath>): ReturnType<typeof pure.normalizeReclaimableWorktreePath> { return pure.normalizeReclaimableWorktreePath(bags.buildNormalizeReclaimableWorktreePathDeps(this), ...args); }
|
|
protected async tryFreshWorktreeAfterLiveConflict(...args: FacadeRestArgs<typeof pure.tryFreshWorktreeAfterLiveConflict>): Promise<{ path: string; branch: string }> { return pure.tryFreshWorktreeAfterLiveConflict(bags.buildTryFreshWorktreeAfterLiveConflictDeps(this, bindTryCreateWorktree(this)), ...args); }
|
|
protected async removeOwnWorktreeWithReconcile(...args: FacadeRestArgs<typeof pure.removeOwnWorktreeWithReconcile>): ReturnType<typeof pure.removeOwnWorktreeWithReconcile> { return pure.removeOwnWorktreeWithReconcile(bags.buildRemoveOwnWorktreeWithReconcileDeps(this), ...args); }
|
|
protected async reclaimExistingWorktree(...args: FacadeRestArgs<typeof impl.reclaimExistingWorktreeImpl>): ReturnType<typeof impl.reclaimExistingWorktreeImpl> { return impl.reclaimExistingWorktreeImpl(bags.buildBranchConflictHandleFacadeDeps(this), ...args); }
|
|
protected async handleBranchConflict(...args: FacadeRestArgs<typeof impl.handleBranchConflictImpl>): ReturnType<typeof impl.handleBranchConflictImpl> { return impl.handleBranchConflictImpl(bags.buildBranchConflictHandleFacadeDeps(this), ...args); }
|
|
protected async recoverMissingWorktreeSessionStartFailure(...args: FacadeRestArgs<typeof impl.recoverMissingWorktreeSessionStartFailureImpl>): ReturnType<typeof impl.recoverMissingWorktreeSessionStartFailureImpl> { return impl.recoverMissingWorktreeSessionStartFailureImpl(bags.buildRecoverMissingWorktreeSessionStartFailureDeps(this), ...args); }
|
|
protected async emitWorktreeReanchoredAudit(...args: FacadeRestArgs<typeof impl.emitWorktreeReanchoredAuditImpl>): ReturnType<typeof impl.emitWorktreeReanchoredAuditImpl> { return impl.emitWorktreeReanchoredAuditImpl(bags.buildStoreRunContextDeps(this), ...args); }
|
|
listWorktreeHolders(): Array<{ taskId: string; worktreePath: string }> { return impl.listWorktreeHoldersImpl(this.activeWorktrees); }
|
|
protected async tryCreateWorktree(...args: FacadeRestArgs<typeof impl.tryCreateWorktreeImpl>): Promise<{ path: string; branch: string }> { return impl.tryCreateWorktreeImpl(bags.buildWorktreeCreateConflictFacadeDeps(this, (this as { MAX_WORKTREE_RETRIES?: number }).MAX_WORKTREE_RETRIES ?? constants.MAX_WORKTREE_RETRIES, bindHandleWorktreeConflict(this), bindTryCreateWorktree(this)), ...args); }
|
|
protected async handleWorktreeConflict(...args: FacadeRestArgs<typeof impl.handleWorktreeConflictImpl>): Promise<{ path: string; branch: string } | null> { return impl.handleWorktreeConflictImpl(bags.buildWorktreeCreateConflictFacadeDeps(this, (this as { MAX_WORKTREE_RETRIES?: number }).MAX_WORKTREE_RETRIES ?? constants.MAX_WORKTREE_RETRIES, bindHandleWorktreeConflict(this), bindTryCreateWorktree(this)), ...args); }
|
|
protected async cleanupConflictingWorktree(...args: FacadeRestArgs<typeof impl.cleanupConflictingWorktreeImpl>): ReturnType<typeof impl.cleanupConflictingWorktreeImpl> { return impl.cleanupConflictingWorktreeImpl(bags.buildCleanupConflictingWorktreeDeps(this), ...args); }
|
|
protected async resolveWorktreeStartPoint(startPoint: string, taskId: string): ReturnType<typeof impl.resolveWorktreeStartPointImpl> { return impl.resolveWorktreeStartPointImpl(this.rootDir, this.store, startPoint, taskId); }
|
|
protected async squashImportDepIntoWorktree(...args: FacadeAfterFirst<typeof impl.squashImportDepIntoWorktreeImpl>): ReturnType<typeof impl.squashImportDepIntoWorktreeImpl> { return impl.squashImportDepIntoWorktreeImpl(this.store, ...args); }
|
|
protected async rebaseNewWorktreeOntoRemote(...args: FacadeAfterSecond<typeof impl.rebaseNewWorktreeOntoRemoteImpl>): ReturnType<typeof impl.rebaseNewWorktreeOntoRemoteImpl> { return impl.rebaseNewWorktreeOntoRemoteImpl(this.rootDir, this.store, ...args); }
|
|
protected async createWorktree(...args: FacadeRestArgs<typeof impl.createWorktreeImpl>): Promise<{ path: string; branch: string }> { return impl.createWorktreeImpl(bags.buildCreateWorktreeFacadeDeps(this, bindTryCreateWorktree(this)), ...args); }
|
|
disposeStoreLifecycleDisposers(): void { impl.disposeStoreLifecycleDisposersImpl(bags.buildDisposeStoreLifecycleDisposersDeps(this)); }
|
|
async cleanup(taskId: string): Promise<void> { return impl.cleanupTaskWorktreeImpl(bags.buildCleanupTaskWorktreeDeps(this), taskId); }
|
|
getWorktreePath(taskId: string): string | undefined { return impl.getWorktreePathImpl(this.workspaceConfig, (id) => this.getActiveWorktreePaths(id), taskId); }
|
|
// getActiveWorktreePaths is hosted on session facades; abstract for getWorktreePath
|
|
protected abstract getActiveWorktreePaths(taskId: string): string[];
|
|
}
|