@xdevops/issue-auto-finish 1.0.91 → 1.0.92

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.
Files changed (43) hide show
  1. package/dist/{chunk-CQ66LL7P.js → chunk-2WDVTLVF.js} +1 -1
  2. package/dist/{chunk-UMQYEYLO.js → chunk-6T7ZHAV2.js} +2 -2
  3. package/dist/{chunk-LDGK5NMS.js → chunk-WZGEYHCC.js} +628 -442
  4. package/dist/{chunk-LDGK5NMS.js.map → chunk-WZGEYHCC.js.map} +1 -1
  5. package/dist/cli.js +2 -2
  6. package/dist/index.js +2 -2
  7. package/dist/lib.js +1 -1
  8. package/dist/lifecycle/DefaultLifecycleHook.d.ts +21 -0
  9. package/dist/lifecycle/DefaultLifecycleHook.d.ts.map +1 -0
  10. package/dist/lifecycle/FeedbackTypes.d.ts +52 -0
  11. package/dist/lifecycle/FeedbackTypes.d.ts.map +1 -0
  12. package/dist/lifecycle/PhaseLifecycleHook.d.ts +70 -0
  13. package/dist/lifecycle/PhaseLifecycleHook.d.ts.map +1 -0
  14. package/dist/lifecycle/PhaseMiddleware.d.ts +47 -0
  15. package/dist/lifecycle/PhaseMiddleware.d.ts.map +1 -0
  16. package/dist/lifecycle/PhaseStateMachine.d.ts +111 -0
  17. package/dist/lifecycle/PhaseStateMachine.d.ts.map +1 -0
  18. package/dist/lifecycle/index.d.ts +8 -0
  19. package/dist/lifecycle/index.d.ts.map +1 -1
  20. package/dist/orchestrator/steps/PhaseHelpers.d.ts +24 -0
  21. package/dist/orchestrator/steps/PhaseHelpers.d.ts.map +1 -0
  22. package/dist/orchestrator/steps/PhaseLoopStep.d.ts +10 -0
  23. package/dist/orchestrator/steps/PhaseLoopStep.d.ts.map +1 -1
  24. package/dist/orchestrator/strategies/AiPhaseStrategy.d.ts +17 -0
  25. package/dist/orchestrator/strategies/AiPhaseStrategy.d.ts.map +1 -0
  26. package/dist/orchestrator/strategies/GateStrategy.d.ts +15 -0
  27. package/dist/orchestrator/strategies/GateStrategy.d.ts.map +1 -0
  28. package/dist/orchestrator/strategies/PhaseStrategy.d.ts +16 -0
  29. package/dist/orchestrator/strategies/PhaseStrategy.d.ts.map +1 -0
  30. package/dist/orchestrator/strategies/VerifyFixStrategy.d.ts +15 -0
  31. package/dist/orchestrator/strategies/VerifyFixStrategy.d.ts.map +1 -0
  32. package/dist/orchestrator/strategies/index.d.ts +17 -0
  33. package/dist/orchestrator/strategies/index.d.ts.map +1 -0
  34. package/dist/{restart-MSUWF4ID.js → restart-5D3ZDD5L.js} +2 -2
  35. package/dist/run.js +2 -2
  36. package/dist/{start-B4CDZAFG.js → start-IQBNXLEI.js} +2 -2
  37. package/package.json +1 -1
  38. package/src/web/frontend/dist/assets/{index-BoYtsxGN.js → index-BR0UoQER.js} +19 -19
  39. package/src/web/frontend/dist/index.html +1 -1
  40. /package/dist/{chunk-CQ66LL7P.js.map → chunk-2WDVTLVF.js.map} +0 -0
  41. /package/dist/{chunk-UMQYEYLO.js.map → chunk-6T7ZHAV2.js.map} +0 -0
  42. /package/dist/{restart-MSUWF4ID.js.map → restart-5D3ZDD5L.js.map} +0 -0
  43. /package/dist/{start-B4CDZAFG.js.map → start-IQBNXLEI.js.map} +0 -0
@@ -4304,6 +4304,44 @@ async function executeSetup(ctx, deps) {
4304
4304
  return { wtGit, wtPlan, wtGitMap };
4305
4305
  }
4306
4306
 
4307
+ // src/lifecycle/FeedbackTypes.ts
4308
+ function inputRequestToFeedback(request) {
4309
+ switch (request.type) {
4310
+ case "interactive-dialog":
4311
+ return {
4312
+ kind: "interactive-dialog",
4313
+ question: request.content,
4314
+ options: request.options ?? []
4315
+ };
4316
+ case "plan-approval":
4317
+ return { kind: "approval-required", scope: "plan" };
4318
+ case "generic":
4319
+ return { kind: "generic", content: request.content };
4320
+ }
4321
+ }
4322
+ function feedbackResponseToString(response) {
4323
+ switch (response.action) {
4324
+ case "approve":
4325
+ return "allow";
4326
+ case "reject":
4327
+ return "reject";
4328
+ case "select":
4329
+ return response.value;
4330
+ case "dismiss":
4331
+ return "";
4332
+ case "pause":
4333
+ return "";
4334
+ }
4335
+ }
4336
+ function stringToFeedbackResponse(str, originalFeedback) {
4337
+ if (str === "allow") return { action: "approve" };
4338
+ if (str === "reject") return { action: "reject" };
4339
+ if (str === "") {
4340
+ return originalFeedback.kind === "interactive-dialog" ? { action: "dismiss" } : { action: "approve" };
4341
+ }
4342
+ return { action: "select", value: str };
4343
+ }
4344
+
4307
4345
  // src/notesync/NoteSyncSettings.ts
4308
4346
  var noteSyncOverride;
4309
4347
  function getNoteSyncEnabled(cfg) {
@@ -4360,8 +4398,14 @@ function clearPendingDialog(issueIid) {
4360
4398
  store.delete(issueIid);
4361
4399
  }
4362
4400
 
4363
- // src/orchestrator/steps/PhaseLoopStep.ts
4364
- var logger17 = logger.child("PhaseLoopStep");
4401
+ // src/orchestrator/steps/PhaseHelpers.ts
4402
+ var logger17 = logger.child("PhaseHelpers");
4403
+ async function safeComment(deps, issueId, message) {
4404
+ try {
4405
+ await deps.gongfeng.createIssueNote(issueId, message);
4406
+ } catch {
4407
+ }
4408
+ }
4365
4409
  function resolveVerifyRunner(deps) {
4366
4410
  return deps.aiRunner;
4367
4411
  }
@@ -4434,18 +4478,6 @@ async function syncResultToIssue(phase, ctx, displayId, phaseName, deps, issueId
4434
4478
  await safeComment(deps, issueId, issueProgressComment(phaseName, "completed"));
4435
4479
  }
4436
4480
  }
4437
- function buildInputHandler(displayId, phaseName, deps) {
4438
- const useAcpGate = deps.config.ai.codebuddyAcpAutoApprove === false;
4439
- return (request) => {
4440
- if (request.type === "interactive-dialog") {
4441
- return handleInteractiveDialog(displayId, phaseName, deps, request);
4442
- }
4443
- if (request.type === "plan-approval" && useAcpGate) {
4444
- return handlePlanApproval(displayId, phaseName, deps);
4445
- }
4446
- return Promise.resolve("allow");
4447
- };
4448
- }
4449
4481
  function handlePlanApproval(displayId, phaseName, deps) {
4450
4482
  logger17.info("ACP plan-approval requested, delegating to review gate", {
4451
4483
  issueIid: displayId,
@@ -4546,39 +4578,69 @@ function updateHooksForPhase(spec, pipelineDef, ctx, wtPlan) {
4546
4578
  });
4547
4579
  }
4548
4580
  }
