feat(pr): pr-create/pr-respond/pr-merge node kinds + handlers (U3)

Adds the three first-class PR workflow node kinds and their handlers via
createPrNodeHandlers(deps), registered in createDefaultNodeHandlers
(fail-closed pr-nodes-unwired when absent). GitHub ops are injected as
callbacks (PrNodeGithubOps) at all three CLI sites (daemon/serve/dashboard)
so the engine never imports the dashboard client (FN-3049). pr-create
routes open/failed as outcomes; pr-merge passes expectedHeadOid and never
writes 'merged' (reconcile corroborates); pr-respond delegates to an
injected respond callback (U5 fills the body). 10 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-05 19:55:31 -07:00
parent b871046ad2
commit 4e16145aab
17 changed files with 750 additions and 13 deletions

View File

@@ -17,6 +17,7 @@ import { InProcessRuntime } from "./runtimes/in-process-runtime.js";
import type { WorktreePool } from "./worktree-pool.js";
import type { ProjectRuntimeConfig } from "./project-runtime.js";
import { PrMonitor } from "./pr-monitor.js";
import type { PrNodeGithubOps } from "./pr-nodes.js";
import { PrCommentHandler } from "./pr-comment-handler.js";
import { NtfyNotifier } from "./notifier.js";
import { NotificationService, OAuthAlertStateStore, OAuthExpiryMonitor, OAuthValidityLogger } from "./notification/index.js";
@@ -220,6 +221,15 @@ export interface ProjectEngineOptions {
* the PR body.
*/
syncGroupPr?: SyncGroupPrFn;
/**
* PR-entity node GitHub ops (U3): the injected `createPr`/`mergePr`/`respond`
* callbacks (+ source resolver + audit) that back the `pr-create`/`pr-respond`/
* `pr-merge` workflow nodes. Injected from the CLI layer because they close
* over the dashboard `GitHubClient`; the engine must not statically import it
* (FN-3049). Mirrors `createGroupPr`/`syncGroupPr`. When absent, the pr-* node
* kinds fail closed (value:"pr-nodes-unwired").
*/
prNodeGithubOps?: PrNodeGithubOps;
/**
* Returns the merge blocker reason for a task, or null/undefined if
* the task is eligible for merge. Imported from @fusion/core.
@@ -364,10 +374,14 @@ export class ProjectEngine {
centralCore: CentralCore,
private options: ProjectEngineOptions = {},
) {
// Pass through externalTaskStore to the runtime config if provided
const runtimeConfig: ProjectRuntimeConfig = options.externalTaskStore
? { ...config, externalTaskStore: options.externalTaskStore }
: config;
// Pass through externalTaskStore + PR node GitHub ops (U3) to the runtime
// config. The runtime binds the engine-owned store and hands the assembled
// PrNodeDeps to the executor's workflow-graph runner.
const runtimeConfig: ProjectRuntimeConfig = {
...config,
...(options.externalTaskStore ? { externalTaskStore: options.externalTaskStore } : {}),
...(options.prNodeGithubOps ? { prNodeGithubOps: options.prNodeGithubOps } : {}),
};
this.runtime = new InProcessRuntime(runtimeConfig, centralCore);
// Let the runtime's SelfHealingManager re-enqueue tasks directly into our
// auto-merge queue when it clears a stale `merging` status, instead of