4549
- async function safeComment(deps, issueId, message) {
4550
- try {
4551
- await deps.gongfeng.createIssueNote(issueId, message);
4552
- } catch {
4553
- }
4554
- }
4555
- async function runPhaseWithLifecycle(phase, phaseCtx, spec, ctx, deps, wtGit, wtPlan, wtGitMap) {
4556
- const { issue } = ctx;
4557
- const displayId = issue.iid;
4558
- deps.tracker.updateState(displayId, spec.startState, { currentPhase: spec.name });
4559
- deps.tracker.updatePhaseProgress(displayId, spec.name, {
4560
- status: "in_progress",
4561
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
4562
- });
4563
- wtPlan.updatePhaseProgress(spec.name, "in_progress");
4564
- await safeComment(deps, issue.id, issueProgressComment(spec.name, "in_progress"));
4565
- const phaseLabel = t(`phase.${spec.name}`) || spec.name;
4566
- deps.eventBus.emitTyped("agent:output", {
4567
- issueIid: displayId,
4568
- phase: spec.name,
4569
- event: { type: "system", content: t("basePhase.aiStarting", { label: phaseLabel }), timestamp: (/* @__PURE__ */ new Date()).toISOString() }
4570
- });
4571
- const callbacks = {
4572
- onStreamEvent: (event) => deps.eventBus.emitTyped("agent:output", {
4581
+
4582
+ // src/lifecycle/DefaultLifecycleHook.ts
4583
+ var logger18 = logger.child("DefaultLifecycleHook");
4584
+ var DefaultLifecycleHook = class {
4585
+ async beforePhase(ctx) {
4586
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4587
+ const { issue } = issueCtx;
4588
+ const displayId = issue.iid;
4589
+ deps.tracker.updateState(displayId, spec.startState, { currentPhase: spec.name });
4590
+ deps.tracker.updatePhaseProgress(displayId, spec.name, {
4591
+ status: "in_progress",
4592
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4593
+ });
4594
+ wtPlan.updatePhaseProgress(spec.name, "in_progress");
4595
+ await safeComment(deps, issue.id, issueProgressComment(spec.name, "in_progress"));
4596
+ const phaseLabel = t(`phase.${spec.name}`) || spec.name;
4597
+ deps.eventBus.emitTyped("agent:output", {
4573
4598
  issueIid: displayId,
4574
4599
  phase: spec.name,
4600
+ event: {
4601
+ type: "system",
4602
+ content: t("basePhase.aiStarting", { label: phaseLabel }),
4603
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4604
+ }
4605
+ });
4606
+ }
4607
+ onStream(ctx, event) {
4608
+ const { spec, issueCtx, deps } = ctx;
4609
+ deps.eventBus.emitTyped("agent:output", {
4610
+ issueIid: issueCtx.issue.iid,
4611
+ phase: spec.name,
4575
4612
  event
4576
- }),
4577
- onInputRequired: buildInputHandler(displayId, spec.name, deps)
4578
- };
4579
- const outcome = await phase.run(phaseCtx, callbacks);
4580
- if (outcome.sessionId) wtPlan.updatePhaseSessionId(spec.name, outcome.sessionId);
4581
- if (outcome.status === "completed") {
4613
+ });
4614
+ }
4615
+ beforeExecute;
4616
+ afterExecute;
4617
+ async onFeedback(ctx, feedback) {
4618
+ const { deps, displayId, spec } = ctx;
4619
+ if (feedback.kind === "interactive-dialog") {
4620
+ const result = await handleInteractiveDialog(displayId, spec.name, deps, {
4621
+ type: "interactive-dialog",
4622
+ content: feedback.question,
4623
+ options: feedback.options
4624
+ });
4625
+ return stringToFeedbackResponse(result, feedback);
4626
+ }
4627
+ if (feedback.kind === "approval-required" && feedback.scope === "plan") {
4628
+ const useAcpGate = deps.config.ai.codebuddyAcpAutoApprove === false;
4629
+ if (useAcpGate) {
4630
+ const result = await handlePlanApproval(displayId, spec.name, deps);
4631
+ return result === "allow" ? { action: "approve" } : { action: "reject" };
4632
+ }
4633
+ return { action: "approve" };
4634
+ }
4635
+ return { action: "approve" };
4636
+ }
4637
+ async afterPhase(ctx, outcome) {
4638
+ const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap, phase } = ctx;
4639
+ const { issue, phaseCtx } = issueCtx;
4640
+ const displayId = issue.iid;
4641
+ if (outcome.sessionId) {
4642
+ wtPlan.updatePhaseSessionId(spec.name, outcome.sessionId);
4643
+ }
4582
4644
  deps.tracker.updateState(displayId, spec.doneState, { currentPhase: spec.name });
4583
4645
  deps.tracker.updatePhaseProgress(displayId, spec.name, {
4584
4646
  status: "completed",
@@ -4586,28 +4648,400 @@ async function runPhaseWithLifecycle(phase, phaseCtx, spec, ctx, deps, wtGit, wt
4586
4648
  });
4587
4649
  wtPlan.updatePhaseProgress(spec.name, "completed");
4588
4650
  await commitPlanFiles(phaseCtx, wtGit, wtGitMap, spec.name, displayId);
4589
- await syncResultToIssue(phase, phaseCtx, displayId, spec.name, deps, issue.id, wtPlan);
4590
- return outcome;
4651
+ if (phase) {
4652
+ await syncResultToIssue(phase, phaseCtx, displayId, spec.name, deps, issue.id, wtPlan);
4653
+ }
4654
+ }
4655
+ async onError(ctx, error) {
4656
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4657
+ const { issue } = issueCtx;
4658
+ const displayId = issue.iid;
4659
+ wtPlan.updatePhaseProgress(spec.name, "failed", error.message);
4660
+ deps.tracker.updatePhaseProgress(displayId, spec.name, { status: "failed" });
4661
+ if (error.wasActiveAtTimeout) {
4662
+ deps.tracker.markFailedSoft(displayId, error.message, "phase_running" /* PhaseRunning */);
4663
+ } else {
4664
+ deps.tracker.markFailed(displayId, error.message, "phase_running" /* PhaseRunning */);
4665
+ }
4666
+ const shortErr = error.message.slice(0, 200);
4667
+ await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4668
+ return { action: "fail", softFail: error.wasActiveAtTimeout };
4591
4669
  }
4592
- if (outcome.status === "running") {
4593
- return outcome;
4670
+ };
4671
+ function createCallbacksFromHook(hook, ctx) {
4672
+ return {
4673
+ onStreamEvent: hook.onStream ? (event) => hook.onStream(ctx, event) : void 0,
4674
+ onInputRequired: hook.onFeedback ? async (request) => {
4675
+ const feedback = inputRequestToFeedback(request);
4676
+ const response = await hook.onFeedback(ctx, feedback);
4677
+ return feedbackResponseToString(response);
4678
+ } : void 0
4679
+ };
4680
+ }
4681
+
4682
+ // src/orchestrator/strategies/GateStrategy.ts
4683
+ var logger19 = logger.child("GateStrategy");
4684
+ var GateStrategy = class {
4685
+ name = "gate";
4686
+ shouldSkip() {
4687
+ return false;
4594
4688
  }
4595
- const errMsg = outcome.error?.message ?? "Unknown error";
4596
- const shortErr = errMsg.slice(0, 200);
4597
- const wasActive = outcome.error?.wasActiveAtTimeout ?? false;
4598
- wtPlan.updatePhaseProgress(spec.name, "failed", errMsg);
4599
- deps.tracker.updatePhaseProgress(displayId, spec.name, { status: "failed" });
4600
- if (wasActive) {
4601
- deps.tracker.markFailedSoft(displayId, errMsg, "phase_running" /* PhaseRunning */);
4602
- } else {
4603
- deps.tracker.markFailed(displayId, errMsg, "phase_running" /* PhaseRunning */);
4689
+ async execute(ctx, _hooks) {
4690
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4691
+ const { issue } = issueCtx;
4692
+ if (deps.shouldAutoApprove(issue.labels)) {
4693
+ logger19.info("Auto-approving review gate (matched autoApproveLabels)", {
4694
+ iid: issue.iid,
4695
+ labels: issue.labels,
4696
+ autoApproveLabels: deps.config.review.autoApproveLabels
4697
+ });
4698
+ if (spec.approvedState) {
4699
+ deps.tracker.updateState(issue.iid, spec.approvedState, { currentPhase: spec.name });
4700
+ }
4701
+ wtPlan.updatePhaseProgress(spec.name, "completed");
4702
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4703
+ status: "completed",
4704
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
4705
+ });
4706
+ try {
4707
+ await deps.gongfeng.createIssueNote(issue.id, t("orchestrator.autoApproveComment"));
4708
+ } catch {
4709
+ }
4710
+ return { paused: false };
4711
+ }
4712
+ deps.tracker.updateState(issue.iid, spec.startState, { currentPhase: spec.name });
4713
+ wtPlan.updatePhaseProgress(spec.name, "in_progress");
4714
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4715
+ status: "in_progress",
4716
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4717
+ });
4718
+ deps.eventBus.emitTyped("review:requested", { issueIid: issue.iid });
4719
+ logger19.info("Review gate reached, pausing", { iid: issue.iid });
4720
+ return { paused: true };
4721
+ }
4722
+ };
4723
+
4724
+ // src/orchestrator/strategies/AiPhaseStrategy.ts
4725
+ var logger20 = logger.child("AiPhaseStrategy");
4726
+ var AiPhaseStrategy = class {
4727
+ name = "ai";
4728
+ shouldSkip(ctx) {
4729
+ const { spec, issueCtx, deps } = ctx;
4730
+ if (spec.name === "uat" && !isE2eEnabledForIssue(issueCtx.issue.iid, deps.tracker, deps.config)) {
4731
+ logger20.info("UAT phase skipped (E2E not enabled for this issue)", { iid: issueCtx.issue.iid });
4732
+ return true;
4733
+ }
4734
+ return false;
4735
+ }
4736
+ async execute(ctx, hooks) {
4737
+ const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
4738
+ const { issue, phaseCtx } = issueCtx;
4739
+ if (this.shouldSkip(ctx)) {
4740
+ deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4741
+ wtPlan.updatePhaseProgress(spec.name, "completed");
4742
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4743
+ status: "completed",
4744
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
4745
+ });
4746
+ return { paused: false };
4747
+ }
4748
+ updateHooksForPhase(spec, issueCtx.pipelineDef, issueCtx, wtPlan);
4749
+ const runner = this.resolveRunner(ctx);
4750
+ if (spec.name === "uat") {
4751
+ const runnerName = runner === deps.e2eAiRunner ? "e2eAiRunner (CodeBuddy)" : "mainRunner";
4752
+ logger20.info("UAT phase starting", { iid: issue.iid, runner: runnerName });
4753
+ }
4754
+ const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4755
+ if (wtGitMap) phase.setWtGitMap(wtGitMap);
4756
+ ctx.phase = phase;
4757
+ const outcome = await runPhaseWithLifecycle(
4758
+ phase,
4759
+ phaseCtx,
4760
+ spec,
4761
+ issueCtx,
4762
+ deps,
4763
+ wtGit,
4764
+ wtPlan,
4765
+ wtGitMap,
4766
+ hooks
4767
+ );
4768
+ if (outcome.status === "running") {
4769
+ return this.handleAsyncOutcome(ctx, outcome);
4770
+ }
4771
+ if (spec.approvedState && outcome.data?.hasReleaseCapability) {
4772
+ return this.handleGateRequest(ctx);
4773
+ }
4774
+ return { paused: false };
4775
+ }
4776
+ resolveRunner(ctx) {
4777
+ const { spec, deps, displayId } = ctx;
4778
+ if (spec.name === "verify") return resolveVerifyRunner(deps);
4779
+ if (spec.name === "uat") return resolveUatRunner(deps, displayId);
4780
+ return deps.aiRunner;
4781
+ }
4782
+ async handleAsyncOutcome(ctx, outcome) {
4783
+ const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
4784
+ const { issue, phaseCtx } = issueCtx;
4785
+ if (outcome.awaitCompletion) {
4786
+ logger20.info("Async phase running, awaiting completion", { iid: issue.iid, phase: spec.name });
4787
+ const finalOutcome = await outcome.awaitCompletion;
4788
+ if (finalOutcome.sessionId) {
4789
+ wtPlan.updatePhaseSessionId(spec.name, finalOutcome.sessionId);
4790
+ }
4791
+ if (finalOutcome.status === "completed") {
4792
+ deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4793
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4794
+ status: "completed",
4795
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
4796
+ });
4797
+ wtPlan.updatePhaseProgress(spec.name, "completed");
4798
+ await commitPlanFiles(phaseCtx, wtGit, wtGitMap, spec.name, issue.iid);
4799
+ const runner = this.resolveRunner(ctx);
4800
+ const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4801
+ await syncResultToIssue(phase, phaseCtx, issue.iid, spec.name, deps, issue.id, wtPlan);
4802
+ logger20.info("Async phase completed successfully", { iid: issue.iid, phase: spec.name });
4803
+ return { paused: false };
4804
+ }
4805
+ const errMsg = finalOutcome.error?.message ?? "Unknown error";
4806
+ const shortErr = errMsg.slice(0, 200);
4807
+ const wasActive = finalOutcome.error?.wasActiveAtTimeout ?? false;
4808
+ wtPlan.updatePhaseProgress(spec.name, "failed", errMsg);
4809
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "failed" });
4810
+ if (wasActive) {
4811
+ deps.tracker.markFailedSoft(issue.iid, errMsg, "phase_running" /* PhaseRunning */);
4812
+ } else {
4813
+ deps.tracker.markFailed(issue.iid, errMsg, "phase_running" /* PhaseRunning */);
4814
+ }
4815
+ await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4816
+ throw new AIExecutionError(spec.name, `Phase ${spec.name} failed: ${shortErr}`, {
4817
+ output: finalOutcome.error?.rawOutput ?? finalOutcome.output,
4818
+ exitCode: finalOutcome.exitCode ?? 1,
4819
+ isRetryable: finalOutcome.error?.isRetryable,
4820
+ wasActiveAtTimeout: wasActive
4821
+ });
4822
+ }
4823
+ deps.tracker.updateState(issue.iid, "phase_waiting" /* PhaseWaiting */, { currentPhase: spec.name });
4824
+ wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4825
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4826
+ const gateEvent = spec.name === "uat" ? "uat:gateRequested" : "release:gateRequested";
4827
+ deps.eventBus.emitTyped(gateEvent, { issueIid: issue.iid });
4828
+ logger20.info("Async phase running (no awaitCompletion), pausing pipeline", {
4829
+ iid: issue.iid,
4830
+ phase: spec.name
4831
+ });
4832
+ return { paused: true };
4833
+ }
4834
+ handleGateRequest(ctx) {
4835
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4836
+ const { issue } = issueCtx;
4837
+ deps.tracker.updateState(issue.iid, "phase_waiting" /* PhaseWaiting */, { currentPhase: spec.name });
4838
+ wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4839
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4840
+ deps.eventBus.emitTyped("release:gateRequested", { issueIid: issue.iid });
4841
+ logger20.info("Phase requested gate, pausing", { iid: issue.iid, phase: spec.name });
4842
+ return { paused: true };
4843
+ }
4844
+ };
4845
+
4846
+ // src/orchestrator/strategies/VerifyFixStrategy.ts
4847
+ var logger21 = logger.child("VerifyFixStrategy");
4848
+ var VerifyFixStrategy = class {
4849
+ name = "verify-fix";
4850
+ shouldSkip() {
4851
+ return false;
4852
+ }
4853
+ async execute(ctx, hooks) {
4854
+ const { issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
4855
+ const { issue, phaseCtx, pipelineDef } = issueCtx;
4856
+ const maxIterations = deps.config.verifyFixLoop.maxIterations;
4857
+ const verifySpec = ctx.spec;
4858
+ const verifyPhaseIdx = pipelineDef.phases.findIndex((p) => p.name === verifySpec.name);
4859
+ const buildPhaseIdx = this.findPreviousAiPhaseIndex(pipelineDef.phases, verifyPhaseIdx);
4860
+ deps.eventBus.emitTyped("verify:loopStarted", {
4861
+ issueIid: issue.iid,
4862
+ maxIterations
4863
+ });
4864
+ logger21.info("Verify-fix loop started", {
4865
+ iid: issue.iid,
4866
+ maxIterations,
4867
+ buildPhaseIdx
4868
+ });
4869
+ for (let iteration = 1; iteration <= maxIterations; iteration++) {
4870
+ if (isShuttingDown()) throw new ServiceShutdownError();
4871
+ logger21.info("Verify-fix loop iteration", { iteration, maxIterations, iid: issue.iid });
4872
+ updateHooksForPhase(verifySpec, pipelineDef, issueCtx, wtPlan);
4873
+ const verifyRunner = resolveVerifyRunner(deps);
4874
+ const verifyPhase = createPhase("verify", verifyRunner, wtGit, wtPlan, deps.config);
4875
+ if (wtGitMap) verifyPhase.setWtGitMap(wtGitMap);
4876
+ let verifyOutcome;
4877
+ try {
4878
+ verifyOutcome = await runPhaseWithLifecycle(
4879
+ verifyPhase,
4880
+ phaseCtx,
4881
+ verifySpec,
4882
+ issueCtx,
4883
+ deps,
4884
+ wtGit,
4885
+ wtPlan,
4886
+ wtGitMap,
4887
+ hooks
4888
+ );
4889
+ } catch (err) {
4890
+ logger21.warn("Verify phase execution failed", {
4891
+ iteration,
4892
+ iid: issue.iid,
4893
+ error: err.message
4894
+ });
4895
+ deps.eventBus.emitTyped("verify:iterationComplete", {
4896
+ issueIid: issue.iid,
4897
+ iteration,
4898
+ passed: false,
4899
+ failures: ["AI runner execution failed"]
4900
+ });
4901
+ if (iteration === maxIterations) throw err;
4902
+ if (buildPhaseIdx >= 0) {
4903
+ await this.executeBuildFix(issueCtx, deps, wtGit, wtPlan, buildPhaseIdx, {
4904
+ iteration,
4905
+ verifyFailures: ["AI runner execution failed: " + err.message],
4906
+ rawReport: ""
4907
+ }, wtGitMap, hooks);
4908
+ }
4909
+ continue;
4910
+ }
4911
+ const report = verifyOutcome.data?.verifyReport;
4912
+ const passed = report ? report.passed : true;
4913
+ deps.eventBus.emitTyped("verify:iterationComplete", {
4914
+ issueIid: issue.iid,
4915
+ iteration,
4916
+ passed,
4917
+ failures: report?.failureReasons
4918
+ });
4919
+ if (passed) {
4920
+ logger21.info("Verify-fix loop passed", { iteration, iid: issue.iid });
4921
+ return { paused: false };
4922
+ }
4923
+ logger21.info("Verify failed, issues found", {
4924
+ iteration,
4925
+ iid: issue.iid,
4926
+ failures: report?.failureReasons,
4927
+ todolistStats: report?.todolistStats
4928
+ });
4929
+ if (iteration === maxIterations) {
4930
+ deps.eventBus.emitTyped("verify:loopExhausted", {
4931
+ issueIid: issue.iid,
4932
+ totalIterations: iteration,
4933
+ failures: report?.failureReasons ?? []
4934
+ });
4935
+ const failMsg = `Verify-fix loop exhausted after ${maxIterations} iterations. Remaining issues: ${report?.failureReasons?.join("; ") ?? "unknown"}`;
4936
+ logger21.warn(failMsg, { iid: issue.iid });
4937
+ throw new AIExecutionError("verify", failMsg, {
4938
+ output: report?.rawReport ?? "",
4939
+ exitCode: 0
4940
+ });
4941
+ }
4942
+ if (buildPhaseIdx >= 0) {
4943
+ await this.executeBuildFix(issueCtx, deps, wtGit, wtPlan, buildPhaseIdx, {
4944
+ iteration,
4945
+ verifyFailures: report?.failureReasons ?? [],
4946
+ rawReport: report?.rawReport ?? ""
4947
+ }, wtGitMap, hooks);
4948
+ }
4949
+ }
4950
+ return { paused: false };
4951
+ }
4952
+ async executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, fixContext, wtGitMap, hooks) {
4953
+ const { issue, phaseCtx, pipelineDef } = ctx;
4954
+ const buildSpec = pipelineDef.phases[buildPhaseIdx];
4955
+ logger21.info("Looping back to build for fix", {
4956
+ iteration: fixContext.iteration,
4957
+ iid: issue.iid,
4958
+ failures: fixContext.verifyFailures
4959
+ });
4960
+ phaseCtx.fixContext = fixContext;
4961
+ try {
4962
+ updateHooksForPhase(buildSpec, pipelineDef, ctx, wtPlan);
4963
+ const buildPhase = createPhase("build", deps.aiRunner, wtGit, wtPlan, deps.config);
4964
+ if (wtGitMap) buildPhase.setWtGitMap(wtGitMap);
4965
+ await runPhaseWithLifecycle(
4966
+ buildPhase,
4967
+ phaseCtx,
4968
+ buildSpec,
4969
+ ctx,
4970
+ deps,
4971
+ wtGit,
4972
+ wtPlan,
4973
+ wtGitMap,
4974
+ hooks
4975
+ );
4976
+ } finally {
4977
+ delete phaseCtx.fixContext;
4978
+ }
4604
4979
  }
4605
- await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4980
+ findPreviousAiPhaseIndex(phases, currentIdx) {
4981
+ for (let j = currentIdx - 1; j >= 0; j--) {
4982
+ if (phases[j].kind === "ai") return j;
4983
+ }
4984
+ return -1;
4985
+ }
4986
+ };
4987
+
4988
+ // src/orchestrator/strategies/index.ts
4989
+ var gateStrategy = new GateStrategy();
4990
+ var aiStrategy = new AiPhaseStrategy();
4991
+ var verifyFixStrategy = new VerifyFixStrategy();
4992
+ function resolveStrategy(spec, config) {
4993
+ if (spec.kind === "gate") {
4994
+ return gateStrategy;
4995
+ }
4996
+ if (spec.name === "verify" && config.verifyFixLoop.enabled) {
4997
+ return verifyFixStrategy;
4998
+ }
4999
+ return aiStrategy;
5000
+ }
5001
+
5002
+ // src/orchestrator/steps/PhaseLoopStep.ts
5003
+ var logger22 = logger.child("PhaseLoopStep");
5004
+ async function runPhaseWithLifecycle(phase, phaseCtx, spec, ctx, deps, wtGit, wtPlan, wtGitMap, hook) {
5005
+ const lifecycleHook = hook ?? new DefaultLifecycleHook();
5006
+ const execCtx = {
5007
+ spec,
5008
+ issueCtx: ctx,
5009
+ deps,
5010
+ wtGit,
5011
+ wtPlan,
5012
+ wtGitMap,
5013
+ phase,
5014
+ displayId: ctx.issue.iid
5015
+ };
5016
+ await lifecycleHook.beforePhase(execCtx);
5017
+ if (lifecycleHook.beforeExecute) {
5018
+ await lifecycleHook.beforeExecute(execCtx);
5019
+ }
5020
+ const callbacks = createCallbacksFromHook(lifecycleHook, execCtx);
5021
+ const outcome = await phase.run(phaseCtx, callbacks);
5022
+ if (outcome.sessionId) wtPlan.updatePhaseSessionId(spec.name, outcome.sessionId);
5023
+ const finalOutcome = lifecycleHook.afterExecute ? await lifecycleHook.afterExecute(execCtx, outcome) : outcome;
5024
+ if (finalOutcome.status === "completed") {
5025
+ await lifecycleHook.afterPhase(execCtx, finalOutcome);
5026
+ return finalOutcome;
5027
+ }
5028
+ if (finalOutcome.status === "running") {
5029
+ return finalOutcome;
5030
+ }
5031
+ const errMsg = finalOutcome.error?.message ?? "Unknown error";
5032
+ const phaseError = {
5033
+ message: errMsg,
5034
+ isRetryable: finalOutcome.error?.isRetryable ?? true,
5035
+ rawOutput: finalOutcome.error?.rawOutput ?? finalOutcome.output,
5036
+ wasActiveAtTimeout: finalOutcome.error?.wasActiveAtTimeout ?? false
5037
+ };
5038
+ await lifecycleHook.onError(execCtx, phaseError);
5039
+ const shortErr = errMsg.slice(0, 200);
4606
5040
  throw new AIExecutionError(spec.name, `Phase ${spec.name} failed: ${shortErr}`, {
4607
- output: outcome.error?.rawOutput ?? outcome.output,
4608
- exitCode: outcome.exitCode ?? 1,
4609
- isRetryable: outcome.error?.isRetryable,
4610
- wasActiveAtTimeout: wasActive
5041
+ output: finalOutcome.error?.rawOutput ?? finalOutcome.output,
5042
+ exitCode: finalOutcome.exitCode ?? 1,
5043
+ isRetryable: finalOutcome.error?.isRetryable,
5044
+ wasActiveAtTimeout: finalOutcome.error?.wasActiveAtTimeout ?? false
4611
5045
  });
4612
5046
  }
4613
5047
  async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
@@ -4631,15 +5065,15 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4631
5065
  if (skippedDeployPhase && !phaseCtx.ports) {
4632
5066
  const existingPorts = deps.getPortsForIssue(issue.iid);
4633
5067
  if (existingPorts && deps.isPreviewRunning(issue.iid)) {
4634
- logger17.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
5068
+ logger22.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
4635
5069
  phaseCtx.ports = existingPorts;
4636
5070
  ctx.wtCtx.ports = existingPorts;
4637
5071
  serversStarted = true;
4638
5072
  } else {
4639
5073
  if (existingPorts) {
4640
- logger17.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
5074
+ logger22.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
4641
5075
  } else {
4642
- logger17.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
5076
+ logger22.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
4643
5077
  }
4644
5078
  const ports = await deps.startPreviewServers(ctx.wtCtx, issue);
4645
5079
  if (ports) {
@@ -4651,93 +5085,25 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4651
5085
  }
4652
5086
  }
4653
5087
  if (startIdx > 0) {
4654
- const currentProgress = wtPlan.readProgress();
4655
- if (currentProgress) {
4656
- let patched = false;
4657
- for (let i = 0; i < startIdx; i++) {
4658
- const prevSpec = pipelineDef.phases[i];
4659
- const pp = currentProgress.phases[prevSpec.name];
4660
- if (pp && pp.status !== "completed") {
4661
- logger17.warn("Fixing stale phase progress", {
4662
- iid: issue.iid,
4663
- phase: prevSpec.name,
4664
- was: pp.status,
4665
- now: "completed"
4666
- });
4667
- pp.status = "completed";
4668
- if (!pp.completedAt) {
4669
- pp.completedAt = (/* @__PURE__ */ new Date()).toISOString();
4670
- }
4671
- patched = true;
4672
- }
4673
- }
4674
- if (patched) {
4675
- wtPlan.writeProgress(currentProgress);
4676
- }
4677
- }
4678
- if (record.phaseProgress) {
4679
- for (let i = 0; i < startIdx; i++) {
4680
- const prevSpec = pipelineDef.phases[i];
4681
- const tp = record.phaseProgress[prevSpec.name];
4682
- if (tp && tp.status !== "completed") {
4683
- deps.tracker.updatePhaseProgress(issue.iid, prevSpec.name, {
4684
- status: "completed",
4685
- completedAt: tp.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
4686
- });
4687
- }
4688
- }
4689
- }
5088
+ healStaleProgress(ctx, deps, wtPlan, startIdx);
4690
5089
  }
5090
+ const hooks = new DefaultLifecycleHook();
4691
5091
  for (let i = startIdx; i < pipelineDef.phases.length; i++) {
4692
- if (isShuttingDown()) {
4693
- throw new ServiceShutdownError();
4694
- }
5092
+ if (isShuttingDown()) throw new ServiceShutdownError();
4695
5093
  const spec = pipelineDef.phases[i];
4696
5094
  const pendingAction = deps.consumePendingAction?.(issue.iid);
4697
- if (pendingAction) {
4698
- throw new PhaseAbortedError(spec.name, pendingAction);
4699
- }
4700
- if (spec.kind === "gate") {
4701
- if (deps.shouldAutoApprove(issue.labels)) {
4702
- logger17.info("Auto-approving review gate (matched autoApproveLabels)", {
4703
- iid: issue.iid,
4704
- labels: issue.labels,
4705
- autoApproveLabels: deps.config.review.autoApproveLabels
4706
- });
4707
- if (spec.approvedState) {
4708
- deps.tracker.updateState(issue.iid, spec.approvedState, { currentPhase: spec.name });
4709
- }
4710
- wtPlan.updatePhaseProgress(spec.name, "completed");
4711
- deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4712
- status: "completed",
4713
- completedAt: (/* @__PURE__ */ new Date()).toISOString()
4714
- });
4715
- try {
4716
- await deps.gongfeng.createIssueNote(
4717
- issue.id,
4718
- t("orchestrator.autoApproveComment")
4719
- );
4720
- } catch {
4721
- }
4722
- continue;
4723
- }
4724
- deps.tracker.updateState(issue.iid, spec.startState, { currentPhase: spec.name });
4725
- wtPlan.updatePhaseProgress(spec.name, "in_progress");
4726
- deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4727
- status: "in_progress",
4728
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
4729
- });
4730
- deps.eventBus.emitTyped("review:requested", { issueIid: issue.iid });
4731
- logger17.info("Review gate reached, pausing", { iid: issue.iid });
4732
- return { serversStarted, paused: true };
4733
- }
4734
- if (spec.name === "verify" && deps.config.verifyFixLoop.enabled) {
4735
- const buildIdx = findPreviousAiPhaseIndex(pipelineDef.phases, i);
4736
- await executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, i, buildIdx, wtGitMap);
4737
- continue;
4738
- }
4739
- if (spec.name === "uat" && !isE2eEnabledForIssue(issue.iid, deps.tracker, deps.config)) {
4740
- logger17.info("UAT phase skipped (E2E not enabled for this issue)", { iid: issue.iid });
5095
+ if (pendingAction) throw new PhaseAbortedError(spec.name, pendingAction);
5096
+ const strategy = resolveStrategy(spec, deps.config);
5097
+ const execCtx = {
5098
+ spec,
5099
+ issueCtx: ctx,
5100
+ deps,
5101
+ wtGit,
5102
+ wtPlan,
5103
+ wtGitMap,
5104
+ displayId: issue.iid
5105
+ };
5106
+ if (strategy.shouldSkip(execCtx)) {
4741
5107
  deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4742
5108
  wtPlan.updatePhaseProgress(spec.name, "completed");
4743
5109
  deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
@@ -4746,49 +5112,12 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4746
5112
  });
4747
5113
  continue;
4748
5114
  }
4749
- updateHooksForPhase(spec, pipelineDef, ctx, wtPlan);
4750
- const runner = spec.name === "verify" ? resolveVerifyRunner(deps) : spec.name === "uat" ? resolveUatRunner(deps, issue.iid) : deps.aiRunner;
4751
- if (spec.name === "uat") {
4752
- const runnerName = runner === deps.e2eAiRunner ? "e2eAiRunner (CodeBuddy)" : "mainRunner";
4753
- logger17.info("UAT phase starting", { iid: issue.iid, runner: runnerName });
4754
- }
4755
- const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4756
- if (wtGitMap) {
4757
- phase.setWtGitMap(wtGitMap);
4758
- }
4759
- const outcome = await runPhaseWithLifecycle(
4760
- phase,
4761
- phaseCtx,
4762
- spec,
4763
- ctx,
4764
- deps,
4765
- wtGit,
4766
- wtPlan,
4767
- wtGitMap
4768
- );
4769
- if (outcome.status === "running") {
4770
- if (outcome.awaitCompletion) {
4771
- logger17.info("Async phase running, awaiting completion", { iid: issue.iid, phase: spec.name });
4772
- const finalOutcome = await awaitAsyncPhase(outcome, spec, ctx, deps, wtGit, wtPlan, wtGitMap);
4773
- if (finalOutcome.status === "completed") {
4774
- continue;
4775
- }
4776
- }
4777
- deps.tracker.updateState(issue.iid, "phase_waiting" /* PhaseWaiting */, { currentPhase: spec.name });
4778
- wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4779
- deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4780
- const gateEvent = spec.name === "uat" ? "uat:gateRequested" : "release:gateRequested";
4781
- deps.eventBus.emitTyped(gateEvent, { issueIid: issue.iid });
4782
- logger17.info("Async phase running (no awaitCompletion), pausing pipeline", { iid: issue.iid, phase: spec.name });
4783
- return { serversStarted, paused: true };
5115
+ const result = await strategy.execute(execCtx, hooks);
5116
+ if (result.paused) {
5117
+ return { serversStarted: serversStarted || !!result.serversStarted, paused: true };
4784
5118
  }
4785
- if (spec.approvedState && outcome.data?.hasReleaseCapability) {
4786
- deps.tracker.updateState(issue.iid, "phase_waiting" /* PhaseWaiting */, { currentPhase: spec.name });
4787
- wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4788
- deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4789
- deps.eventBus.emitTyped("release:gateRequested", { issueIid: issue.iid });
4790
- logger17.info("Phase requested gate, pausing", { iid: issue.iid, phase: spec.name });
4791
- return { serversStarted, paused: true };
5119
+ if (result.serversStarted) {
5120
+ serversStarted = true;
4792
5121
  }
4793
5122
  if (needsDeployment && !serversStarted && lifecycleManager.shouldDeployPreview(spec.name)) {
4794
5123
  const ports = await deps.startPreviewServers(ctx.wtCtx, issue);
@@ -4801,191 +5130,48 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4801
5130
  }
4802
5131
  return { serversStarted, paused: false };
4803
5132
  }
4804
- async function awaitAsyncPhase(outcome, spec, ctx, deps, wtGit, wtPlan, wtGitMap) {
4805
- const { issue } = ctx;
4806
- const displayId = issue.iid;
4807
- const phaseCtx = ctx.phaseCtx;
4808
- const finalOutcome = await outcome.awaitCompletion;
4809
- if (finalOutcome.sessionId) {
4810
- wtPlan.updatePhaseSessionId(spec.name, finalOutcome.sessionId);
4811
- }
4812
- if (finalOutcome.status === "completed") {
4813
- deps.tracker.updateState(displayId, spec.doneState, { currentPhase: spec.name });
4814
- deps.tracker.updatePhaseProgress(displayId, spec.name, {
4815
- status: "completed",
4816
- completedAt: (/* @__PURE__ */ new Date()).toISOString()
4817
- });
4818
- wtPlan.updatePhaseProgress(spec.name, "completed");
4819
- await commitPlanFiles(phaseCtx, wtGit, wtGitMap, spec.name, displayId);
4820
- const runner = spec.name === "uat" ? resolveUatRunner(deps, displayId) : deps.aiRunner;
4821
- const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4822
- await syncResultToIssue(phase, phaseCtx, displayId, spec.name, deps, issue.id, wtPlan);
4823
- logger17.info("Async phase completed successfully", { iid: displayId, phase: spec.name });
4824
- return finalOutcome;
4825
- }
4826
- const errMsg = finalOutcome.error?.message ?? "Unknown error";
4827
- const shortErr = errMsg.slice(0, 200);
4828
- const wasActive = finalOutcome.error?.wasActiveAtTimeout ?? false;
4829
- wtPlan.updatePhaseProgress(spec.name, "failed", errMsg);
4830
- deps.tracker.updatePhaseProgress(displayId, spec.name, { status: "failed" });
4831
- if (wasActive) {
4832
- deps.tracker.markFailedSoft(displayId, errMsg, "phase_running" /* PhaseRunning */);
4833
- } else {
4834
- deps.tracker.markFailed(displayId, errMsg, "phase_running" /* PhaseRunning */);
4835
- }
4836
- await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4837
- throw new AIExecutionError(spec.name, `Phase ${spec.name} failed: ${shortErr}`, {
4838
- output: finalOutcome.error?.rawOutput ?? finalOutcome.output,
4839
- exitCode: finalOutcome.exitCode ?? 1,
4840
- isRetryable: finalOutcome.error?.isRetryable,
4841
- wasActiveAtTimeout: wasActive
4842
- });
4843
- }
4844
- function findPreviousAiPhaseIndex(phases, currentIdx) {
4845
- for (let j = currentIdx - 1; j >= 0; j--) {
4846
- if (phases[j].kind === "ai") return j;
4847
- }
4848
- return -1;
4849
- }
4850
- async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, buildPhaseIdx, wtGitMap) {
4851
- const { issue, lifecycleManager, phaseCtx } = ctx;
4852
- const maxIterations = deps.config.verifyFixLoop.maxIterations;
4853
- const verifySpec = ctx.pipelineDef.phases[verifyPhaseIdx];
4854
- deps.eventBus.emitTyped("verify:loopStarted", {
4855
- issueIid: issue.iid,
4856
- maxIterations
4857
- });
4858
- logger17.info("Verify-fix loop started", {
4859
- iid: issue.iid,
4860
- maxIterations,
4861
- buildPhaseIdx
4862
- });
4863
- for (let iteration = 1; iteration <= maxIterations; iteration++) {
4864
- if (isShuttingDown()) {
4865
- throw new ServiceShutdownError();
4866
- }
4867
- logger17.info("Verify-fix loop iteration", {
4868
- iteration,
4869
- maxIterations,
4870
- iid: issue.iid
4871
- });
4872
- updateHooksForPhase(verifySpec, ctx.pipelineDef, ctx, wtPlan);
4873
- const verifyRunner = resolveVerifyRunner(deps);
4874
- const verifyPhase = createPhase("verify", verifyRunner, wtGit, wtPlan, deps.config);
4875
- if (wtGitMap) {
4876
- verifyPhase.setWtGitMap(wtGitMap);
4877
- }
4878
- let verifyOutcome;
4879
- try {
4880
- verifyOutcome = await runPhaseWithLifecycle(
4881
- verifyPhase,
4882
- phaseCtx,
4883
- verifySpec,
4884
- ctx,
4885
- deps,
4886
- wtGit,
4887
- wtPlan,
4888
- wtGitMap
4889
- );
4890
- } catch (err) {
4891
- logger17.warn("Verify phase execution failed", {
4892
- iteration,
4893
- iid: issue.iid,
4894
- error: err.message
4895
- });
4896
- deps.eventBus.emitTyped("verify:iterationComplete", {
4897
- issueIid: issue.iid,
4898
- iteration,
4899
- passed: false,
4900
- failures: ["AI runner execution failed"]
4901
- });
4902
- if (iteration === maxIterations) {
4903
- throw err;
4904
- }
4905
- if (buildPhaseIdx >= 0) {
4906
- await executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, {
4907
- iteration,
4908
- verifyFailures: ["AI runner execution failed: " + err.message],
4909
- rawReport: ""
4910
- }, wtGitMap);
5133
+ function healStaleProgress(ctx, deps, wtPlan, startIdx) {
5134
+ const { issue, pipelineDef, record } = ctx;
5135
+ const currentProgress = wtPlan.readProgress();
5136
+ if (currentProgress) {
5137
+ let patched = false;
5138
+ for (let i = 0; i < startIdx; i++) {
5139
+ const prevSpec = pipelineDef.phases[i];
5140
+ const pp = currentProgress.phases[prevSpec.name];
5141
+ if (pp && pp.status !== "completed") {
5142
+ logger22.warn("Fixing stale phase progress", {
5143
+ iid: issue.iid,
5144
+ phase: prevSpec.name,
5145
+ was: pp.status,
5146
+ now: "completed"
5147
+ });
5148
+ pp.status = "completed";
5149
+ if (!pp.completedAt) {
5150
+ pp.completedAt = (/* @__PURE__ */ new Date()).toISOString();
5151
+ }
5152
+ patched = true;
4911
5153
  }
4912
- continue;
4913
- }
4914
- const report = verifyOutcome.data?.verifyReport;
4915
- const passed = report ? report.passed : true;
4916
- deps.eventBus.emitTyped("verify:iterationComplete", {
4917
- issueIid: issue.iid,
4918
- iteration,
4919
- passed,
4920
- failures: report?.failureReasons
4921
- });
4922
- if (passed) {
4923
- logger17.info("Verify-fix loop passed", {
4924
- iteration,
4925
- iid: issue.iid
4926
- });
4927
- return;
4928
- }
4929
- logger17.info("Verify failed, issues found", {
4930
- iteration,
4931
- iid: issue.iid,
4932
- failures: report?.failureReasons,
4933
- todolistStats: report?.todolistStats
4934
- });
4935
- if (iteration === maxIterations) {
4936
- deps.eventBus.emitTyped("verify:loopExhausted", {
4937
- issueIid: issue.iid,
4938
- totalIterations: iteration,
4939
- failures: report?.failureReasons ?? []
4940
- });
4941
- const failMsg = `Verify-fix loop exhausted after ${maxIterations} iterations. Remaining issues: ${report?.failureReasons?.join("; ") ?? "unknown"}`;
4942
- logger17.warn(failMsg, { iid: issue.iid });
4943
- throw new AIExecutionError("verify", failMsg, {
4944
- output: report?.rawReport ?? "",
4945
- exitCode: 0
4946
- });
4947
5154
  }
4948
- if (buildPhaseIdx >= 0) {
4949
- await executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, {
4950
- iteration,
4951
- verifyFailures: report?.failureReasons ?? [],
4952
- rawReport: report?.rawReport ?? ""
4953
- }, wtGitMap);
5155
+ if (patched) {
5156
+ wtPlan.writeProgress(currentProgress);
4954
5157
  }
4955
5158
  }
4956
- }
4957
- async function executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, fixContext, wtGitMap) {
4958
- const { issue, phaseCtx } = ctx;
4959
- const buildSpec = ctx.pipelineDef.phases[buildPhaseIdx];
4960
- logger17.info("Looping back to build for fix", {
4961
- iteration: fixContext.iteration,
4962
- iid: issue.iid,
4963
- failures: fixContext.verifyFailures
4964
- });
4965
- phaseCtx.fixContext = fixContext;
4966
- try {
4967
- updateHooksForPhase(buildSpec, ctx.pipelineDef, ctx, wtPlan);
4968
- const buildPhase = createPhase("build", deps.aiRunner, wtGit, wtPlan, deps.config);
4969
- if (wtGitMap) {
4970
- buildPhase.setWtGitMap(wtGitMap);
5159
+ if (record.phaseProgress) {
5160
+ for (let i = 0; i < startIdx; i++) {
5161
+ const prevSpec = pipelineDef.phases[i];
5162
+ const tp = record.phaseProgress[prevSpec.name];
5163
+ if (tp && tp.status !== "completed") {
5164
+ deps.tracker.updatePhaseProgress(issue.iid, prevSpec.name, {
5165
+ status: "completed",
5166
+ completedAt: tp.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5167
+ });
5168
+ }
4971
5169
  }
4972
- await runPhaseWithLifecycle(
4973
- buildPhase,
4974
- phaseCtx,
4975
- buildSpec,
4976
- ctx,
4977
- deps,
4978
- wtGit,
4979
- wtPlan,
4980
- wtGitMap
4981
- );
4982
- } finally {
4983
- delete phaseCtx.fixContext;
4984
5170
  }
4985
5171
  }
4986
5172
 
4987
5173
  // src/orchestrator/steps/CompletionStep.ts
4988
- var logger18 = logger.child("CompletionStep");
5174
+ var logger23 = logger.child("CompletionStep");
4989
5175
  async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
4990
5176
  const { issue, branchName, wtCtx } = ctx;
4991
5177
  deps.emitProgress(issue.iid, "create_mr", t("orchestrator.createMrProgress"));
@@ -5017,7 +5203,7 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
5017
5203
  mrIid: void 0
5018
5204
  });
5019
5205
  } catch (err) {
5020
- logger18.warn("Failed to publish E2E screenshots", {
5206
+ logger23.warn("Failed to publish E2E screenshots", {
5021
5207
  iid: issue.iid,
5022
5208
  error: err.message
5023
5209
  });
@@ -5037,19 +5223,19 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
5037
5223
  await deps.claimer.releaseClaim(issue.id, issue.iid, "completed");
5038
5224
  }
5039
5225
  if (phaseResult.serversStarted && deps.config.preview.keepAfterComplete) {
5040
- logger18.info("Preview servers kept running after completion", { iid: issue.iid });
5226
+ logger23.info("Preview servers kept running after completion", { iid: issue.iid });
5041
5227
  } else {
5042
5228
  deps.stopPreviewServers(issue.iid);
5043
5229
  await deps.mainGitMutex.runExclusive(async () => {
5044
5230
  if (wtCtx.workspace) {
5045
5231
  await deps.workspaceManager.cleanupWorkspace(wtCtx.workspace);
5046
- logger18.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
5232
+ logger23.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
5047
5233
  } else {
5048
5234
  try {
5049
5235
  await deps.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
5050
- logger18.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5236
+ logger23.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5051
5237
  } catch (err) {
5052
- logger18.warn("Failed to cleanup worktree", {
5238
+ logger23.warn("Failed to cleanup worktree", {
5053
5239
  dir: wtCtx.gitRootDir,
5054
5240
  error: err.message
5055
5241
  });
@@ -5057,16 +5243,16 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
5057
5243
  }
5058
5244
  });
5059
5245
  }
5060
- logger18.info("Issue processing completed", { iid: issue.iid });
5246
+ logger23.info("Issue processing completed", { iid: issue.iid });
5061
5247
  }
5062
5248
 
5063
5249
  // src/orchestrator/steps/FailureHandler.ts
5064
- var logger19 = logger.child("FailureHandler");
5250
+ var logger24 = logger.child("FailureHandler");
5065
5251
  async function handleFailure(err, issue, wtCtx, deps) {
5066
5252
  const errorMsg = err.message;
5067
5253
  const isRetryable = err instanceof AIExecutionError ? err.isRetryable : true;
5068
5254
  const wasActiveAtTimeout = err instanceof AIExecutionError && err.wasActiveAtTimeout;
5069
- logger19.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
5255
+ logger24.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
5070
5256
  metrics.incCounter("iaf_issues_failed_total");
5071
5257
  const currentRecord = deps.tracker.get(issue.iid);
5072
5258
  const failedAtState = currentRecord?.state || "pending" /* Pending */;
@@ -5079,11 +5265,11 @@ async function handleFailure(err, issue, wtCtx, deps) {
5079
5265
  }
5080
5266
  }
5081
5267
  if (wasReset) {
5082
- logger19.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
5268
+ logger24.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
5083
5269
  throw err;
5084
5270
  }
5085
5271
  if (failedAtState === "paused" /* Paused */) {
5086
- logger19.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
5272
+ logger24.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
5087
5273
  throw err;
5088
5274
  }
5089
5275
  try {
@@ -5105,7 +5291,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
5105
5291
  try {
5106
5292
  await deps.claimer.releaseClaim(issue.id, issue.iid, "failed");
5107
5293
  } catch (releaseErr) {
5108
- logger19.warn("Failed to release lock on failure", {
5294
+ logger24.warn("Failed to release lock on failure", {
5109
5295
  iid: issue.iid,
5110
5296
  error: releaseErr.message
5111
5297
  });
@@ -5113,7 +5299,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
5113
5299
  }
5114
5300
  deps.stopPreviewServers(issue.iid);
5115
5301
  const preservedDirs = wtCtx.workspace ? [wtCtx.workspace.primary.gitRootDir, ...wtCtx.workspace.associates.map((a) => a.gitRootDir)] : [wtCtx.gitRootDir];
5116
- logger19.info("Worktree(s) preserved for debugging", {
5302
+ logger24.info("Worktree(s) preserved for debugging", {
5117
5303
  primary: wtCtx.gitRootDir,
5118
5304
  all: preservedDirs
5119
5305
  });
@@ -5122,7 +5308,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
5122
5308
 
5123
5309
  // src/orchestrator/PipelineOrchestrator.ts
5124
5310
  var execFileAsync2 = promisify2(execFile2);
5125
- var logger20 = logger.child("PipelineOrchestrator");
5311
+ var logger25 = logger.child("PipelineOrchestrator");
5126
5312
  var PipelineOrchestrator = class {
5127
5313
  config;
5128
5314
  gongfeng;
@@ -5152,7 +5338,7 @@ var PipelineOrchestrator = class {
5152
5338
  setAIRunner(runner) {
5153
5339
  this.aiRunner = runner;
5154
5340
  this.conflictResolver = new ConflictResolver(runner);
5155
- logger20.info("AIRunner replaced via hot-reload");
5341
+ logger25.info("AIRunner replaced via hot-reload");
5156
5342
  }
5157
5343
  constructor(config, gongfeng, git, aiRunner, tracker, supplementStore, mainGitMutex, eventBusInstance, wsConfig, tenantId, e2eAiRunner) {
5158
5344
  this.config = config;
@@ -5170,7 +5356,7 @@ var PipelineOrchestrator = class {
5170
5356
  this.pipelineDef = mode === "plan-mode" ? buildPlanModePipeline({ releaseEnabled: config.release.enabled, e2eEnabled: config.e2e.enabled }) : getPipelineDef(mode);
5171
5357
  registerPipeline(this.pipelineDef);
5172
5358
  this.lifecycleManager = createLifecycleManager(this.pipelineDef);
5173
- logger20.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
5359
+ logger25.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
5174
5360
  this.portAllocator = new PortAllocator({
5175
5361
  backendPortBase: config.e2e.backendPortBase,
5176
5362
  frontendPortBase: config.e2e.frontendPortBase
@@ -5186,7 +5372,7 @@ var PipelineOrchestrator = class {
5186
5372
  mainGitMutex: this.mainGitMutex,
5187
5373
  gongfengApiUrl: config.gongfeng.apiUrl
5188
5374
  });
5189
- logger20.info("WorkspaceManager initialized", {
5375
+ logger25.info("WorkspaceManager initialized", {
5190
5376
  tenantId: this.tenantId,
5191
5377
  primary: effectiveWsConfig.primary.name,
5192
5378
  associates: effectiveWsConfig.associates.map((a) => a.name)
@@ -5207,7 +5393,7 @@ var PipelineOrchestrator = class {
5207
5393
  this.claimer = claimer;
5208
5394
  }
5209
5395
  async cleanupStaleState() {
5210
- logger20.info("Cleaning up stale worktree state...");
5396
+ logger25.info("Cleaning up stale worktree state...");
5211
5397
  let cleaned = 0;
5212
5398
  const repoGitRoot = this.config.project.gitRootDir;
5213
5399
  try {
@@ -5220,7 +5406,7 @@ var PipelineOrchestrator = class {
5220
5406
  try {
5221
5407
  await fs11.access(gitFile);
5222
5408
  } catch {
5223
- logger20.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
5409
+ logger25.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
5224
5410
  await this.mainGit.worktreeRemove(wtDir, true).catch(() => {
5225
5411
  });
5226
5412
  await this.mainGit.worktreePrune();
@@ -5229,32 +5415,32 @@ var PipelineOrchestrator = class {
5229
5415
  }
5230
5416
  const wtGit = new GitOperations(wtDir);
5231
5417
  if (await wtGit.isRebaseInProgress()) {
5232
- logger20.warn("Aborting residual rebase in worktree", { dir: wtDir });
5418
+ logger25.warn("Aborting residual rebase in worktree", { dir: wtDir });
5233
5419
  await wtGit.rebaseAbort();
5234
5420
  cleaned++;
5235
5421
  }
5236
5422
  const indexLock = path12.join(wtDir, ".git", "index.lock");
5237
5423
  try {
5238
5424
  await fs11.unlink(indexLock);
5239
- logger20.warn("Removed stale index.lock", { path: indexLock });
5425
+ logger25.warn("Removed stale index.lock", { path: indexLock });
5240
5426
  cleaned++;
5241
5427
  } catch {
5242
5428
  }
5243
5429
  } catch (err) {
5244
- logger20.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
5430
+ logger25.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
5245
5431
  }
5246
5432
  }
5247
5433
  } catch (err) {
5248
- logger20.warn("Failed to list worktrees for cleanup", { error: err.message });
5434
+ logger25.warn("Failed to list worktrees for cleanup", { error: err.message });
5249
5435
  }
5250
5436
  const mainIndexLock = path12.join(repoGitRoot, ".git", "index.lock");
5251
5437
  try {
5252
5438
  await fs11.unlink(mainIndexLock);
5253
- logger20.warn("Removed stale main repo index.lock", { path: mainIndexLock });
5439
+ logger25.warn("Removed stale main repo index.lock", { path: mainIndexLock });
5254
5440
  cleaned++;
5255
5441
  } catch {
5256
5442
  }
5257
- logger20.info("Stale state cleanup complete", { cleaned });
5443
+ logger25.info("Stale state cleanup complete", { cleaned });
5258
5444
  }
5259
5445
  /**
5260
5446
  * 重启后清理幽灵端口分配。
@@ -5267,7 +5453,7 @@ var PipelineOrchestrator = class {
5267
5453
  for (const record of this.tracker.getAll()) {
5268
5454
  if (record.ports) {
5269
5455
  const iid = getIid(record);
5270
- logger20.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
5456
+ logger25.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
5271
5457
  this.tracker.updateState(iid, record.state, {
5272
5458
  ports: void 0,
5273
5459
  previewStartedAt: void 0
@@ -5312,20 +5498,20 @@ var PipelineOrchestrator = class {
5312
5498
  }
5313
5499
  try {
5314
5500
  await this.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
5315
- logger20.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5501
+ logger25.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5316
5502
  } catch (err) {
5317
- logger20.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
5503
+ logger25.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
5318
5504
  }
5319
5505
  }
5320
5506
  async installDependencies(workDir) {
5321
- logger20.info("Installing dependencies in worktree", { workDir });
5507
+ logger25.info("Installing dependencies in worktree", { workDir });
5322
5508
  const knowledge = getProjectKnowledge() ?? KNOWLEDGE_DEFAULTS;
5323
5509
  const pkgMgr = knowledge.toolchain.packageManager.toLowerCase();
5324
5510
  const isNodeProject = ["npm", "pnpm", "yarn", "bun"].some((m) => pkgMgr.includes(m));
5325
5511
  if (isNodeProject) {
5326
5512
  const ready = await this.ensureNodeModules(workDir);
5327
5513
  if (ready) {
5328
- logger20.info("node_modules ready \u2014 skipping install");
5514
+ logger25.info("node_modules ready \u2014 skipping install");
5329
5515
  return;
5330
5516
  }
5331
5517
  }
@@ -5338,10 +5524,10 @@ var PipelineOrchestrator = class {
5338
5524
  maxBuffer: 10 * 1024 * 1024,
5339
5525
  timeout: 3e5
5340
5526
  });
5341
- logger20.info("Dependencies installed");
5527
+ logger25.info("Dependencies installed");
5342
5528
  } catch (err) {
5343
5529
  if (fallbackCmd) {
5344
- logger20.warn(`${installCmd} failed, retrying with fallback command`, {
5530
+ logger25.warn(`${installCmd} failed, retrying with fallback command`, {
5345
5531
  error: err.message
5346
5532
  });
5347
5533
  const [fallbackBin, ...fallbackArgs] = fallbackCmd.split(/\s+/);
@@ -5351,14 +5537,14 @@ var PipelineOrchestrator = class {
5351
5537
  maxBuffer: 10 * 1024 * 1024,
5352
5538
  timeout: 3e5
5353
5539
  });
5354
- logger20.info("Dependencies installed (fallback)");
5540
+ logger25.info("Dependencies installed (fallback)");
5355
5541
  } catch (retryErr) {
5356
- logger20.warn("Fallback install also failed", {
5542
+ logger25.warn("Fallback install also failed", {
5357
5543
  error: retryErr.message
5358
5544
  });
5359
5545
  }
5360
5546
  } else {
5361
- logger20.warn("Install failed, no fallback configured", {
5547
+ logger25.warn("Install failed, no fallback configured", {
5362
5548
  error: err.message
5363
5549
  });
5364
5550
  }
@@ -5368,7 +5554,7 @@ var PipelineOrchestrator = class {
5368
5554
  const targetBin = path12.join(workDir, "node_modules", ".bin");
5369
5555
  try {
5370
5556
  await fs11.access(targetBin);
5371
- logger20.info("node_modules already complete (has .bin/)");
5557
+ logger25.info("node_modules already complete (has .bin/)");
5372
5558
  return true;
5373
5559
  } catch {
5374
5560
  }
@@ -5377,19 +5563,19 @@ var PipelineOrchestrator = class {
5377
5563
  try {
5378
5564
  await fs11.access(sourceNM);
5379
5565
  } catch {
5380
- logger20.warn("Main repo node_modules not found, skipping seed", { sourceNM });
5566
+ logger25.warn("Main repo node_modules not found, skipping seed", { sourceNM });
5381
5567
  return false;
5382
5568
  }
5383
- logger20.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
5569
+ logger25.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
5384
5570
  try {
5385
5571
  await execFileAsync2("rm", ["-rf", targetNM], { timeout: 6e4 });
5386
5572
  await execFileAsync2("cp", ["-a", "--reflink=auto", sourceNM, targetNM], {
5387
5573
  timeout: 12e4
5388
5574
  });
5389
- logger20.info("node_modules seeded from main repo");
5575
+ logger25.info("node_modules seeded from main repo");
5390
5576
  return true;
5391
5577
  } catch (err) {
5392
- logger20.warn("Failed to seed node_modules from main repo", {
5578
+ logger25.warn("Failed to seed node_modules from main repo", {
5393
5579
  error: err.message
5394
5580
  });
5395
5581
  return false;
@@ -5399,16 +5585,16 @@ var PipelineOrchestrator = class {
5399
5585
  const record = this.tracker.get(issueIid);
5400
5586
  if (!record) throw new IssueNotFoundError(issueIid);
5401
5587
  const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
5402
- logger20.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
5588
+ logger25.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
5403
5589
  this.pendingActions.set(issueIid, "restart");
5404
5590
  this.aiRunner.killByWorkDir(wtCtx.workDir);
5405
5591
  this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
5406
5592
  this.stopPreviewServers(issueIid);
5407
5593
  try {
5408
5594
  const deleted = await this.gongfeng.cleanupAgentNotes(getExternalId(record));
5409
- logger20.info("Agent notes cleaned up", { issueIid, deleted });
5595
+ logger25.info("Agent notes cleaned up", { issueIid, deleted });
5410
5596
  } catch (err) {
5411
- logger20.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
5597
+ logger25.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
5412
5598
  }
5413
5599
  await this.mainGitMutex.runExclusive(async () => {
5414
5600
  await this.cleanupWorktree(wtCtx);
@@ -5425,19 +5611,19 @@ var PipelineOrchestrator = class {
5425
5611
  await this.cleanupE2eOutputs(issueIid);
5426
5612
  this.tracker.resetFull(issueIid);
5427
5613
  this.pendingActions.delete(issueIid);
5428
- logger20.info("Issue restarted", { issueIid });
5614
+ logger25.info("Issue restarted", { issueIid });
5429
5615
  }
5430
5616
  async cancelIssue(issueIid) {
5431
5617
  const record = this.tracker.get(issueIid);
5432
5618
  if (!record) throw new IssueNotFoundError(issueIid);
5433
5619
  const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
5434
- logger20.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
5620
+ logger25.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
5435
5621
  this.aiRunner.killByWorkDir(wtCtx.workDir);
5436
5622
  this.stopPreviewServers(issueIid);
5437
5623
  try {
5438
5624
  await this.gongfeng.removeLabelsWithPrefix(getExternalId(record), "auto-finish");
5439
5625
  } catch (err) {
5440
- logger20.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
5626
+ logger25.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
5441
5627
  }
5442
5628
  await this.mainGitMutex.runExclusive(async () => {
5443
5629
  await this.cleanupWorktree(wtCtx);
@@ -5454,7 +5640,7 @@ var PipelineOrchestrator = class {
5454
5640
  this.tracker.clearProcessingLock(issueIid);
5455
5641
  this.tracker.updateState(issueIid, "skipped" /* Skipped */);
5456
5642
  await this.cleanupE2eOutputs(issueIid);
5457
- logger20.info("Issue cancelled", { issueIid });
5643
+ logger25.info("Issue cancelled", { issueIid });
5458
5644
  }
5459
5645
  /**
5460
5646
  * Remove the E2E output directory for an issue: {uatVendorDir}/outputs/issue-{iid}
@@ -5466,9 +5652,9 @@ var PipelineOrchestrator = class {
5466
5652
  const outputDir = path12.join(abs, "outputs", `issue-${issueIid}`);
5467
5653
  try {
5468
5654
  await fs11.rm(outputDir, { recursive: true, force: true });
5469
- logger20.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
5655
+ logger25.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
5470
5656
  } catch (err) {
5471
- logger20.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
5657
+ logger25.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
5472
5658
  }
5473
5659
  }
5474
5660
  /**
@@ -5481,9 +5667,9 @@ var PipelineOrchestrator = class {
5481
5667
  const wsRoot = this.workspaceManager.getWorkspaceRoot(issueIid);
5482
5668
  try {
5483
5669
  await fs11.rm(wsRoot, { recursive: true, force: true });
5484
- logger20.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
5670
+ logger25.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
5485
5671
  } catch (err) {
5486
- logger20.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
5672
+ logger25.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
5487
5673
  }
5488
5674
  }
5489
5675
  retryFromPhase(issueIid, phase) {
@@ -5499,7 +5685,7 @@ var PipelineOrchestrator = class {
5499
5685
  this.aiRunner.killByWorkDir(wtCtx.workDir);
5500
5686
  }
5501
5687
  this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
5502
- logger20.info("Retrying issue from phase", { issueIid, phase });
5688
+ logger25.info("Retrying issue from phase", { issueIid, phase });
5503
5689
  const ok = this.tracker.resetToPhase(issueIid, phase, issueDef);
5504
5690
  if (!ok) {
5505
5691
  throw new InvalidPhaseError(phase);
@@ -5526,7 +5712,7 @@ var PipelineOrchestrator = class {
5526
5712
  } else {
5527
5713
  this.tracker.pauseIssue(issueIid, record.currentPhase ?? "");
5528
5714
  }
5529
- logger20.info("Issue abort requested", { issueIid, state: record.state });
5715
+ logger25.info("Issue abort requested", { issueIid, state: record.state });
5530
5716
  }
5531
5717
  continueIssue(issueIid) {
5532
5718
  const record = this.tracker.get(issueIid);
@@ -5536,7 +5722,7 @@ var PipelineOrchestrator = class {
5536
5722
  }
5537
5723
  const issueDef = this.getIssueSpecificPipelineDef(record);
5538
5724
  this.tracker.resumeFromPause(issueIid, issueDef, false);
5539
- logger20.info("Issue continued from pause", { issueIid });
5725
+ logger25.info("Issue continued from pause", { issueIid });
5540
5726
  }
5541
5727
  redoPhase(issueIid) {
5542
5728
  const record = this.tracker.get(issueIid);
@@ -5580,7 +5766,7 @@ var PipelineOrchestrator = class {
5580
5766
  }
5581
5767
  this.eventBus.emitTyped("issue:redone", { issueIid });
5582
5768
  }
5583
- logger20.info("Issue redo requested", { issueIid, state: record.state });
5769
+ logger25.info("Issue redo requested", { issueIid, state: record.state });
5584
5770
  }
5585
5771
  /**
5586
5772
  * 处理中止/重做的共享逻辑:
@@ -5653,7 +5839,7 @@ var PipelineOrchestrator = class {
5653
5839
  async _processIssueImpl(issue) {
5654
5840
  const branchName = `${this.config.project.branchPrefix}-${issue.iid}`;
5655
5841
  const wtCtx = this.computeWorktreeContext(issue.iid, branchName);
5656
- logger20.info("Processing issue", {
5842
+ logger25.info("Processing issue", {
5657
5843
  iid: issue.iid,
5658
5844
  title: issue.title,
5659
5845
  branchName,
@@ -5760,7 +5946,7 @@ var PipelineOrchestrator = class {
5760
5946
  title,
5761
5947
  description
5762
5948
  });
5763
- logger20.info("Merge request created successfully", {
5949
+ logger25.info("Merge request created successfully", {
5764
5950
  iid: issue.iid,
5765
5951
  mrIid: mr.iid,
5766
5952
  mrUrl: mr.web_url
@@ -5768,7 +5954,7 @@ var PipelineOrchestrator = class {
5768
5954
  return { url: mr.web_url, iid: mr.iid };
5769
5955
  } catch (err) {
5770
5956
  const errorMsg = err.message;
5771
- logger20.warn("Failed to create merge request, trying to find existing one", {
5957
+ logger25.warn("Failed to create merge request, trying to find existing one", {
5772
5958
  iid: issue.iid,
5773
5959
  error: errorMsg
5774
5960
  });
@@ -5785,7 +5971,7 @@ var PipelineOrchestrator = class {
5785
5971
  this.config.project.baseBranch
5786
5972
  );
5787
5973
  if (existing) {
5788
- logger20.info("Found existing merge request", {
5974
+ logger25.info("Found existing merge request", {
5789
5975
  iid: issueIid,
5790
5976
  mrIid: existing.iid,
5791
5977
  mrUrl: existing.web_url
@@ -5793,7 +5979,7 @@ var PipelineOrchestrator = class {
5793
5979
  return { url: existing.web_url, iid: existing.iid };
5794
5980
  }
5795
5981
  } catch (findErr) {
5796
- logger20.warn("Failed to find existing merge request", {
5982
+ logger25.warn("Failed to find existing merge request", {
5797
5983
  iid: issueIid,
5798
5984
  error: findErr.message
5799
5985
  });
@@ -5838,7 +6024,7 @@ var PipelineOrchestrator = class {
5838
6024
  });
5839
6025
  return ports;
5840
6026
  } catch (err) {
5841
- logger20.error("Failed to start preview servers", {
6027
+ logger25.error("Failed to start preview servers", {
5842
6028
  iid: issue.iid,
5843
6029
  error: err.message
5844
6030
  });
@@ -5873,7 +6059,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5873
6059
  await this.mainGitMutex.runExclusive(async () => {
5874
6060
  await this.cleanupWorktree(wtCtx);
5875
6061
  });
5876
- logger20.info("Preview stopped and worktree cleaned", { iid: issueIid });
6062
+ logger25.info("Preview stopped and worktree cleaned", { iid: issueIid });
5877
6063
  }
5878
6064
  async markDeployed(issueIid) {
5879
6065
  const record = this.tracker.get(issueIid);
@@ -5890,7 +6076,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5890
6076
  try {
5891
6077
  await this.gongfeng.closeIssue(externalId);
5892
6078
  } catch (err) {
5893
- logger20.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
6079
+ logger25.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
5894
6080
  }
5895
6081
  try {
5896
6082
  const issue = await this.gongfeng.getIssueDetail(externalId);
@@ -5898,10 +6084,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5898
6084
  labels.push("auto-finish:deployed");
5899
6085
  await this.gongfeng.updateIssueLabels(externalId, labels);
5900
6086
  } catch (err) {
5901
- logger20.warn("Failed to update labels", { iid: issueIid, error: err.message });
6087
+ logger25.warn("Failed to update labels", { iid: issueIid, error: err.message });
5902
6088
  }
5903
6089
  this.tracker.updateState(issueIid, "deployed" /* Deployed */);
5904
- logger20.info("Issue marked as deployed", { iid: issueIid });
6090
+ logger25.info("Issue marked as deployed", { iid: issueIid });
5905
6091
  }
5906
6092
  async restartPreview(issueIid) {
5907
6093
  const record = this.tracker.get(issueIid);
@@ -5928,7 +6114,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5928
6114
  throw err;
5929
6115
  }
5930
6116
  const url = this.buildPreviewUrl(issueIid);
5931
- logger20.info("Preview restarted", { iid: issueIid, url });
6117
+ logger25.info("Preview restarted", { iid: issueIid, url });
5932
6118
  return url;
5933
6119
  }
5934
6120
  getPreviewHost() {
@@ -5961,7 +6147,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5961
6147
  if (!record) throw new IssueNotFoundError(issueIid);
5962
6148
  const baseBranch = this.config.project.baseBranch;
5963
6149
  const branchName = record.branchName;
5964
- logger20.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
6150
+ logger25.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
5965
6151
  this.tracker.updateState(issueIid, "resolving_conflict" /* ResolvingConflict */);
5966
6152
  this.eventBus.emitTyped("conflict:started", { issueIid });
5967
6153
  try {
@@ -5994,7 +6180,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5994
6180
  });
5995
6181
  }
5996
6182
  });
5997
- logger20.info("Running verification after conflict resolution", { issueIid });
6183
+ logger25.info("Running verification after conflict resolution", { issueIid });
5998
6184
  const wtPlan = new PlanPersistence(wtCtx.workDir, issueIid);
5999
6185
  wtPlan.ensureDir();
6000
6186
  const verifyPhase = createPhase("verify", this.aiRunner, wtGit, wtPlan, this.config);
@@ -6032,10 +6218,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
6032
6218
  } catch {
6033
6219
  }
6034
6220
  await this.commentOnMr(record.mrUrl, t("conflict.mrResolvedComment"));
6035
- logger20.info("Conflict resolution completed", { issueIid });
6221
+ logger25.info("Conflict resolution completed", { issueIid });
6036
6222
  } catch (err) {
6037
6223
  const errorMsg = err.message;
6038
- logger20.error("Conflict resolution failed", { issueIid, error: errorMsg });
6224
+ logger25.error("Conflict resolution failed", { issueIid, error: errorMsg });
6039
6225
  try {
6040
6226
  const wtGit = new GitOperations(wtCtx.gitRootDir);
6041
6227
  if (await wtGit.isRebaseInProgress()) {
@@ -6065,7 +6251,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
6065
6251
  try {
6066
6252
  await this.gongfeng.createMergeRequestNote(mrIid, body);
6067
6253
  } catch (err) {
6068
- logger20.warn("Failed to comment on MR", { mrIid, error: err.message });
6254
+ logger25.warn("Failed to comment on MR", { mrIid, error: err.message });
6069
6255
  }
6070
6256
  }
6071
6257
  };
@@ -6141,7 +6327,7 @@ ${questions}
6141
6327
  }
6142
6328
 
6143
6329
  // src/services/BrainstormService.ts
6144
- var logger21 = logger.child("Brainstorm");
6330
+ var logger26 = logger.child("Brainstorm");
6145
6331
  function agentConfigToAIConfig(agentCfg, timeoutMs) {
6146
6332
  return {
6147
6333
  mode: agentCfg.mode,
@@ -6177,7 +6363,7 @@ var BrainstormService = class {
6177
6363
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6178
6364
  };
6179
6365
  this.sessions.set(session.id, session);
6180
- logger21.info("Created brainstorm session", { sessionId: session.id });
6366
+ logger26.info("Created brainstorm session", { sessionId: session.id });
6181
6367
  return session;
6182
6368
  }
6183
6369
  getSession(id) {
@@ -6186,7 +6372,7 @@ var BrainstormService = class {
6186
6372
  async generate(sessionId, onEvent) {
6187
6373
  const session = this.requireSession(sessionId);
6188
6374
  session.status = "generating";
6189
- logger21.info("Generating SDD", { sessionId });
6375
+ logger26.info("Generating SDD", { sessionId });
6190
6376
  const prompt = buildGeneratePrompt(session.transcript);
6191
6377
  const result = await this.generatorRunner.run({
6192
6378
  prompt,
@@ -6212,7 +6398,7 @@ var BrainstormService = class {
6212
6398
  const session = this.requireSession(sessionId);
6213
6399
  const roundNum = session.rounds.length + 1;
6214
6400
  session.status = "reviewing";
6215
- logger21.info("Reviewing SDD", { sessionId, round: roundNum });
6401
+ logger26.info("Reviewing SDD", { sessionId, round: roundNum });
6216
6402
  onEvent?.({ type: "round:start", data: { round: roundNum, phase: "review" }, round: roundNum });
6217
6403
  const prompt = buildReviewPrompt(session.currentSdd, roundNum);
6218
6404
  const result = await this.reviewerRunner.run({
@@ -6245,7 +6431,7 @@ var BrainstormService = class {
6245
6431
  throw new Error("No review round to refine from");
6246
6432
  }
6247
6433
  session.status = "refining";
6248
- logger21.info("Refining SDD", { sessionId, round: currentRound.round });
6434
+ logger26.info("Refining SDD", { sessionId, round: currentRound.round });
6249
6435
  const prompt = buildRefinePrompt(currentRound.questions);
6250
6436
  const result = await this.generatorRunner.run({
6251
6437
  prompt,
@@ -6332,4 +6518,4 @@ export {
6332
6518
  PipelineOrchestrator,
6333
6519
  BrainstormService
6334
6520
  };
6335
- //# sourceMappingURL=chunk-LDGK5NMS.js.map
6521
+ //# sourceMappingURL=chunk-WZGEYHCC.js.map