@xdevops/issue-auto-finish 1.0.91 → 1.0.93

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 (67) hide show
  1. package/dist/{PtyRunner-NYASBTRP.js → PtyRunner-XMWDMH3L.js} +4 -2
  2. package/dist/ai-runner/DialogClassifier.d.ts +44 -0
  3. package/dist/ai-runner/DialogClassifier.d.ts.map +1 -0
  4. package/dist/ai-runner/PtyRunner.d.ts +17 -0
  5. package/dist/ai-runner/PtyRunner.d.ts.map +1 -1
  6. package/dist/{ai-runner-TOHVJJ76.js → ai-runner-S2ATTGWX.js} +2 -2
  7. package/dist/{analyze-DBH4K3J7.js → analyze-DAVYPBHK.js} +2 -2
  8. package/dist/{braindump-RYI4BGMG.js → braindump-A4R3A4QT.js} +2 -2
  9. package/dist/{chunk-ENF24C44.js → chunk-2XACBKPB.js} +2 -2
  10. package/dist/{chunk-UMQYEYLO.js → chunk-BPVRMZU4.js} +9 -9
  11. package/dist/{chunk-4XMYOXGZ.js → chunk-HD6V7KPE.js} +878 -67
  12. package/dist/chunk-HD6V7KPE.js.map +1 -0
  13. package/dist/{chunk-LDGK5NMS.js → chunk-OPWP73PW.js} +797 -1056
  14. package/dist/chunk-OPWP73PW.js.map +1 -0
  15. package/dist/{chunk-CQ66LL7P.js → chunk-SNSEW7DS.js} +1 -1
  16. package/dist/cli.js +5 -5
  17. package/dist/hooks/HookInjector.d.ts +14 -0
  18. package/dist/hooks/HookInjector.d.ts.map +1 -1
  19. package/dist/index.js +4 -4
  20. package/dist/{init-UKTP7LXS.js → init-OD7CLRWK.js} +2 -2
  21. package/dist/lib.js +2 -2
  22. package/dist/lifecycle/DefaultLifecycleHook.d.ts +21 -0
  23. package/dist/lifecycle/DefaultLifecycleHook.d.ts.map +1 -0
  24. package/dist/lifecycle/FeedbackTypes.d.ts +52 -0
  25. package/dist/lifecycle/FeedbackTypes.d.ts.map +1 -0
  26. package/dist/lifecycle/PhaseLifecycleHook.d.ts +70 -0
  27. package/dist/lifecycle/PhaseLifecycleHook.d.ts.map +1 -0
  28. package/dist/lifecycle/PhaseMiddleware.d.ts +47 -0
  29. package/dist/lifecycle/PhaseMiddleware.d.ts.map +1 -0
  30. package/dist/lifecycle/PhaseStateMachine.d.ts +111 -0
  31. package/dist/lifecycle/PhaseStateMachine.d.ts.map +1 -0
  32. package/dist/lifecycle/index.d.ts +8 -0
  33. package/dist/lifecycle/index.d.ts.map +1 -1
  34. package/dist/orchestrator/steps/PhaseHelpers.d.ts +24 -0
  35. package/dist/orchestrator/steps/PhaseHelpers.d.ts.map +1 -0
  36. package/dist/orchestrator/steps/PhaseLoopStep.d.ts +10 -0
  37. package/dist/orchestrator/steps/PhaseLoopStep.d.ts.map +1 -1
  38. package/dist/orchestrator/strategies/AiPhaseStrategy.d.ts +17 -0
  39. package/dist/orchestrator/strategies/AiPhaseStrategy.d.ts.map +1 -0
  40. package/dist/orchestrator/strategies/GateStrategy.d.ts +15 -0
  41. package/dist/orchestrator/strategies/GateStrategy.d.ts.map +1 -0
  42. package/dist/orchestrator/strategies/PhaseStrategy.d.ts +16 -0
  43. package/dist/orchestrator/strategies/PhaseStrategy.d.ts.map +1 -0
  44. package/dist/orchestrator/strategies/VerifyFixStrategy.d.ts +15 -0
  45. package/dist/orchestrator/strategies/VerifyFixStrategy.d.ts.map +1 -0
  46. package/dist/orchestrator/strategies/index.d.ts +17 -0
  47. package/dist/orchestrator/strategies/index.d.ts.map +1 -0
  48. package/dist/{restart-MSUWF4ID.js → restart-JVVOYC6C.js} +2 -2
  49. package/dist/run.js +4 -4
  50. package/dist/{start-B4CDZAFG.js → start-INU24RRG.js} +2 -2
  51. package/package.json +1 -1
  52. package/src/web/frontend/dist/assets/index-BrvoaFSK.css +1 -0
  53. package/src/web/frontend/dist/assets/{index-BoYtsxGN.js → index-CmyxgdS_.js} +54 -54
  54. package/src/web/frontend/dist/index.html +2 -2
  55. package/dist/chunk-4XMYOXGZ.js.map +0 -1
  56. package/dist/chunk-LDGK5NMS.js.map +0 -1
  57. package/src/web/frontend/dist/assets/index-DWOHf3bd.css +0 -1
  58. /package/dist/{PtyRunner-NYASBTRP.js.map → PtyRunner-XMWDMH3L.js.map} +0 -0
  59. /package/dist/{ai-runner-TOHVJJ76.js.map → ai-runner-S2ATTGWX.js.map} +0 -0
  60. /package/dist/{analyze-DBH4K3J7.js.map → analyze-DAVYPBHK.js.map} +0 -0
  61. /package/dist/{braindump-RYI4BGMG.js.map → braindump-A4R3A4QT.js.map} +0 -0
  62. /package/dist/{chunk-ENF24C44.js.map → chunk-2XACBKPB.js.map} +0 -0
  63. /package/dist/{chunk-UMQYEYLO.js.map → chunk-BPVRMZU4.js.map} +0 -0
  64. /package/dist/{chunk-CQ66LL7P.js.map → chunk-SNSEW7DS.js.map} +0 -0
  65. /package/dist/{init-UKTP7LXS.js.map → init-OD7CLRWK.js.map} +0 -0
  66. /package/dist/{restart-MSUWF4ID.js.map → restart-JVVOYC6C.js.map} +0 -0
  67. /package/dist/{start-B4CDZAFG.js.map → start-INU24RRG.js.map} +0 -0
@@ -18,6 +18,9 @@ import {
18
18
  rePlanPrompt,
19
19
  verifyPrompt
20
20
  } from "./chunk-GPZX4DSY.js";
21
+ import {
22
+ HookInjector
23
+ } from "./chunk-HD6V7KPE.js";
21
24
  import {
22
25
  getProjectKnowledge
23
26
  } from "./chunk-ACVOOHAR.js";
@@ -233,8 +236,8 @@ var GongfengClient = class {
233
236
  const encoded = encodeURIComponent(this.projectPath);
234
237
  return `${this.apiUrl}/api/v3/projects/${encoded}`;
235
238
  }
236
- async requestRaw(path13, options = {}) {
237
- const url = `${this.projectApiBase}${path13}`;
239
+ async requestRaw(path12, options = {}) {
240
+ const url = `${this.projectApiBase}${path12}`;
238
241
  logger4.debug("API request", { method: options.method || "GET", url });
239
242
  return this.circuitBreaker.execute(
240
243
  () => this.retryPolicy.execute(async () => {
@@ -251,11 +254,11 @@ var GongfengClient = class {
251
254
  throw new GongfengApiError(resp.status, `Gongfeng API error ${resp.status}: ${body}`, body);
252
255
  }
253
256
  return resp;
254
- }, `requestRaw ${options.method || "GET"} ${path13}`)
257
+ }, `requestRaw ${options.method || "GET"} ${path12}`)
255
258
  );
256
259
  }
257
- async request(path13, options = {}) {
258
- const resp = await this.requestRaw(path13, options);
260
+ async request(path12, options = {}) {
261
+ const resp = await this.requestRaw(path12, options);
259
262
  return resp.json();
260
263
  }
261
264
  async createIssue(title, description, labels) {
@@ -434,8 +437,8 @@ var GongfengClient = class {
434
437
  }
435
438
  return mr;
436
439
  }
437
- async requestGlobal(path13, options = {}) {
438
- const url = `${this.apiUrl}${path13}`;
440
+ async requestGlobal(path12, options = {}) {
441
+ const url = `${this.apiUrl}${path12}`;
439
442
  logger4.debug("API request (global)", { method: options.method || "GET", url });
440
443
  const resp = await this.circuitBreaker.execute(
441
444
  () => this.retryPolicy.execute(async () => {
@@ -452,7 +455,7 @@ var GongfengClient = class {
452
455
  throw new GongfengApiError(r.status, `Gongfeng API error ${r.status}: ${body}`, body);
453
456
  }
454
457
  return r;
455
- }, `requestGlobal ${options.method || "GET"} ${path13}`)
458
+ }, `requestGlobal ${options.method || "GET"} ${path12}`)
456
459
  );
457
460
  return resp.json();
458
461
  }
@@ -1646,7 +1649,7 @@ var PlanPersistence = class _PlanPersistence {
1646
1649
  };
1647
1650
 
1648
1651
  // src/phases/BasePhase.ts
1649
- import path5 from "path";
1652
+ import path4 from "path";
1650
1653
 
1651
1654
  // src/rules/RuleResolver.ts
1652
1655
  import { readdir, readFile } from "fs/promises";
@@ -1743,454 +1746,6 @@ ${rule.content}`;
1743
1746
  }
1744
1747
  };
1745
1748
 
1746
- // src/hooks/HookInjector.ts
1747
- import fs3 from "fs";
1748
- import path4 from "path";
1749
- var logger7 = logger.child("HookInjector");
1750
- var HOOKS_DIR = ".claude-plan/.hooks";
1751
- var EVENTS_FILE_NAME = ".hook-events.jsonl";
1752
- var MANIFEST_FILE_NAME = ".artifact-manifest.jsonl";
1753
- var CONTEXT_FILE_NAME = ".hook-context.json";
1754
- var HookInjector = class {
1755
- inject(ctx) {
1756
- this.writeHookScripts(ctx);
1757
- this.writeContextFile(ctx);
1758
- this.writeSettingsLocal(ctx);
1759
- this.initEventsFile(ctx);
1760
- logger7.info("Hooks injected", {
1761
- workDir: ctx.workDir,
1762
- issueIid: ctx.issueIid,
1763
- phase: ctx.phaseName,
1764
- artifacts: ctx.expectedArtifacts
1765
- });
1766
- }
1767
- /**
1768
- * 阶段切换时更新 hooks 配置(重写脚本 + settings.local.json)。
1769
- * 保留 events/manifest 文件(不截断),仅更新脚本和配置。
1770
- */
1771
- updateForPhase(ctx) {
1772
- this.writeHookScripts(ctx);
1773
- this.writeContextFile(ctx);
1774
- this.writeSettingsLocal(ctx);
1775
- logger7.info("Hooks updated for phase", {
1776
- workDir: ctx.workDir,
1777
- issueIid: ctx.issueIid,
1778
- phase: ctx.phaseName
1779
- });
1780
- }
1781
- readManifest(workDir) {
1782
- const manifestPath = path4.join(workDir, ".claude-plan", MANIFEST_FILE_NAME);
1783
- return readJsonl(manifestPath);
1784
- }
1785
- readEvents(workDir) {
1786
- const eventsPath = path4.join(workDir, ".claude-plan", EVENTS_FILE_NAME);
1787
- return readJsonl(eventsPath);
1788
- }
1789
- getEventsFilePath(workDir) {
1790
- return path4.join(workDir, ".claude-plan", EVENTS_FILE_NAME);
1791
- }
1792
- getManifestFilePath(workDir) {
1793
- return path4.join(workDir, ".claude-plan", MANIFEST_FILE_NAME);
1794
- }
1795
- cleanup(workDir) {
1796
- const hooksDir = path4.join(workDir, HOOKS_DIR);
1797
- try {
1798
- if (fs3.existsSync(hooksDir)) {
1799
- fs3.rmSync(hooksDir, { recursive: true });
1800
- }
1801
- } catch (err) {
1802
- logger7.warn("Failed to cleanup hooks", { error: err.message });
1803
- }
1804
- }
1805
- // ---------------------------------------------------------------------------
1806
- // Private
1807
- // ---------------------------------------------------------------------------
1808
- writeHookScripts(ctx) {
1809
- const hooksDir = path4.join(ctx.workDir, HOOKS_DIR);
1810
- fs3.mkdirSync(hooksDir, { recursive: true });
1811
- const eventsFile = path4.join(ctx.workDir, ".claude-plan", EVENTS_FILE_NAME);
1812
- const manifestFile = path4.join(ctx.workDir, ".claude-plan", MANIFEST_FILE_NAME);
1813
- const contextFile = path4.join(ctx.workDir, ".claude-plan", CONTEXT_FILE_NAME);
1814
- const expected = ctx.expectedArtifacts.join(",");
1815
- const phaseExpected = (ctx.phaseExpectedArtifacts ?? ctx.expectedArtifacts).join(",");
1816
- const scripts = [
1817
- { name: "session-start.sh", content: buildSessionStartScript(eventsFile) },
1818
- { name: "compact-restore.sh", content: buildCompactRestoreScript(eventsFile, contextFile) },
1819
- { name: "post-tool-use.sh", content: buildPostToolUseScript(eventsFile, manifestFile, expected) },
1820
- { name: "post-artifact.sh", content: buildPostArtifactScript(manifestFile, expected) },
1821
- { name: "exit-plan-mode.sh", content: buildExitPlanModeScript(eventsFile) },
1822
- { name: "permission.sh", content: buildPermissionScript(eventsFile) },
1823
- { name: "protect-files.sh", content: buildProtectFilesScript(eventsFile, ctx.phaseName, ctx.planDir) },
1824
- { name: "stop.sh", content: buildStopScript(eventsFile, ctx.planDir, phaseExpected) }
1825
- ];
1826
- for (const { name, content } of scripts) {
1827
- const scriptPath = path4.join(hooksDir, name);
1828
- fs3.writeFileSync(scriptPath, content, { mode: 493 });
1829
- }
1830
- }
1831
- writeContextFile(ctx) {
1832
- const contextPath = path4.join(ctx.workDir, ".claude-plan", CONTEXT_FILE_NAME);
1833
- const context = {
1834
- issueIid: ctx.issueIid,
1835
- issueTitle: ctx.issueTitle ?? "",
1836
- issueDescription: ctx.issueDescription ?? "",
1837
- phaseName: ctx.phaseName ?? "",
1838
- expectedArtifacts: ctx.expectedArtifacts,
1839
- planDir: ctx.planDir
1840
- };
1841
- fs3.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
1842
- }
1843
- writeSettingsLocal(ctx) {
1844
- const claudeDir = path4.join(ctx.workDir, ".claude");
1845
- fs3.mkdirSync(claudeDir, { recursive: true });
1846
- const settingsPath = path4.join(claudeDir, "settings.local.json");
1847
- let existing = {};
1848
- if (fs3.existsSync(settingsPath)) {
1849
- try {
1850
- existing = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
1851
- } catch {
1852
- logger7.warn("Failed to parse existing settings.local.json, overwriting");
1853
- }
1854
- }
1855
- const hooksDir = path4.join(ctx.workDir, HOOKS_DIR);
1856
- const hooks = buildHooksConfig(hooksDir, ctx);
1857
- const merged = { ...existing, hooks };
1858
- fs3.writeFileSync(settingsPath, JSON.stringify(merged, null, 2), "utf-8");
1859
- }
1860
- initEventsFile(ctx) {
1861
- const eventsPath = path4.join(ctx.workDir, ".claude-plan", EVENTS_FILE_NAME);
1862
- const manifestPath = path4.join(ctx.workDir, ".claude-plan", MANIFEST_FILE_NAME);
1863
- fs3.writeFileSync(eventsPath, "", "utf-8");
1864
- fs3.writeFileSync(manifestPath, "", "utf-8");
1865
- }
1866
- };
1867
- function buildHooksConfig(hooksDir, ctx) {
1868
- const isPlanPhase = ctx.phaseName === "plan";
1869
- const artifactIfPatterns = buildArtifactIfPatterns(ctx.expectedArtifacts);
1870
- const config = {
1871
- SessionStart: [
1872
- {
1873
- hooks: [{
1874
- type: "command",
1875
- command: path4.join(hooksDir, "session-start.sh"),
1876
- timeout: 5
1877
- }]
1878
- },
1879
- {
1880
- matcher: "compact",
1881
- hooks: [{
1882
- type: "command",
1883
- command: path4.join(hooksDir, "compact-restore.sh"),
1884
- timeout: 5
1885
- }]
1886
- }
1887
- ],
1888
- PreToolUse: [
1889
- {
1890
- matcher: "Edit|Write",
1891
- hooks: [{
1892
- type: "command",
1893
- command: path4.join(hooksDir, "protect-files.sh"),
1894
- timeout: 5,
1895
- ...buildProtectIfClause(ctx.phaseName)
1896
- }]
1897
- }
1898
- ],
1899
- PostToolUse: buildPostToolUseConfig(hooksDir, artifactIfPatterns),
1900
- PermissionRequest: buildPermissionRequestConfig(hooksDir, isPlanPhase),
1901
- Stop: [{
1902
- hooks: [{
1903
- type: "command",
1904
- command: path4.join(hooksDir, "stop.sh"),
1905
- timeout: 15
1906
- }]
1907
- }]
1908
- };
1909
- return config;
1910
- }
1911
- function buildPermissionRequestConfig(hooksDir, isPlanPhase) {
1912
- const groups = [];
1913
- if (isPlanPhase) {
1914
- groups.push({
1915
- matcher: "ExitPlanMode",
1916
- hooks: [{
1917
- type: "command",
1918
- command: path4.join(hooksDir, "exit-plan-mode.sh"),
1919
- timeout: 5
1920
- }]
1921
- });
1922
- }
1923
- groups.push({
1924
- matcher: "Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch|mcp__.*",
1925
- hooks: [{
1926
- type: "command",
1927
- command: path4.join(hooksDir, "permission.sh"),
1928
- timeout: 5
1929
- }]
1930
- });
1931
- return groups;
1932
- }
1933
- function buildPostToolUseConfig(hooksDir, artifactIfPatterns) {
1934
- const groups = [];
1935
- if (artifactIfPatterns) {
1936
- groups.push({
1937
- matcher: "Write|Edit",
1938
- hooks: [{
1939
- type: "command",
1940
- command: path4.join(hooksDir, "post-artifact.sh"),
1941
- timeout: 10,
1942
- if: artifactIfPatterns
1943
- }]
1944
- });
1945
- }
1946
- groups.push({
1947
- matcher: "Write|Edit",
1948
- hooks: [{
1949
- type: "command",
1950
- command: path4.join(hooksDir, "post-tool-use.sh"),
1951
- timeout: 10
1952
- }]
1953
- });
1954
- return groups;
1955
- }
1956
- function buildArtifactIfPatterns(artifacts) {
1957
- if (artifacts.length === 0) return void 0;
1958
- return artifacts.flatMap((f) => [`Write(*${f})`, `Edit(*${f})`]).join("|");
1959
- }
1960
- function buildProtectIfClause(phaseName) {
1961
- const alwaysProtected = [".env", ".env.*", "package-lock.json", "pnpm-lock.yaml"];
1962
- const patterns = [...alwaysProtected];
1963
- if (phaseName === "build") {
1964
- patterns.push("01-plan.md");
1965
- }
1966
- if (phaseName === "verify") {
1967
- patterns.push("01-plan.md");
1968
- }
1969
- const ifValue = patterns.flatMap((f) => [`Edit(*${f})`, `Write(*${f})`]).join("|");
1970
- return { if: ifValue };
1971
- }
1972
- function buildSessionStartScript(eventsFile) {
1973
- return `#!/bin/bash
1974
- set -euo pipefail
1975
- INPUT=$(cat)
1976
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
1977
- printf '{"ts":"%s","event":"session_start","session_id":"%s"}\\n' \\
1978
- "$(date -u +%FT%TZ)" "$SESSION_ID" >> ${quote(eventsFile)}
1979
- exit 0
1980
- `;
1981
- }
1982
- function buildCompactRestoreScript(eventsFile, contextFile) {
1983
- return `#!/bin/bash
1984
- set -euo pipefail
1985
-
1986
- CONTEXT_FILE=${quote(contextFile)}
1987
- if [ ! -f "$CONTEXT_FILE" ]; then
1988
- exit 0
1989
- fi
1990
-
1991
- ISSUE_IID=$(jq -r '.issueIid // empty' < "$CONTEXT_FILE")
1992
- ISSUE_TITLE=$(jq -r '.issueTitle // empty' < "$CONTEXT_FILE")
1993
- ISSUE_DESC=$(jq -r '.issueDescription // empty' < "$CONTEXT_FILE")
1994
- PHASE=$(jq -r '.phaseName // empty' < "$CONTEXT_FILE")
1995
- PLAN_DIR=$(jq -r '.planDir // empty' < "$CONTEXT_FILE")
1996
- ARTIFACTS=$(jq -r '.expectedArtifacts | join(", ") // empty' < "$CONTEXT_FILE")
1997
-
1998
- READY=""
1999
- MISSING=""
2000
- for f in $(jq -r '.expectedArtifacts[]' < "$CONTEXT_FILE" 2>/dev/null); do
2001
- FPATH="$PLAN_DIR/$f"
2002
- if [ -f "$FPATH" ] && [ "$(wc -c < "$FPATH")" -ge 50 ]; then
2003
- READY="$READY $f"
2004
- else
2005
- MISSING="$MISSING $f"
2006
- fi
2007
- done
2008
- READY=$(echo "$READY" | xargs)
2009
- MISSING=$(echo "$MISSING" | xargs)
2010
-
2011
- printf '{"ts":"%s","event":"compact_restore"}\\n' "$(date -u +%FT%TZ)" >> ${quote(eventsFile)}
2012
-
2013
- cat <<CONTEXT
2014
- [\u4E0A\u4E0B\u6587\u6062\u590D \u2014 compaction \u540E\u81EA\u52A8\u6CE8\u5165]
2015
- Issue #$ISSUE_IID: $ISSUE_TITLE
2016
- \u5F53\u524D\u9636\u6BB5: $PHASE
2017
- \u9884\u671F\u4EA7\u7269: $ARTIFACTS
2018
- \u5DF2\u5C31\u7EEA: \${READY:-\u65E0}
2019
- \u672A\u5B8C\u6210: \${MISSING:-\u65E0}
2020
-
2021
- \u9700\u6C42\u63CF\u8FF0:
2022
- $ISSUE_DESC
2023
- CONTEXT
2024
- exit 0
2025
- `;
2026
- }
2027
- function buildPostToolUseScript(eventsFile, manifestFile, expected) {
2028
- return `#!/bin/bash
2029
- set -euo pipefail
2030
- INPUT=$(cat)
2031
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
2032
- [ -z "$FILE_PATH" ] && exit 0
2033
-
2034
- EXPECTED=${quote(expected)}
2035
- BASENAME=$(basename "$FILE_PATH")
2036
-
2037
- if echo "$EXPECTED" | tr ',' '\\n' | grep -qx "$BASENAME"; then
2038
- BYTES=$(wc -c < "$FILE_PATH" 2>/dev/null || echo 0)
2039
- printf '{"ts":"%s","event":"artifact_write","file":"%s","path":"%s","bytes":%s}\\n' \\
2040
- "$(date -u +%FT%TZ)" "$BASENAME" "$FILE_PATH" "$BYTES" >> ${quote(manifestFile)}
2041
- fi
2042
-
2043
- printf '{"ts":"%s","event":"artifact_write","file":"%s","path":"%s","bytes":0}\\n' \\
2044
- "$(date -u +%FT%TZ)" "$BASENAME" "$FILE_PATH" >> ${quote(eventsFile)}
2045
- exit 0
2046
- `;
2047
- }
2048
- function buildPostArtifactScript(manifestFile, expected) {
2049
- return `#!/bin/bash
2050
- set -euo pipefail
2051
- INPUT=$(cat)
2052
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
2053
- [ -z "$FILE_PATH" ] && exit 0
2054
-
2055
- EXPECTED=${quote(expected)}
2056
- BASENAME=$(basename "$FILE_PATH")
2057
-
2058
- if echo "$EXPECTED" | tr ',' '\\n' | grep -qx "$BASENAME"; then
2059
- BYTES=$(wc -c < "$FILE_PATH" 2>/dev/null || echo 0)
2060
- printf '{"ts":"%s","event":"write","file":"%s","path":"%s","bytes":%s}\\n' \\
2061
- "$(date -u +%FT%TZ)" "$BASENAME" "$FILE_PATH" "$BYTES" >> ${quote(manifestFile)}
2062
- fi
2063
- exit 0
2064
- `;
2065
- }
2066
- function buildExitPlanModeScript(eventsFile) {
2067
- return `#!/bin/bash
2068
- set -euo pipefail
2069
- INPUT=$(cat)
2070
-
2071
- printf '{"ts":"%s","event":"exit_plan_mode"}\\n' "$(date -u +%FT%TZ)" >> ${quote(eventsFile)}
2072
-
2073
- echo '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}'
2074
- exit 0
2075
- `;
2076
- }
2077
- function buildPermissionScript(eventsFile) {
2078
- return `#!/bin/bash
2079
- set -euo pipefail
2080
- INPUT=$(cat)
2081
- TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
2082
- printf '{"ts":"%s","event":"permission_request","tool":"%s"}\\n' \\
2083
- "$(date -u +%FT%TZ)" "$TOOL" >> ${quote(eventsFile)}
2084
- echo '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}'
2085
- exit 0
2086
- `;
2087
- }
2088
- function buildProtectFilesScript(eventsFile, phaseName, planDir) {
2089
- return `#!/bin/bash
2090
- set -euo pipefail
2091
- INPUT=$(cat)
2092
- TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
2093
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
2094
- [ -z "$FILE_PATH" ] && exit 0
2095
-
2096
- BASENAME=$(basename "$FILE_PATH")
2097
- PHASE=${quote(phaseName ?? "")}
2098
-
2099
- blocked_reason() {
2100
- printf '{"ts":"%s","event":"protect_blocked","tool":"%s","file":"%s"}\\n' \\
2101
- "$(date -u +%FT%TZ)" "$TOOL" "$BASENAME" >> ${quote(eventsFile)}
2102
- echo "$1" >&2
2103
- exit 2
2104
- }
2105
-
2106
- case "$BASENAME" in
2107
- .env|.env.*)
2108
- blocked_reason "\u7981\u6B62\u4FEE\u6539\u73AF\u5883\u914D\u7F6E\u6587\u4EF6 $BASENAME\uFF0C\u8BF7\u901A\u8FC7 .env.example \u6216\u6587\u6863\u8BF4\u660E\u914D\u7F6E\u53D8\u66F4\u3002"
2109
- ;;
2110
- package-lock.json|pnpm-lock.yaml)
2111
- blocked_reason "\u7981\u6B62\u76F4\u63A5\u4FEE\u6539\u9501\u6587\u4EF6 $BASENAME\uFF0C\u8BF7\u901A\u8FC7 npm install / pnpm install \u66F4\u65B0\u4F9D\u8D56\u3002"
2112
- ;;
2113
- esac
2114
-
2115
- if [ "$PHASE" = "build" ] || [ "$PHASE" = "verify" ]; then
2116
- case "$BASENAME" in
2117
- 01-plan.md)
2118
- blocked_reason "\u5728 $PHASE \u9636\u6BB5\u7981\u6B62\u4FEE\u6539\u89C4\u5212\u6587\u6863 01-plan.md\uFF0C\u8BE5\u6587\u4EF6\u5728 plan \u9636\u6BB5\u5DF2\u786E\u5B9A\u3002"
2119
- ;;
2120
- esac
2121
- fi
2122
-
2123
- exit 0
2124
- `;
2125
- }
2126
- function buildStopScript(eventsFile, planDir, phaseExpected) {
2127
- return `#!/bin/bash
2128
- set -euo pipefail
2129
- INPUT=$(cat)
2130
- STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
2131
-
2132
- PLAN_DIR=${quote(planDir)}
2133
- MIN_BYTES=50
2134
- PHASE_EXPECTED=${quote(phaseExpected)}
2135
-
2136
- MISSING=""
2137
- READY=""
2138
- for f in $(echo "$PHASE_EXPECTED" | tr ',' ' '); do
2139
- [ -z "$f" ] && continue
2140
- FPATH="$PLAN_DIR/$f"
2141
- if [ -f "$FPATH" ] && [ "$(wc -c < "$FPATH")" -ge "$MIN_BYTES" ]; then
2142
- BYTES=$(wc -c < "$FPATH")
2143
- READY="$READY $f(\${BYTES} bytes)"
2144
- else
2145
- MISSING="$MISSING $f"
2146
- fi
2147
- done
2148
-
2149
- MISSING=$(echo "$MISSING" | xargs)
2150
- READY=$(echo "$READY" | xargs)
2151
-
2152
- if [ -n "$MISSING" ] && [ "$STOP_ACTIVE" != "true" ]; then
2153
- printf '{"ts":"%s","event":"stop","blocked":true,"missing":"%s"}\\n' \\
2154
- "$(date -u +%FT%TZ)" "$MISSING" >> ${quote(eventsFile)}
2155
-
2156
- REASON="\u4EA7\u7269\u672A\u5C31\u7EEA: $MISSING\u3002\u8BF7\u5199\u5165 $PLAN_DIR/ \u4E0B\u7684\u5BF9\u5E94\u6587\u4EF6\u3002\u5DF2\u5C31\u7EEA: \${READY:-\u65E0}"
2157
-
2158
- printf '{"decision":"block","reason":"%s"}' "$REASON"
2159
- exit 0
2160
- fi
2161
-
2162
- printf '{"ts":"%s","event":"stop","blocked":false,"missing":"%s"}\\n' \\
2163
- "$(date -u +%FT%TZ)" "\${MISSING:-none}" >> ${quote(eventsFile)}
2164
- exit 0
2165
- `;
2166
- }
2167
- function quote(s) {
2168
- return `"${s.replace(/"/g, '\\"')}"`;
2169
- }
2170
- function readJsonl(filePath) {
2171
- if (!fs3.existsSync(filePath)) return [];
2172
- try {
2173
- const content = fs3.readFileSync(filePath, "utf-8").trim();
2174
- if (!content) return [];
2175
- return content.split("\n").reduce((acc, line) => {
2176
- const trimmed = line.trim();
2177
- if (!trimmed) return acc;
2178
- try {
2179
- acc.push(JSON.parse(trimmed));
2180
- } catch {
2181
- logger7.debug("Skipping malformed JSONL line", { line: trimmed });
2182
- }
2183
- return acc;
2184
- }, []);
2185
- } catch {
2186
- return [];
2187
- }
2188
- }
2189
-
2190
- // src/hooks/HookEventWatcher.ts
2191
- import fs4 from "fs";
2192
- var logger8 = logger.child("HookEventWatcher");
2193
-
2194
1749
  // src/phases/BasePhase.ts
2195
1750
  var BasePhase = class _BasePhase {
2196
1751
  static MIN_ARTIFACT_BYTES = 50;
@@ -2335,7 +1890,7 @@ ${t("basePhase.rulesSection", { rules })}`;
2335
1890
  const resultFiles = this.getResultFiles(ctx);
2336
1891
  const snapshotFilenames = resultFiles.map((f) => f.filename);
2337
1892
  const artifactCheck = snapshotFilenames.length > 0 ? () => snapshotFilenames.every((fn) => this.plan.isArtifactReady(fn, _BasePhase.MIN_ARTIFACT_BYTES)) : void 0;
2338
- const artifactPaths = snapshotFilenames.length > 0 ? snapshotFilenames.map((fn) => path5.join(this.plan.planDir, fn)) : void 0;
1893
+ const artifactPaths = snapshotFilenames.length > 0 ? snapshotFilenames.map((fn) => path4.join(this.plan.planDir, fn)) : void 0;
2339
1894
  let capturedSessionId;
2340
1895
  const result = await this.aiRunner.run({
2341
1896
  prompt,
@@ -2432,14 +1987,14 @@ ${t("basePhase.rulesSection", { rules })}`;
2432
1987
  const context = `${ctx.demand.title} ${ctx.demand.description} ${ctx.demand.supplement ? JSON.stringify(ctx.demand.supplement) : ""}`;
2433
1988
  if (ctx.workspace && ctx.workspace.repos.length > 1) {
2434
1989
  for (const repo of ctx.workspace.repos) {
2435
- const rulesDir = path5.join(repo.gitRootDir, ".cursor", "rules");
1990
+ const rulesDir = path4.join(repo.gitRootDir, ".cursor", "rules");
2436
1991
  try {
2437
1992
  await resolver.loadRules(rulesDir);
2438
1993
  } catch {
2439
1994
  }
2440
1995
  }
2441
1996
  } else {
2442
- const rulesDir = path5.join(this.plan.baseDir, ".cursor", "rules");
1997
+ const rulesDir = path4.join(this.plan.baseDir, ".cursor", "rules");
2443
1998
  await resolver.loadRules(rulesDir);
2444
1999
  }
2445
2000
  const matched = resolver.matchRules(context);
@@ -2713,20 +2268,20 @@ var BuildPhase = class extends BasePhase {
2713
2268
  };
2714
2269
 
2715
2270
  // src/release/ReleaseDetectCache.ts
2716
- import fs5 from "fs";
2717
- import path6 from "path";
2271
+ import fs3 from "fs";
2272
+ import path5 from "path";
2718
2273
  import { createHash } from "crypto";
2719
- var logger9 = logger.child("ReleaseDetectCache");
2274
+ var logger7 = logger.child("ReleaseDetectCache");
2720
2275
  function hashProjectPath(projectPath) {
2721
2276
  return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
2722
2277
  }
2723
2278
  var ReleaseDetectCache = class {
2724
2279
  cacheDir;
2725
2280
  constructor(dataDir) {
2726
- this.cacheDir = path6.join(dataDir, "release-detect");
2281
+ this.cacheDir = path5.join(dataDir, "release-detect");
2727
2282
  }
2728
2283
  filePath(projectPath) {
2729
- return path6.join(this.cacheDir, `${hashProjectPath(projectPath)}.json`);
2284
+ return path5.join(this.cacheDir, `${hashProjectPath(projectPath)}.json`);
2730
2285
  }
2731
2286
  /**
2732
2287
  * 读取缓存。返回 null 如果不存在、已过期或校验失败。
@@ -2734,21 +2289,21 @@ var ReleaseDetectCache = class {
2734
2289
  get(projectPath, ttlMs) {
2735
2290
  const fp = this.filePath(projectPath);
2736
2291
  try {
2737
- if (!fs5.existsSync(fp)) return null;
2738
- const raw = fs5.readFileSync(fp, "utf-8");
2292
+ if (!fs3.existsSync(fp)) return null;
2293
+ const raw = fs3.readFileSync(fp, "utf-8");
2739
2294
  const data = JSON.parse(raw);
2740
2295
  if (data.projectPath !== projectPath) {
2741
- logger9.warn("Cache projectPath mismatch, ignoring", { expected: projectPath, got: data.projectPath });
2296
+ logger7.warn("Cache projectPath mismatch, ignoring", { expected: projectPath, got: data.projectPath });
2742
2297
  return null;
2743
2298
  }
2744
2299
  const age = Date.now() - new Date(data.detectedAt).getTime();
2745
2300
  if (age > ttlMs) {
2746
- logger9.debug("Cache expired", { projectPath, ageMs: age, ttlMs });
2301
+ logger7.debug("Cache expired", { projectPath, ageMs: age, ttlMs });
2747
2302
  return null;
2748
2303
  }
2749
2304
  return data;
2750
2305
  } catch (err) {
2751
- logger9.warn("Failed to read release detect cache", { path: fp, error: err.message });
2306
+ logger7.warn("Failed to read release detect cache", { path: fp, error: err.message });
2752
2307
  return null;
2753
2308
  }
2754
2309
  }
@@ -2758,13 +2313,13 @@ var ReleaseDetectCache = class {
2758
2313
  set(result) {
2759
2314
  const fp = this.filePath(result.projectPath);
2760
2315
  try {
2761
- if (!fs5.existsSync(this.cacheDir)) {
2762
- fs5.mkdirSync(this.cacheDir, { recursive: true });
2316
+ if (!fs3.existsSync(this.cacheDir)) {
2317
+ fs3.mkdirSync(this.cacheDir, { recursive: true });
2763
2318
  }
2764
- fs5.writeFileSync(fp, JSON.stringify(result, null, 2), "utf-8");
2765
- logger9.debug("Release detect cache written", { projectPath: result.projectPath, path: fp });
2319
+ fs3.writeFileSync(fp, JSON.stringify(result, null, 2), "utf-8");
2320
+ logger7.debug("Release detect cache written", { projectPath: result.projectPath, path: fp });
2766
2321
  } catch (err) {
2767
- logger9.warn("Failed to write release detect cache", { path: fp, error: err.message });
2322
+ logger7.warn("Failed to write release detect cache", { path: fp, error: err.message });
2768
2323
  }
2769
2324
  }
2770
2325
  /**
@@ -2773,14 +2328,14 @@ var ReleaseDetectCache = class {
2773
2328
  invalidate(projectPath) {
2774
2329
  const fp = this.filePath(projectPath);
2775
2330
  try {
2776
- if (fs5.existsSync(fp)) {
2777
- fs5.unlinkSync(fp);
2778
- logger9.info("Release detect cache invalidated", { projectPath });
2331
+ if (fs3.existsSync(fp)) {
2332
+ fs3.unlinkSync(fp);
2333
+ logger7.info("Release detect cache invalidated", { projectPath });
2779
2334
  return true;
2780
2335
  }
2781
2336
  return false;
2782
2337
  } catch (err) {
2783
- logger9.warn("Failed to invalidate release detect cache", { path: fp, error: err.message });
2338
+ logger7.warn("Failed to invalidate release detect cache", { path: fp, error: err.message });
2784
2339
  return false;
2785
2340
  }
2786
2341
  }
@@ -3231,9 +2786,9 @@ function createLifecycleManager(def) {
3231
2786
 
3232
2787
  // src/workspace/WorkspaceConfig.ts
3233
2788
  import { z } from "zod";
3234
- import fs6 from "fs";
3235
- import path7 from "path";
3236
- var logger10 = logger.child("WorkspaceConfig");
2789
+ import fs4 from "fs";
2790
+ import path6 from "path";
2791
+ var logger8 = logger.child("WorkspaceConfig");
3237
2792
  var repoConfigSchema = z.object({
3238
2793
  name: z.string().min(1, "Repo name is required"),
3239
2794
  projectPath: z.string().min(1, "Gongfeng project path is required"),
@@ -3249,29 +2804,29 @@ var workspaceConfigSchema = z.object({
3249
2804
  });
3250
2805
  function loadWorkspaceConfig(configPath) {
3251
2806
  if (!configPath) return null;
3252
- if (!fs6.existsSync(configPath)) {
3253
- logger10.warn("Workspace config file not found, falling back to single-repo mode", {
2807
+ if (!fs4.existsSync(configPath)) {
2808
+ logger8.warn("Workspace config file not found, falling back to single-repo mode", {
3254
2809
  path: configPath
3255
2810
  });
3256
2811
  return null;
3257
2812
  }
3258
2813
  try {
3259
- const raw = fs6.readFileSync(configPath, "utf-8");
2814
+ const raw = fs4.readFileSync(configPath, "utf-8");
3260
2815
  const json = JSON.parse(raw);
3261
2816
  const result = workspaceConfigSchema.safeParse(json);
3262
2817
  if (!result.success) {
3263
2818
  const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
3264
- logger10.error(`Workspace config validation failed:
2819
+ logger8.error(`Workspace config validation failed:
3265
2820
  ${issues}`);
3266
2821
  return null;
3267
2822
  }
3268
- logger10.info("Workspace config loaded", {
2823
+ logger8.info("Workspace config loaded", {
3269
2824
  primary: result.data.primary.name,
3270
2825
  associates: result.data.associates.map((a) => a.name)
3271
2826
  });
3272
2827
  return result.data;
3273
2828
  } catch (err) {
3274
- logger10.error("Failed to parse workspace config", {
2829
+ logger8.error("Failed to parse workspace config", {
3275
2830
  path: configPath,
3276
2831
  error: err.message
3277
2832
  });
@@ -3297,14 +2852,14 @@ function isMultiRepo(ws) {
3297
2852
  }
3298
2853
  function persistWorkspaceConfig(ws, filePath) {
3299
2854
  try {
3300
- const dir = path7.dirname(filePath);
3301
- if (!fs6.existsSync(dir)) {
3302
- fs6.mkdirSync(dir, { recursive: true });
2855
+ const dir = path6.dirname(filePath);
2856
+ if (!fs4.existsSync(dir)) {
2857
+ fs4.mkdirSync(dir, { recursive: true });
3303
2858
  }
3304
- fs6.writeFileSync(filePath, JSON.stringify(ws, null, 2) + "\n", "utf-8");
3305
- logger10.info("Workspace config auto-generated from .env", { path: filePath });
2859
+ fs4.writeFileSync(filePath, JSON.stringify(ws, null, 2) + "\n", "utf-8");
2860
+ logger8.info("Workspace config auto-generated from .env", { path: filePath });
3306
2861
  } catch (err) {
3307
- logger10.warn("Failed to persist workspace config", {
2862
+ logger8.warn("Failed to persist workspace config", {
3308
2863
  path: filePath,
3309
2864
  error: err.message
3310
2865
  });
@@ -3312,12 +2867,12 @@ function persistWorkspaceConfig(ws, filePath) {
3312
2867
  }
3313
2868
 
3314
2869
  // src/workspace/WorkspaceManager.ts
3315
- import path8 from "path";
3316
- import fs7 from "fs/promises";
2870
+ import path7 from "path";
2871
+ import fs5 from "fs/promises";
3317
2872
  import { execFile } from "child_process";
3318
2873
  import { promisify } from "util";
3319
2874
  var execFileAsync = promisify(execFile);
3320
- var logger11 = logger.child("WorkspaceManager");
2875
+ var logger9 = logger.child("WorkspaceManager");
3321
2876
  var WorkspaceManager = class {
3322
2877
  wsConfig;
3323
2878
  worktreeBaseDir;
@@ -3346,7 +2901,7 @@ var WorkspaceManager = class {
3346
2901
  */
3347
2902
  async prepareWorkspace(issueIid, branchName, globalBaseBranch, globalBranchPrefix) {
3348
2903
  const wsRoot = this.getWorkspaceRoot(issueIid);
3349
- await fs7.mkdir(wsRoot, { recursive: true });
2904
+ await fs5.mkdir(wsRoot, { recursive: true });
3350
2905
  const primaryCtx = await this.preparePrimaryRepo(
3351
2906
  issueIid,
3352
2907
  branchName,
@@ -3365,7 +2920,7 @@ var WorkspaceManager = class {
3365
2920
  );
3366
2921
  associateCtxs.push(ctx);
3367
2922
  }
3368
- logger11.info("Workspace prepared", {
2923
+ logger9.info("Workspace prepared", {
3369
2924
  issueIid,
3370
2925
  wsRoot,
3371
2926
  repos: [primaryCtx.name, ...associateCtxs.map((a) => a.name)]
@@ -3390,7 +2945,7 @@ var WorkspaceManager = class {
3390
2945
  await git.commit(message);
3391
2946
  await git.push(wsCtx.branchName);
3392
2947
  committed.push(repo.name);
3393
- logger11.info("Committed and pushed changes", {
2948
+ logger9.info("Committed and pushed changes", {
3394
2949
  repo: repo.name,
3395
2950
  branch: wsCtx.branchName
3396
2951
  });
@@ -3404,19 +2959,19 @@ var WorkspaceManager = class {
3404
2959
  async cleanupWorkspace(wsCtx) {
3405
2960
  try {
3406
2961
  await this.mainGit.worktreeRemove(wsCtx.primary.gitRootDir, true);
3407
- logger11.info("Primary worktree removed", { dir: wsCtx.primary.gitRootDir });
2962
+ logger9.info("Primary worktree removed", { dir: wsCtx.primary.gitRootDir });
3408
2963
  } catch (err) {
3409
- logger11.warn("Failed to remove primary worktree", {
2964
+ logger9.warn("Failed to remove primary worktree", {
3410
2965
  dir: wsCtx.primary.gitRootDir,
3411
2966
  error: err.message
3412
2967
  });
3413
2968
  }
3414
2969
  for (const assoc of wsCtx.associates) {
3415
2970
  try {
3416
- await fs7.rm(assoc.gitRootDir, { recursive: true, force: true });
3417
- logger11.info("Associate repo dir removed", { name: assoc.name, dir: assoc.gitRootDir });
2971
+ await fs5.rm(assoc.gitRootDir, { recursive: true, force: true });
2972
+ logger9.info("Associate repo dir removed", { name: assoc.name, dir: assoc.gitRootDir });
3418
2973
  } catch (err) {
3419
- logger11.warn("Failed to remove associate repo dir", {
2974
+ logger9.warn("Failed to remove associate repo dir", {
3420
2975
  name: assoc.name,
3421
2976
  dir: assoc.gitRootDir,
3422
2977
  error: err.message
@@ -3424,9 +2979,9 @@ var WorkspaceManager = class {
3424
2979
  }
3425
2980
  }
3426
2981
  try {
3427
- const entries = await fs7.readdir(wsCtx.workspaceRoot);
2982
+ const entries = await fs5.readdir(wsCtx.workspaceRoot);
3428
2983
  if (entries.length === 0) {
3429
- await fs7.rmdir(wsCtx.workspaceRoot);
2984
+ await fs5.rmdir(wsCtx.workspaceRoot);
3430
2985
  }
3431
2986
  } catch {
3432
2987
  }
@@ -3438,13 +2993,13 @@ var WorkspaceManager = class {
3438
2993
  const wsRoot = this.getWorkspaceRoot(issueIid);
3439
2994
  const primary = this.wsConfig.primary;
3440
2995
  const defaultPrefix = globalBranchPrefix ?? primary.branchPrefix ?? "feat/issue";
3441
- const primaryDir = path8.join(wsRoot, primary.name);
2996
+ const primaryDir = path7.join(wsRoot, primary.name);
3442
2997
  const repos = [{
3443
2998
  name: primary.name,
3444
2999
  projectPath: primary.projectPath,
3445
3000
  role: primary.role ?? "",
3446
3001
  gitRootDir: primaryDir,
3447
- workDir: path8.join(primaryDir, primary.projectSubDir ?? ""),
3002
+ workDir: path7.join(primaryDir, primary.projectSubDir ?? ""),
3448
3003
  baseBranch: primary.baseBranch ?? globalBaseBranch,
3449
3004
  branchPrefix: primary.branchPrefix ?? defaultPrefix,
3450
3005
  isPrimary: true
@@ -3454,8 +3009,8 @@ var WorkspaceManager = class {
3454
3009
  name: assoc.name,
3455
3010
  projectPath: assoc.projectPath,
3456
3011
  role: assoc.role ?? "",
3457
- gitRootDir: path8.join(wsRoot, assoc.name),
3458
- workDir: path8.join(wsRoot, assoc.name, assoc.projectSubDir ?? ""),
3012
+ gitRootDir: path7.join(wsRoot, assoc.name),
3013
+ workDir: path7.join(wsRoot, assoc.name, assoc.projectSubDir ?? ""),
3459
3014
  baseBranch: assoc.baseBranch ?? globalBaseBranch,
3460
3015
  branchPrefix: assoc.branchPrefix ?? defaultPrefix,
3461
3016
  isPrimary: false
@@ -3464,12 +3019,12 @@ var WorkspaceManager = class {
3464
3019
  return repos;
3465
3020
  }
3466
3021
  getWorkspaceRoot(issueIid) {
3467
- return path8.join(this.worktreeBaseDir, `issue-${issueIid}`);
3022
+ return path7.join(this.worktreeBaseDir, `issue-${issueIid}`);
3468
3023
  }
3469
3024
  // ── Internal helpers ──
3470
3025
  async preparePrimaryRepo(issueIid, branchName, wsRoot, globalBaseBranch) {
3471
3026
  const primary = this.wsConfig.primary;
3472
- const repoDir = path8.join(wsRoot, primary.name);
3027
+ const repoDir = path7.join(wsRoot, primary.name);
3473
3028
  const baseBranch = primary.baseBranch ?? globalBaseBranch;
3474
3029
  await this.ensurePrimaryWorktree(repoDir, branchName, baseBranch);
3475
3030
  return {
@@ -3477,18 +3032,18 @@ var WorkspaceManager = class {
3477
3032
  projectPath: primary.projectPath,
3478
3033
  role: primary.role ?? "",
3479
3034
  gitRootDir: repoDir,
3480
- workDir: path8.join(repoDir, primary.projectSubDir ?? ""),
3035
+ workDir: path7.join(repoDir, primary.projectSubDir ?? ""),
3481
3036
  baseBranch,
3482
3037
  branchPrefix: primary.branchPrefix ?? "feat/issue",
3483
3038
  isPrimary: true
3484
3039
  };
3485
3040
  }
3486
3041
  async ensurePrimaryWorktree(repoDir, branchName, baseBranch) {
3487
- const wsRoot = path8.dirname(repoDir);
3042
+ const wsRoot = path7.dirname(repoDir);
3488
3043
  if (wsRoot !== repoDir) {
3489
3044
  try {
3490
- await fs7.access(path8.join(wsRoot, ".git"));
3491
- logger11.info("Migrating legacy worktree to primary subdir", { from: wsRoot, to: repoDir });
3045
+ await fs5.access(path7.join(wsRoot, ".git"));
3046
+ logger9.info("Migrating legacy worktree to primary subdir", { from: wsRoot, to: repoDir });
3492
3047
  await this.mainGit.worktreeRemove(wsRoot, true);
3493
3048
  await this.mainGit.worktreePrune();
3494
3049
  await this.cleanStaleDir(wsRoot);
@@ -3498,11 +3053,11 @@ var WorkspaceManager = class {
3498
3053
  const worktrees = await this.mainGit.worktreeList();
3499
3054
  if (worktrees.includes(repoDir)) {
3500
3055
  try {
3501
- await fs7.access(path8.join(repoDir, ".git"));
3502
- logger11.info("Reusing existing primary worktree", { dir: repoDir });
3056
+ await fs5.access(path7.join(repoDir, ".git"));
3057
+ logger9.info("Reusing existing primary worktree", { dir: repoDir });
3503
3058
  return;
3504
3059
  } catch {
3505
- logger11.warn("Primary worktree registered but .git missing, recreating", { dir: repoDir });
3060
+ logger9.warn("Primary worktree registered but .git missing, recreating", { dir: repoDir });
3506
3061
  await this.mainGit.worktreeRemove(repoDir, true);
3507
3062
  await this.mainGit.worktreePrune();
3508
3063
  }
@@ -3521,19 +3076,19 @@ var WorkspaceManager = class {
3521
3076
  await this.mainGit.worktreeAdd(repoDir, branchName, `origin/${baseBranch}`);
3522
3077
  }
3523
3078
  async prepareAssociateRepo(assoc, _issueIid, branchName, wsRoot, globalBaseBranch, globalBranchPrefix) {
3524
- const repoDir = path8.join(wsRoot, assoc.name);
3079
+ const repoDir = path7.join(wsRoot, assoc.name);
3525
3080
  const baseBranch = assoc.baseBranch ?? globalBaseBranch;
3526
3081
  const cloneUrl = `${this.gongfengApiUrl}/${assoc.projectPath}.git`;
3527
- const gitDirExists = await this.dirExists(path8.join(repoDir, ".git"));
3082
+ const gitDirExists = await this.dirExists(path7.join(repoDir, ".git"));
3528
3083
  if (!gitDirExists) {
3529
3084
  await this.cleanStaleDir(repoDir);
3530
- logger11.info("Cloning associate repo", { name: assoc.name, url: cloneUrl });
3085
+ logger9.info("Cloning associate repo", { name: assoc.name, url: cloneUrl });
3531
3086
  await execFileAsync("git", ["clone", "--depth", "50", cloneUrl, repoDir], {
3532
3087
  timeout: 3e5,
3533
3088
  maxBuffer: 10 * 1024 * 1024
3534
3089
  });
3535
3090
  } else {
3536
- logger11.info("Reusing existing associate clone", { name: assoc.name, dir: repoDir });
3091
+ logger9.info("Reusing existing associate clone", { name: assoc.name, dir: repoDir });
3537
3092
  }
3538
3093
  const assocGit = new GitOperations(repoDir);
3539
3094
  await assocGit.fetch();
@@ -3556,7 +3111,7 @@ var WorkspaceManager = class {
3556
3111
  projectPath: assoc.projectPath,
3557
3112
  role: assoc.role ?? "",
3558
3113
  gitRootDir: repoDir,
3559
- workDir: path8.join(repoDir, assoc.projectSubDir ?? ""),
3114
+ workDir: path7.join(repoDir, assoc.projectSubDir ?? ""),
3560
3115
  baseBranch,
3561
3116
  branchPrefix: assoc.branchPrefix ?? globalBranchPrefix,
3562
3117
  isPrimary: false
@@ -3564,13 +3119,13 @@ var WorkspaceManager = class {
3564
3119
  }
3565
3120
  async cleanStaleDir(dir) {
3566
3121
  if (await this.dirExists(dir)) {
3567
- logger11.warn("Removing stale directory", { dir });
3568
- await fs7.rm(dir, { recursive: true, force: true });
3122
+ logger9.warn("Removing stale directory", { dir });
3123
+ await fs5.rm(dir, { recursive: true, force: true });
3569
3124
  }
3570
3125
  }
3571
3126
  async dirExists(dir) {
3572
3127
  try {
3573
- await fs7.access(dir);
3128
+ await fs5.access(dir);
3574
3129
  return true;
3575
3130
  } catch {
3576
3131
  return false;
@@ -3579,8 +3134,8 @@ var WorkspaceManager = class {
3579
3134
  };
3580
3135
 
3581
3136
  // src/orchestrator/PipelineOrchestrator.ts
3582
- import path12 from "path";
3583
- import fs11 from "fs/promises";
3137
+ import path11 from "path";
3138
+ import fs9 from "fs/promises";
3584
3139
  import fsSync from "fs";
3585
3140
  import { execFile as execFile2 } from "child_process";
3586
3141
  import { promisify as promisify2 } from "util";
@@ -3613,8 +3168,8 @@ function mapSupplement(s) {
3613
3168
  }
3614
3169
 
3615
3170
  // src/utils/MergeRequestHelper.ts
3616
- import fs8 from "fs";
3617
- import path9 from "path";
3171
+ import fs6 from "fs";
3172
+ import path8 from "path";
3618
3173
  var TAPD_PATTERNS = [
3619
3174
  /--story=(\d+)/i,
3620
3175
  /--bug=(\d+)/i,
@@ -3658,9 +3213,9 @@ function generateMRDescription(options) {
3658
3213
  ];
3659
3214
  const planSections = [];
3660
3215
  for (const { filename, label } of summaryFiles) {
3661
- const filePath = path9.join(planDir, ".claude-plan", `issue-${issueIid}`, filename);
3662
- if (fs8.existsSync(filePath)) {
3663
- const content = fs8.readFileSync(filePath, "utf-8");
3216
+ const filePath = path8.join(planDir, ".claude-plan", `issue-${issueIid}`, filename);
3217
+ if (fs6.existsSync(filePath)) {
3218
+ const content = fs6.readFileSync(filePath, "utf-8");
3664
3219
  const summary = extractSummary(content);
3665
3220
  if (summary) {
3666
3221
  planSections.push(`### ${label}
@@ -3684,7 +3239,7 @@ function extractSummary(content, maxLines = 20) {
3684
3239
 
3685
3240
  // src/deploy/PortAllocator.ts
3686
3241
  import net from "net";
3687
- var logger12 = logger.child("PortAllocator");
3242
+ var logger10 = logger.child("PortAllocator");
3688
3243
  var DEFAULT_OPTIONS = {
3689
3244
  backendPortBase: 4e3,
3690
3245
  frontendPortBase: 9e3,
@@ -3709,7 +3264,7 @@ var PortAllocator = class {
3709
3264
  async allocate(issueIid) {
3710
3265
  const existing = this.allocated.get(issueIid);
3711
3266
  if (existing) {
3712
- logger12.info("Returning already allocated ports", { issueIid, ports: existing });
3267
+ logger10.info("Returning already allocated ports", { issueIid, ports: existing });
3713
3268
  return existing;
3714
3269
  }
3715
3270
  const usedBackend = new Set([...this.allocated.values()].map((p) => p.backendPort));
@@ -3727,10 +3282,10 @@ var PortAllocator = class {
3727
3282
  if (beOk && feOk) {
3728
3283
  const pair = { backendPort, frontendPort };
3729
3284
  this.allocated.set(issueIid, pair);
3730
- logger12.info("Ports allocated", { issueIid, ...pair });
3285
+ logger10.info("Ports allocated", { issueIid, ...pair });
3731
3286
  return pair;
3732
3287
  }
3733
- logger12.debug("Port pair unavailable, trying next", {
3288
+ logger10.debug("Port pair unavailable, trying next", {
3734
3289
  backendPort,
3735
3290
  frontendPort,
3736
3291
  beOk,
@@ -3745,7 +3300,7 @@ var PortAllocator = class {
3745
3300
  const pair = this.allocated.get(issueIid);
3746
3301
  if (pair) {
3747
3302
  this.allocated.delete(issueIid);
3748
- logger12.info("Ports released", { issueIid, ...pair });
3303
+ logger10.info("Ports released", { issueIid, ...pair });
3749
3304
  }
3750
3305
  }
3751
3306
  getPortsForIssue(issueIid) {
@@ -3756,15 +3311,15 @@ var PortAllocator = class {
3756
3311
  }
3757
3312
  restore(issueIid, ports) {
3758
3313
  this.allocated.set(issueIid, ports);
3759
- logger12.info("Ports restored from persistence", { issueIid, ...ports });
3314
+ logger10.info("Ports restored from persistence", { issueIid, ...ports });
3760
3315
  }
3761
3316
  };
3762
3317
 
3763
3318
  // src/deploy/DevServerManager.ts
3764
3319
  import { spawn } from "child_process";
3765
- import fs9 from "fs";
3766
- import path10 from "path";
3767
- var logger13 = logger.child("DevServerManager");
3320
+ import fs7 from "fs";
3321
+ import path9 from "path";
3322
+ var logger11 = logger.child("DevServerManager");
3768
3323
  var DEFAULT_OPTIONS2 = {};
3769
3324
  var DevServerManager = class {
3770
3325
  servers = /* @__PURE__ */ new Map();
@@ -3772,25 +3327,25 @@ var DevServerManager = class {
3772
3327
  logDir;
3773
3328
  constructor(options) {
3774
3329
  this.options = { ...DEFAULT_OPTIONS2, ...options };
3775
- this.logDir = path10.join(resolveDataDir(), "preview-logs");
3776
- if (!fs9.existsSync(this.logDir)) {
3777
- fs9.mkdirSync(this.logDir, { recursive: true });
3330
+ this.logDir = path9.join(resolveDataDir(), "preview-logs");
3331
+ if (!fs7.existsSync(this.logDir)) {
3332
+ fs7.mkdirSync(this.logDir, { recursive: true });
3778
3333
  }
3779
3334
  }
3780
3335
  getLogPath(issueIid, type) {
3781
- const filePath = path10.join(this.logDir, `${issueIid}-${type}.log`);
3782
- return fs9.existsSync(filePath) ? filePath : null;
3336
+ const filePath = path9.join(this.logDir, `${issueIid}-${type}.log`);
3337
+ return fs7.existsSync(filePath) ? filePath : null;
3783
3338
  }
3784
3339
  async startServers(wtCtx, ports) {
3785
3340
  if (this.servers.has(wtCtx.issueIid)) {
3786
- logger13.info("Servers already running for issue", { issueIid: wtCtx.issueIid });
3341
+ logger11.info("Servers already running for issue", { issueIid: wtCtx.issueIid });
3787
3342
  return;
3788
3343
  }
3789
- logger13.info("Starting dev servers", { issueIid: wtCtx.issueIid, ...ports });
3790
- const backendLogPath = path10.join(this.logDir, `${wtCtx.issueIid}-backend.log`);
3791
- const frontendLogPath = path10.join(this.logDir, `${wtCtx.issueIid}-frontend.log`);
3792
- const backendLog = fs9.createWriteStream(backendLogPath, { flags: "a" });
3793
- const frontendLog = fs9.createWriteStream(frontendLogPath, { flags: "a" });
3344
+ logger11.info("Starting dev servers", { issueIid: wtCtx.issueIid, ...ports });
3345
+ const backendLogPath = path9.join(this.logDir, `${wtCtx.issueIid}-backend.log`);
3346
+ const frontendLogPath = path9.join(this.logDir, `${wtCtx.issueIid}-frontend.log`);
3347
+ const backendLog = fs7.createWriteStream(backendLogPath, { flags: "a" });
3348
+ const frontendLog = fs7.createWriteStream(frontendLogPath, { flags: "a" });
3794
3349
  const tsLine = (stream, data) => `[${(/* @__PURE__ */ new Date()).toISOString()}] [${stream}] ${data.toString().trimEnd()}
3795
3350
  `;
3796
3351
  const backendEnv = {
@@ -3814,9 +3369,9 @@ var DevServerManager = class {
3814
3369
  backendLog.write(tsLine("stderr", data));
3815
3370
  });
3816
3371
  backend.on("exit", (code) => {
3817
- logger13.info("Backend process exited", { issueIid: wtCtx.issueIid, code });
3372
+ logger11.info("Backend process exited", { issueIid: wtCtx.issueIid, code });
3818
3373
  });
3819
- const frontendDir = path10.join(wtCtx.workDir, "frontend");
3374
+ const frontendDir = path9.join(wtCtx.workDir, "frontend");
3820
3375
  const frontendEnv = {
3821
3376
  ...process.env,
3822
3377
  BACKEND_PORT: String(ports.backendPort),
@@ -3838,7 +3393,7 @@ var DevServerManager = class {
3838
3393
  frontendLog.write(tsLine("stderr", data));
3839
3394
  });
3840
3395
  frontend.on("exit", (code) => {
3841
- logger13.info("Frontend process exited", { issueIid: wtCtx.issueIid, code });
3396
+ logger11.info("Frontend process exited", { issueIid: wtCtx.issueIid, code });
3842
3397
  });
3843
3398
  const serverSet = {
3844
3399
  backend,
@@ -3850,14 +3405,14 @@ var DevServerManager = class {
3850
3405
  frontendLog
3851
3406
  };
3852
3407
  this.servers.set(wtCtx.issueIid, serverSet);
3853
- logger13.info("Dev servers spawned, waiting for startup", { issueIid: wtCtx.issueIid, ...ports });
3408
+ logger11.info("Dev servers spawned, waiting for startup", { issueIid: wtCtx.issueIid, ...ports });
3854
3409
  await new Promise((r) => setTimeout(r, 1e4));
3855
- logger13.info("Dev servers startup grace period done", { issueIid: wtCtx.issueIid });
3410
+ logger11.info("Dev servers startup grace period done", { issueIid: wtCtx.issueIid });
3856
3411
  }
3857
3412
  stopServers(issueIid) {
3858
3413
  const set = this.servers.get(issueIid);
3859
3414
  if (!set) return;
3860
- logger13.info("Stopping dev servers", { issueIid, ports: set.ports });
3415
+ logger11.info("Stopping dev servers", { issueIid, ports: set.ports });
3861
3416
  killProcess(set.backend, `backend #${issueIid}`);
3862
3417
  killProcess(set.frontend, `frontend #${issueIid}`);
3863
3418
  set.backendLog.end();
@@ -3894,7 +3449,7 @@ function killProcess(proc, label) {
3894
3449
  }
3895
3450
  setTimeout(() => {
3896
3451
  if (!proc.killed && proc.exitCode === null) {
3897
- logger13.warn(`Force killing ${label}`);
3452
+ logger11.warn(`Force killing ${label}`);
3898
3453
  try {
3899
3454
  process.kill(-pid, "SIGKILL");
3900
3455
  } catch {
@@ -3903,7 +3458,7 @@ function killProcess(proc, label) {
3903
3458
  }
3904
3459
  }, 5e3);
3905
3460
  } catch (err) {
3906
- logger13.warn(`Failed to kill ${label}`, { error: err.message });
3461
+ logger11.warn(`Failed to kill ${label}`, { error: err.message });
3907
3462
  }
3908
3463
  }
3909
3464
 
@@ -3922,13 +3477,13 @@ function isE2eEnabledForIssue(issueIid, tracker, cfg) {
3922
3477
  }
3923
3478
 
3924
3479
  // src/e2e/ScreenshotCollector.ts
3925
- import fs10 from "fs";
3926
- import path11 from "path";
3927
- var logger14 = logger.child("ScreenshotCollector");
3480
+ import fs8 from "fs";
3481
+ import path10 from "path";
3482
+ var logger12 = logger.child("ScreenshotCollector");
3928
3483
  var MAX_SCREENSHOTS = 20;
3929
3484
  function walkDir(dir, files = []) {
3930
- for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
3931
- const full = path11.join(dir, entry.name);
3485
+ for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
3486
+ const full = path10.join(dir, entry.name);
3932
3487
  if (entry.isDirectory()) {
3933
3488
  walkDir(full, files);
3934
3489
  } else if (entry.isFile() && entry.name.endsWith(".png")) {
@@ -3938,34 +3493,34 @@ function walkDir(dir, files = []) {
3938
3493
  return files;
3939
3494
  }
3940
3495
  function collectScreenshots(workDir) {
3941
- const testResultsDir = path11.join(workDir, "frontend", "test-results");
3942
- if (!fs10.existsSync(testResultsDir)) {
3943
- logger14.debug("test-results directory not found", { dir: testResultsDir });
3496
+ const testResultsDir = path10.join(workDir, "frontend", "test-results");
3497
+ if (!fs8.existsSync(testResultsDir)) {
3498
+ logger12.debug("test-results directory not found", { dir: testResultsDir });
3944
3499
  return [];
3945
3500
  }
3946
3501
  const pngFiles = walkDir(testResultsDir);
3947
3502
  if (pngFiles.length === 0) {
3948
- logger14.debug("No screenshots found");
3503
+ logger12.debug("No screenshots found");
3949
3504
  return [];
3950
3505
  }
3951
3506
  const screenshots = pngFiles.map((filePath) => {
3952
- const relative = path11.relative(testResultsDir, filePath);
3953
- const testName = relative.split(path11.sep)[0] || path11.basename(filePath, ".png");
3507
+ const relative = path10.relative(testResultsDir, filePath);
3508
+ const testName = relative.split(path10.sep)[0] || path10.basename(filePath, ".png");
3954
3509
  return { filePath, testName };
3955
3510
  });
3956
3511
  if (screenshots.length > MAX_SCREENSHOTS) {
3957
- logger14.warn("Too many screenshots, truncating", {
3512
+ logger12.warn("Too many screenshots, truncating", {
3958
3513
  total: screenshots.length,
3959
3514
  max: MAX_SCREENSHOTS
3960
3515
  });
3961
3516
  return screenshots.slice(0, MAX_SCREENSHOTS);
3962
3517
  }
3963
- logger14.info("Screenshots collected", { count: screenshots.length });
3518
+ logger12.info("Screenshots collected", { count: screenshots.length });
3964
3519
  return screenshots;
3965
3520
  }
3966
3521
 
3967
3522
  // src/e2e/ScreenshotPublisher.ts
3968
- var logger15 = logger.child("ScreenshotPublisher");
3523
+ var logger13 = logger.child("ScreenshotPublisher");
3969
3524
  function buildComment(uploaded, truncated) {
3970
3525
  const lines = [t("screenshot.title"), ""];
3971
3526
  for (const item of uploaded) {
@@ -3984,12 +3539,12 @@ var ScreenshotPublisher = class {
3984
3539
  const { workDir, issueIid, issueId, mrIid } = options;
3985
3540
  const screenshots = collectScreenshots(workDir);
3986
3541
  if (screenshots.length === 0) {
3987
- logger15.info("No E2E screenshots to publish", { issueIid });
3542
+ logger13.info("No E2E screenshots to publish", { issueIid });
3988
3543
  return;
3989
3544
  }
3990
3545
  const uploaded = await this.uploadAll(screenshots);
3991
3546
  if (uploaded.length === 0) {
3992
- logger15.warn("All screenshot uploads failed", { issueIid });
3547
+ logger13.warn("All screenshot uploads failed", { issueIid });
3993
3548
  return;
3994
3549
  }
3995
3550
  const truncated = screenshots.length >= 20;
@@ -3998,7 +3553,7 @@ var ScreenshotPublisher = class {
3998
3553
  if (mrIid) {
3999
3554
  await this.postToMergeRequest(mrIid, comment);
4000
3555
  }
4001
- logger15.info("E2E screenshots published", {
3556
+ logger13.info("E2E screenshots published", {
4002
3557
  issueIid,
4003
3558
  mrIid,
4004
3559
  count: uploaded.length
@@ -4014,7 +3569,7 @@ var ScreenshotPublisher = class {
4014
3569
  markdown: result.markdown
4015
3570
  });
4016
3571
  } catch (err) {
4017
- logger15.warn("Failed to upload screenshot", {
3572
+ logger13.warn("Failed to upload screenshot", {
4018
3573
  filePath: screenshot.filePath,
4019
3574
  error: err.message
4020
3575
  });
@@ -4026,7 +3581,7 @@ var ScreenshotPublisher = class {
4026
3581
  try {
4027
3582
  await this.gongfeng.createIssueNote(issueId, comment);
4028
3583
  } catch (err) {
4029
- logger15.warn("Failed to post screenshots to issue", {
3584
+ logger13.warn("Failed to post screenshots to issue", {
4030
3585
  issueId,
4031
3586
  error: err.message
4032
3587
  });
@@ -4036,7 +3591,7 @@ var ScreenshotPublisher = class {
4036
3591
  try {
4037
3592
  await this.gongfeng.createMergeRequestNote(mrIid, comment);
4038
3593
  } catch (err) {
4039
- logger15.warn("Failed to post screenshots to merge request", {
3594
+ logger13.warn("Failed to post screenshots to merge request", {
4040
3595
  mrIid,
4041
3596
  error: err.message
4042
3597
  });
@@ -4219,7 +3774,7 @@ metrics.registerCounter("iaf_braindump_batches_total", "Total braindump batches"
4219
3774
  metrics.registerCounter("iaf_braindump_tasks_total", "Total braindump tasks");
4220
3775
 
4221
3776
  // src/orchestrator/steps/SetupStep.ts
4222
- var logger16 = logger.child("SetupStep");
3777
+ var logger14 = logger.child("SetupStep");
4223
3778
  async function executeSetup(ctx, deps) {
4224
3779
  const { issue, wtCtx, record, pipelineDef, branchName } = ctx;
4225
3780
  try {
@@ -4228,7 +3783,7 @@ async function executeSetup(ctx, deps) {
4228
3783
  "auto-finish:processing"
4229
3784
  ]);
4230
3785
  } catch (err) {
4231
- logger16.warn("Failed to update issue labels", { error: err.message });
3786
+ logger14.warn("Failed to update issue labels", { error: err.message });
4232
3787
  }
4233
3788
  await deps.mainGitMutex.runExclusive(async () => {
4234
3789
  deps.emitProgress(issue.iid, "fetch", t("orchestrator.fetchProgress"));
@@ -4287,7 +3842,7 @@ async function executeSetup(ctx, deps) {
4287
3842
  issueDescription: issue.description
4288
3843
  });
4289
3844
  } catch (err) {
4290
- logger16.warn("Failed to inject Claude Code hooks (non-blocking)", {
3845
+ logger14.warn("Failed to inject Claude Code hooks (non-blocking)", {
4291
3846
  error: err.message
4292
3847
  });
4293
3848
  }
@@ -4304,6 +3859,44 @@ async function executeSetup(ctx, deps) {
4304
3859
  return { wtGit, wtPlan, wtGitMap };
4305
3860
  }
4306
3861
 
3862
+ // src/lifecycle/FeedbackTypes.ts
3863
+ function inputRequestToFeedback(request) {
3864
+ switch (request.type) {
3865
+ case "interactive-dialog":
3866
+ return {
3867
+ kind: "interactive-dialog",
3868
+ question: request.content,
3869
+ options: request.options ?? []
3870
+ };
3871
+ case "plan-approval":
3872
+ return { kind: "approval-required", scope: "plan" };
3873
+ case "generic":
3874
+ return { kind: "generic", content: request.content };
3875
+ }
3876
+ }
3877
+ function feedbackResponseToString(response) {
3878
+ switch (response.action) {
3879
+ case "approve":
3880
+ return "allow";
3881
+ case "reject":
3882
+ return "reject";
3883
+ case "select":
3884
+ return response.value;
3885
+ case "dismiss":
3886
+ return "";
3887
+ case "pause":
3888
+ return "";
3889
+ }
3890
+ }
3891
+ function stringToFeedbackResponse(str, originalFeedback) {
3892
+ if (str === "allow") return { action: "approve" };
3893
+ if (str === "reject") return { action: "reject" };
3894
+ if (str === "") {
3895
+ return originalFeedback.kind === "interactive-dialog" ? { action: "dismiss" } : { action: "approve" };
3896
+ }
3897
+ return { action: "select", value: str };
3898
+ }
3899
+
4307
3900
  // src/notesync/NoteSyncSettings.ts
4308
3901
  var noteSyncOverride;
4309
3902
  function getNoteSyncEnabled(cfg) {
@@ -4360,8 +3953,14 @@ function clearPendingDialog(issueIid) {
4360
3953
  store.delete(issueIid);
4361
3954
  }
4362
3955
 
4363
- // src/orchestrator/steps/PhaseLoopStep.ts
4364
- var logger17 = logger.child("PhaseLoopStep");
3956
+ // src/orchestrator/steps/PhaseHelpers.ts
3957
+ var logger15 = logger.child("PhaseHelpers");
3958
+ async function safeComment(deps, issueId, message) {
3959
+ try {
3960
+ await deps.gongfeng.createIssueNote(issueId, message);
3961
+ } catch {
3962
+ }
3963
+ }
4365
3964
  function resolveVerifyRunner(deps) {
4366
3965
  return deps.aiRunner;
4367
3966
  }
@@ -4377,7 +3976,7 @@ async function commitPlanFiles(ctx, wtGit, wtGitMap, phaseName, displayId) {
4377
3976
  for (const repo of ctx.workspace.repos) {
4378
3977
  const repoGit = wtGitMap?.get(repo.name);
4379
3978
  if (!repoGit) {
4380
- logger17.warn("Missing GitOperations for repo, skipping commit", { repo: repo.name });
3979
+ logger15.warn("Missing GitOperations for repo, skipping commit", { repo: repo.name });
4381
3980
  continue;
4382
3981
  }
4383
3982
  const branch = repo.branchPrefix ? `${repo.branchPrefix}-${displayId}` : ctx.branchName;
@@ -4386,10 +3985,10 @@ async function commitPlanFiles(ctx, wtGit, wtGitMap, phaseName, displayId) {
4386
3985
  await repoGit.add(["."]);
4387
3986
  await repoGit.commit(commitMsg);
4388
3987
  await repoGit.push(branch);
4389
- logger17.info("Committed changes for repo", { repo: repo.name, branch });
3988
+ logger15.info("Committed changes for repo", { repo: repo.name, branch });
4390
3989
  }
4391
3990
  } catch (err) {
4392
- logger17.warn("Failed to commit/push for repo", {
3991
+ logger15.warn("Failed to commit/push for repo", {
4393
3992
  repo: repo.name,
4394
3993
  error: err.message
4395
3994
  });
@@ -4427,27 +4026,15 @@ async function syncResultToIssue(phase, ctx, displayId, phaseName, deps, issueId
4427
4026
  summary
4428
4027
  );
4429
4028
  await safeComment(deps, issueId, comment);
4430
- logger17.info("Result synced to issue", { issueIid: displayId, file: file.filename });
4029
+ logger15.info("Result synced to issue", { issueIid: displayId, file: file.filename });
4431
4030
  }
4432
4031
  } catch (err) {
4433
- logger17.warn("Failed to sync result to issue", { error: err.message });
4032
+ logger15.warn("Failed to sync result to issue", { error: err.message });
4434
4033
  await safeComment(deps, issueId, issueProgressComment(phaseName, "completed"));
4435
4034
  }
4436
4035
  }
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
4036
  function handlePlanApproval(displayId, phaseName, deps) {
4450
- logger17.info("ACP plan-approval requested, delegating to review gate", {
4037
+ logger15.info("ACP plan-approval requested, delegating to review gate", {
4451
4038
  issueIid: displayId,
4452
4039
  phase: phaseName
4453
4040
  });
@@ -4457,14 +4044,14 @@ function handlePlanApproval(displayId, phaseName, deps) {
4457
4044
  const data = payload.data;
4458
4045
  if (data.issueIid !== displayId) return;
4459
4046
  cleanup();
4460
- logger17.info("ACP plan-approval approved via review gate", { issueIid: displayId });
4047
+ logger15.info("ACP plan-approval approved via review gate", { issueIid: displayId });
4461
4048
  resolve("allow");
4462
4049
  };
4463
4050
  const onRejected = (payload) => {
4464
4051
  const data = payload.data;
4465
4052
  if (data.issueIid !== displayId) return;
4466
4053
  cleanup();
4467
- logger17.info("ACP plan-approval rejected via review gate", { issueIid: displayId });
4054
+ logger15.info("ACP plan-approval rejected via review gate", { issueIid: displayId });
4468
4055
  resolve("reject");
4469
4056
  };
4470
4057
  const cleanup = () => {
@@ -4476,7 +4063,7 @@ function handlePlanApproval(displayId, phaseName, deps) {
4476
4063
  });
4477
4064
  }
4478
4065
  function handleInteractiveDialog(displayId, phaseName, deps, request) {
4479
- logger17.info("Interactive dialog forwarded to frontend", {
4066
+ logger15.info("Interactive dialog forwarded to frontend", {
4480
4067
  issueIid: displayId,
4481
4068
  phase: phaseName,
4482
4069
  question: request.content.slice(0, 80),
@@ -4506,7 +4093,7 @@ function handleInteractiveDialog(displayId, phaseName, deps, request) {
4506
4093
  if (data.issueIid !== displayId) return;
4507
4094
  cleanup();
4508
4095
  clearPendingDialog(displayId);
4509
- logger17.info("Interactive dialog response received from frontend", {
4096
+ logger15.info("Interactive dialog response received from frontend", {
4510
4097
  issueIid: displayId,
4511
4098
  response: data.response
4512
4099
  });
@@ -4516,7 +4103,7 @@ function handleInteractiveDialog(displayId, phaseName, deps, request) {
4516
4103
  const data = payload.data;
4517
4104
  if (data.issueIid !== displayId) return;
4518
4105
  cleanup();
4519
- logger17.info("Interactive dialog dismissed by user (false positive)", { issueIid: displayId });
4106
+ logger15.info("Interactive dialog dismissed by user (false positive)", { issueIid: displayId });
4520
4107
  resolve("");
4521
4108
  };
4522
4109
  deps.eventBus.on("agent:inputResponse", onResponse);
@@ -4540,45 +4127,75 @@ function updateHooksForPhase(spec, pipelineDef, ctx, wtPlan) {
4540
4127
  issueDescription: ctx.issue.description
4541
4128
  });
4542
4129
  } catch (err) {
4543
- logger17.warn("Failed to update hooks for phase (non-blocking)", {
4130
+ logger15.warn("Failed to update hooks for phase (non-blocking)", {
4544
4131
  phase: spec.name,
4545
4132
  error: err.message
4546
4133
  });
4547
4134
  }
4548
4135
  }
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", {
4136
+
4137
+ // src/lifecycle/DefaultLifecycleHook.ts
4138
+ var logger16 = logger.child("DefaultLifecycleHook");
4139
+ var DefaultLifecycleHook = class {
4140
+ async beforePhase(ctx) {
4141
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4142
+ const { issue } = issueCtx;
4143
+ const displayId = issue.iid;
4144
+ deps.tracker.updateState(displayId, spec.startState, { currentPhase: spec.name });
4145
+ deps.tracker.updatePhaseProgress(displayId, spec.name, {
4146
+ status: "in_progress",
4147
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4148
+ });
4149
+ wtPlan.updatePhaseProgress(spec.name, "in_progress");
4150
+ await safeComment(deps, issue.id, issueProgressComment(spec.name, "in_progress"));
4151
+ const phaseLabel = t(`phase.${spec.name}`) || spec.name;
4152
+ deps.eventBus.emitTyped("agent:output", {
4573
4153
  issueIid: displayId,
4574
4154
  phase: spec.name,
4155
+ event: {
4156
+ type: "system",
4157
+ content: t("basePhase.aiStarting", { label: phaseLabel }),
4158
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4159
+ }
4160
+ });
4161
+ }
4162
+ onStream(ctx, event) {
4163
+ const { spec, issueCtx, deps } = ctx;
4164
+ deps.eventBus.emitTyped("agent:output", {
4165
+ issueIid: issueCtx.issue.iid,
4166
+ phase: spec.name,
4575
4167
  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") {
4168
+ });
4169
+ }
4170
+ beforeExecute;
4171
+ afterExecute;
4172
+ async onFeedback(ctx, feedback) {
4173
+ const { deps, displayId, spec } = ctx;
4174
+ if (feedback.kind === "interactive-dialog") {
4175
+ const result = await handleInteractiveDialog(displayId, spec.name, deps, {
4176
+ type: "interactive-dialog",
4177
+ content: feedback.question,
4178
+ options: feedback.options
4179
+ });
4180
+ return stringToFeedbackResponse(result, feedback);
4181
+ }
4182
+ if (feedback.kind === "approval-required" && feedback.scope === "plan") {
4183
+ const useAcpGate = deps.config.ai.codebuddyAcpAutoApprove === false;
4184
+ if (useAcpGate) {
4185
+ const result = await handlePlanApproval(displayId, spec.name, deps);
4186
+ return result === "allow" ? { action: "approve" } : { action: "reject" };
4187
+ }
4188
+ return { action: "approve" };
4189
+ }
4190
+ return { action: "approve" };
4191
+ }
4192
+ async afterPhase(ctx, outcome) {
4193
+ const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap, phase } = ctx;
4194
+ const { issue, phaseCtx } = issueCtx;
4195
+ const displayId = issue.iid;
4196
+ if (outcome.sessionId) {
4197
+ wtPlan.updatePhaseSessionId(spec.name, outcome.sessionId);
4198
+ }
4582
4199
  deps.tracker.updateState(displayId, spec.doneState, { currentPhase: spec.name });
4583
4200
  deps.tracker.updatePhaseProgress(displayId, spec.name, {
4584
4201
  status: "completed",
@@ -4586,28 +4203,400 @@ async function runPhaseWithLifecycle(phase, phaseCtx, spec, ctx, deps, wtGit, wt
4586
4203
  });
4587
4204
  wtPlan.updatePhaseProgress(spec.name, "completed");
4588
4205
  await commitPlanFiles(phaseCtx, wtGit, wtGitMap, spec.name, displayId);
4589
- await syncResultToIssue(phase, phaseCtx, displayId, spec.name, deps, issue.id, wtPlan);
4590
- return outcome;
4206
+ if (phase) {
4207
+ await syncResultToIssue(phase, phaseCtx, displayId, spec.name, deps, issue.id, wtPlan);
4208
+ }
4209
+ }
4210
+ async onError(ctx, error) {
4211
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4212
+ const { issue } = issueCtx;
4213
+ const displayId = issue.iid;
4214
+ wtPlan.updatePhaseProgress(spec.name, "failed", error.message);
4215
+ deps.tracker.updatePhaseProgress(displayId, spec.name, { status: "failed" });
4216
+ if (error.wasActiveAtTimeout) {
4217
+ deps.tracker.markFailedSoft(displayId, error.message, "phase_running" /* PhaseRunning */);
4218
+ } else {
4219
+ deps.tracker.markFailed(displayId, error.message, "phase_running" /* PhaseRunning */);
4220
+ }
4221
+ const shortErr = error.message.slice(0, 200);
4222
+ await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4223
+ return { action: "fail", softFail: error.wasActiveAtTimeout };
4591
4224
  }
4592
- if (outcome.status === "running") {
4593
- return outcome;
4225
+ };
4226
+ function createCallbacksFromHook(hook, ctx) {
4227
+ return {
4228
+ onStreamEvent: hook.onStream ? (event) => hook.onStream(ctx, event) : void 0,
4229
+ onInputRequired: hook.onFeedback ? async (request) => {
4230
+ const feedback = inputRequestToFeedback(request);
4231
+ const response = await hook.onFeedback(ctx, feedback);
4232
+ return feedbackResponseToString(response);
4233
+ } : void 0
4234
+ };
4235
+ }
4236
+
4237
+ // src/orchestrator/strategies/GateStrategy.ts
4238
+ var logger17 = logger.child("GateStrategy");
4239
+ var GateStrategy = class {
4240
+ name = "gate";
4241
+ shouldSkip() {
4242
+ return false;
4594
4243
  }
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 */);
4244
+ async execute(ctx, _hooks) {
4245
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4246
+ const { issue } = issueCtx;
4247
+ if (deps.shouldAutoApprove(issue.labels)) {
4248
+ logger17.info("Auto-approving review gate (matched autoApproveLabels)", {
4249
+ iid: issue.iid,
4250
+ labels: issue.labels,
4251
+ autoApproveLabels: deps.config.review.autoApproveLabels
4252
+ });
4253
+ if (spec.approvedState) {
4254
+ deps.tracker.updateState(issue.iid, spec.approvedState, { currentPhase: spec.name });
4255
+ }
4256
+ wtPlan.updatePhaseProgress(spec.name, "completed");
4257
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4258
+ status: "completed",
4259
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
4260
+ });
4261
+ try {
4262
+ await deps.gongfeng.createIssueNote(issue.id, t("orchestrator.autoApproveComment"));
4263
+ } catch {
4264
+ }
4265
+ return { paused: false };
4266
+ }
4267
+ deps.tracker.updateState(issue.iid, spec.startState, { currentPhase: spec.name });
4268
+ wtPlan.updatePhaseProgress(spec.name, "in_progress");
4269
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4270
+ status: "in_progress",
4271
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4272
+ });
4273
+ deps.eventBus.emitTyped("review:requested", { issueIid: issue.iid });
4274
+ logger17.info("Review gate reached, pausing", { iid: issue.iid });
4275
+ return { paused: true };
4604
4276
  }
4605
- await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4277
+ };
4278
+
4279
+ // src/orchestrator/strategies/AiPhaseStrategy.ts
4280
+ var logger18 = logger.child("AiPhaseStrategy");
4281
+ var AiPhaseStrategy = class {
4282
+ name = "ai";
4283
+ shouldSkip(ctx) {
4284
+ const { spec, issueCtx, deps } = ctx;
4285
+ if (spec.name === "uat" && !isE2eEnabledForIssue(issueCtx.issue.iid, deps.tracker, deps.config)) {
4286
+ logger18.info("UAT phase skipped (E2E not enabled for this issue)", { iid: issueCtx.issue.iid });
4287
+ return true;
4288
+ }
4289
+ return false;
4290
+ }
4291
+ async execute(ctx, hooks) {
4292
+ const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
4293
+ const { issue, phaseCtx } = issueCtx;
4294
+ if (this.shouldSkip(ctx)) {
4295
+ deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4296
+ wtPlan.updatePhaseProgress(spec.name, "completed");
4297
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4298
+ status: "completed",
4299
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
4300
+ });
4301
+ return { paused: false };
4302
+ }
4303
+ updateHooksForPhase(spec, issueCtx.pipelineDef, issueCtx, wtPlan);
4304
+ const runner = this.resolveRunner(ctx);
4305
+ if (spec.name === "uat") {
4306
+ const runnerName = runner === deps.e2eAiRunner ? "e2eAiRunner (CodeBuddy)" : "mainRunner";
4307
+ logger18.info("UAT phase starting", { iid: issue.iid, runner: runnerName });
4308
+ }
4309
+ const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4310
+ if (wtGitMap) phase.setWtGitMap(wtGitMap);
4311
+ ctx.phase = phase;
4312
+ const outcome = await runPhaseWithLifecycle(
4313
+ phase,
4314
+ phaseCtx,
4315
+ spec,
4316
+ issueCtx,
4317
+ deps,
4318
+ wtGit,
4319
+ wtPlan,
4320
+ wtGitMap,
4321
+ hooks
4322
+ );
4323
+ if (outcome.status === "running") {
4324
+ return this.handleAsyncOutcome(ctx, outcome);
4325
+ }
4326
+ if (spec.approvedState && outcome.data?.hasReleaseCapability) {
4327
+ return this.handleGateRequest(ctx);
4328
+ }
4329
+ return { paused: false };
4330
+ }
4331
+ resolveRunner(ctx) {
4332
+ const { spec, deps, displayId } = ctx;
4333
+ if (spec.name === "verify") return resolveVerifyRunner(deps);
4334
+ if (spec.name === "uat") return resolveUatRunner(deps, displayId);
4335
+ return deps.aiRunner;
4336
+ }
4337
+ async handleAsyncOutcome(ctx, outcome) {
4338
+ const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
4339
+ const { issue, phaseCtx } = issueCtx;
4340
+ if (outcome.awaitCompletion) {
4341
+ logger18.info("Async phase running, awaiting completion", { iid: issue.iid, phase: spec.name });
4342
+ const finalOutcome = await outcome.awaitCompletion;
4343
+ if (finalOutcome.sessionId) {
4344
+ wtPlan.updatePhaseSessionId(spec.name, finalOutcome.sessionId);
4345
+ }
4346
+ if (finalOutcome.status === "completed") {
4347
+ deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4348
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
4349
+ status: "completed",
4350
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
4351
+ });
4352
+ wtPlan.updatePhaseProgress(spec.name, "completed");
4353
+ await commitPlanFiles(phaseCtx, wtGit, wtGitMap, spec.name, issue.iid);
4354
+ const runner = this.resolveRunner(ctx);
4355
+ const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4356
+ await syncResultToIssue(phase, phaseCtx, issue.iid, spec.name, deps, issue.id, wtPlan);
4357
+ logger18.info("Async phase completed successfully", { iid: issue.iid, phase: spec.name });
4358
+ return { paused: false };
4359
+ }
4360
+ const errMsg = finalOutcome.error?.message ?? "Unknown error";
4361
+ const shortErr = errMsg.slice(0, 200);
4362
+ const wasActive = finalOutcome.error?.wasActiveAtTimeout ?? false;
4363
+ wtPlan.updatePhaseProgress(spec.name, "failed", errMsg);
4364
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "failed" });
4365
+ if (wasActive) {
4366
+ deps.tracker.markFailedSoft(issue.iid, errMsg, "phase_running" /* PhaseRunning */);
4367
+ } else {
4368
+ deps.tracker.markFailed(issue.iid, errMsg, "phase_running" /* PhaseRunning */);
4369
+ }
4370
+ await safeComment(deps, issue.id, issueProgressComment(spec.name, "failed", shortErr));
4371
+ throw new AIExecutionError(spec.name, `Phase ${spec.name} failed: ${shortErr}`, {
4372
+ output: finalOutcome.error?.rawOutput ?? finalOutcome.output,
4373
+ exitCode: finalOutcome.exitCode ?? 1,
4374
+ isRetryable: finalOutcome.error?.isRetryable,
4375
+ wasActiveAtTimeout: wasActive
4376
+ });
4377
+ }
4378
+ deps.tracker.updateState(issue.iid, "phase_waiting" /* PhaseWaiting */, { currentPhase: spec.name });
4379
+ wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4380
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4381
+ const gateEvent = spec.name === "uat" ? "uat:gateRequested" : "release:gateRequested";
4382
+ deps.eventBus.emitTyped(gateEvent, { issueIid: issue.iid });
4383
+ logger18.info("Async phase running (no awaitCompletion), pausing pipeline", {
4384
+ iid: issue.iid,
4385
+ phase: spec.name
4386
+ });
4387
+ return { paused: true };
4388
+ }
4389
+ handleGateRequest(ctx) {
4390
+ const { spec, issueCtx, deps, wtPlan } = ctx;
4391
+ const { issue } = issueCtx;
4392
+ deps.tracker.updateState(issue.iid, "phase_waiting" /* PhaseWaiting */, { currentPhase: spec.name });
4393
+ wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4394
+ deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4395
+ deps.eventBus.emitTyped("release:gateRequested", { issueIid: issue.iid });
4396
+ logger18.info("Phase requested gate, pausing", { iid: issue.iid, phase: spec.name });
4397
+ return { paused: true };
4398
+ }
4399
+ };
4400
+
4401
+ // src/orchestrator/strategies/VerifyFixStrategy.ts
4402
+ var logger19 = logger.child("VerifyFixStrategy");
4403
+ var VerifyFixStrategy = class {
4404
+ name = "verify-fix";
4405
+ shouldSkip() {
4406
+ return false;
4407
+ }
4408
+ async execute(ctx, hooks) {
4409
+ const { issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
4410
+ const { issue, phaseCtx, pipelineDef } = issueCtx;
4411
+ const maxIterations = deps.config.verifyFixLoop.maxIterations;
4412
+ const verifySpec = ctx.spec;
4413
+ const verifyPhaseIdx = pipelineDef.phases.findIndex((p) => p.name === verifySpec.name);
4414
+ const buildPhaseIdx = this.findPreviousAiPhaseIndex(pipelineDef.phases, verifyPhaseIdx);
4415
+ deps.eventBus.emitTyped("verify:loopStarted", {
4416
+ issueIid: issue.iid,
4417
+ maxIterations
4418
+ });
4419
+ logger19.info("Verify-fix loop started", {
4420
+ iid: issue.iid,
4421
+ maxIterations,
4422
+ buildPhaseIdx
4423
+ });
4424
+ for (let iteration = 1; iteration <= maxIterations; iteration++) {
4425
+ if (isShuttingDown()) throw new ServiceShutdownError();
4426
+ logger19.info("Verify-fix loop iteration", { iteration, maxIterations, iid: issue.iid });
4427
+ updateHooksForPhase(verifySpec, pipelineDef, issueCtx, wtPlan);
4428
+ const verifyRunner = resolveVerifyRunner(deps);
4429
+ const verifyPhase = createPhase("verify", verifyRunner, wtGit, wtPlan, deps.config);
4430
+ if (wtGitMap) verifyPhase.setWtGitMap(wtGitMap);
4431
+ let verifyOutcome;
4432
+ try {
4433
+ verifyOutcome = await runPhaseWithLifecycle(
4434
+ verifyPhase,
4435
+ phaseCtx,
4436
+ verifySpec,
4437
+ issueCtx,
4438
+ deps,
4439
+ wtGit,
4440
+ wtPlan,
4441
+ wtGitMap,
4442
+ hooks
4443
+ );
4444
+ } catch (err) {
4445
+ logger19.warn("Verify phase execution failed", {
4446
+ iteration,
4447
+ iid: issue.iid,
4448
+ error: err.message
4449
+ });
4450
+ deps.eventBus.emitTyped("verify:iterationComplete", {
4451
+ issueIid: issue.iid,
4452
+ iteration,
4453
+ passed: false,
4454
+ failures: ["AI runner execution failed"]
4455
+ });
4456
+ if (iteration === maxIterations) throw err;
4457
+ if (buildPhaseIdx >= 0) {
4458
+ await this.executeBuildFix(issueCtx, deps, wtGit, wtPlan, buildPhaseIdx, {
4459
+ iteration,
4460
+ verifyFailures: ["AI runner execution failed: " + err.message],
4461
+ rawReport: ""
4462
+ }, wtGitMap, hooks);
4463
+ }
4464
+ continue;
4465
+ }
4466
+ const report = verifyOutcome.data?.verifyReport;
4467
+ const passed = report ? report.passed : true;
4468
+ deps.eventBus.emitTyped("verify:iterationComplete", {
4469
+ issueIid: issue.iid,
4470
+ iteration,
4471
+ passed,
4472
+ failures: report?.failureReasons
4473
+ });
4474
+ if (passed) {
4475
+ logger19.info("Verify-fix loop passed", { iteration, iid: issue.iid });
4476
+ return { paused: false };
4477
+ }
4478
+ logger19.info("Verify failed, issues found", {
4479
+ iteration,
4480
+ iid: issue.iid,
4481
+ failures: report?.failureReasons,
4482
+ todolistStats: report?.todolistStats
4483
+ });
4484
+ if (iteration === maxIterations) {
4485
+ deps.eventBus.emitTyped("verify:loopExhausted", {
4486
+ issueIid: issue.iid,
4487
+ totalIterations: iteration,
4488
+ failures: report?.failureReasons ?? []
4489
+ });
4490
+ const failMsg = `Verify-fix loop exhausted after ${maxIterations} iterations. Remaining issues: ${report?.failureReasons?.join("; ") ?? "unknown"}`;
4491
+ logger19.warn(failMsg, { iid: issue.iid });
4492
+ throw new AIExecutionError("verify", failMsg, {
4493
+ output: report?.rawReport ?? "",
4494
+ exitCode: 0
4495
+ });
4496
+ }
4497
+ if (buildPhaseIdx >= 0) {
4498
+ await this.executeBuildFix(issueCtx, deps, wtGit, wtPlan, buildPhaseIdx, {
4499
+ iteration,
4500
+ verifyFailures: report?.failureReasons ?? [],
4501
+ rawReport: report?.rawReport ?? ""
4502
+ }, wtGitMap, hooks);
4503
+ }
4504
+ }
4505
+ return { paused: false };
4506
+ }
4507
+ async executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, fixContext, wtGitMap, hooks) {
4508
+ const { issue, phaseCtx, pipelineDef } = ctx;
4509
+ const buildSpec = pipelineDef.phases[buildPhaseIdx];
4510
+ logger19.info("Looping back to build for fix", {
4511
+ iteration: fixContext.iteration,
4512
+ iid: issue.iid,
4513
+ failures: fixContext.verifyFailures
4514
+ });
4515
+ phaseCtx.fixContext = fixContext;
4516
+ try {
4517
+ updateHooksForPhase(buildSpec, pipelineDef, ctx, wtPlan);
4518
+ const buildPhase = createPhase("build", deps.aiRunner, wtGit, wtPlan, deps.config);
4519
+ if (wtGitMap) buildPhase.setWtGitMap(wtGitMap);
4520
+ await runPhaseWithLifecycle(
4521
+ buildPhase,
4522
+ phaseCtx,
4523
+ buildSpec,
4524
+ ctx,
4525
+ deps,
4526
+ wtGit,
4527
+ wtPlan,
4528
+ wtGitMap,
4529
+ hooks
4530
+ );
4531
+ } finally {
4532
+ delete phaseCtx.fixContext;
4533
+ }
4534
+ }
4535
+ findPreviousAiPhaseIndex(phases, currentIdx) {
4536
+ for (let j = currentIdx - 1; j >= 0; j--) {
4537
+ if (phases[j].kind === "ai") return j;
4538
+ }
4539
+ return -1;
4540
+ }
4541
+ };
4542
+
4543
+ // src/orchestrator/strategies/index.ts
4544
+ var gateStrategy = new GateStrategy();
4545
+ var aiStrategy = new AiPhaseStrategy();
4546
+ var verifyFixStrategy = new VerifyFixStrategy();
4547
+ function resolveStrategy(spec, config) {
4548
+ if (spec.kind === "gate") {
4549
+ return gateStrategy;
4550
+ }
4551
+ if (spec.name === "verify" && config.verifyFixLoop.enabled) {
4552
+ return verifyFixStrategy;
4553
+ }
4554
+ return aiStrategy;
4555
+ }
4556
+
4557
+ // src/orchestrator/steps/PhaseLoopStep.ts
4558
+ var logger20 = logger.child("PhaseLoopStep");
4559
+ async function runPhaseWithLifecycle(phase, phaseCtx, spec, ctx, deps, wtGit, wtPlan, wtGitMap, hook) {
4560
+ const lifecycleHook = hook ?? new DefaultLifecycleHook();
4561
+ const execCtx = {
4562
+ spec,
4563
+ issueCtx: ctx,
4564
+ deps,
4565
+ wtGit,
4566
+ wtPlan,
4567
+ wtGitMap,
4568
+ phase,
4569
+ displayId: ctx.issue.iid
4570
+ };
4571
+ await lifecycleHook.beforePhase(execCtx);
4572
+ if (lifecycleHook.beforeExecute) {
4573
+ await lifecycleHook.beforeExecute(execCtx);
4574
+ }
4575
+ const callbacks = createCallbacksFromHook(lifecycleHook, execCtx);
4576
+ const outcome = await phase.run(phaseCtx, callbacks);
4577
+ if (outcome.sessionId) wtPlan.updatePhaseSessionId(spec.name, outcome.sessionId);
4578
+ const finalOutcome = lifecycleHook.afterExecute ? await lifecycleHook.afterExecute(execCtx, outcome) : outcome;
4579
+ if (finalOutcome.status === "completed") {
4580
+ await lifecycleHook.afterPhase(execCtx, finalOutcome);
4581
+ return finalOutcome;
4582
+ }
4583
+ if (finalOutcome.status === "running") {
4584
+ return finalOutcome;
4585
+ }
4586
+ const errMsg = finalOutcome.error?.message ?? "Unknown error";
4587
+ const phaseError = {
4588
+ message: errMsg,
4589
+ isRetryable: finalOutcome.error?.isRetryable ?? true,
4590
+ rawOutput: finalOutcome.error?.rawOutput ?? finalOutcome.output,
4591
+ wasActiveAtTimeout: finalOutcome.error?.wasActiveAtTimeout ?? false
4592
+ };
4593
+ await lifecycleHook.onError(execCtx, phaseError);
4594
+ const shortErr = errMsg.slice(0, 200);
4606
4595
  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
4596
+ output: finalOutcome.error?.rawOutput ?? finalOutcome.output,
4597
+ exitCode: finalOutcome.exitCode ?? 1,
4598
+ isRetryable: finalOutcome.error?.isRetryable,
4599
+ wasActiveAtTimeout: finalOutcome.error?.wasActiveAtTimeout ?? false
4611
4600
  });
4612
4601
  }
4613
4602
  async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
@@ -4631,15 +4620,15 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4631
4620
  if (skippedDeployPhase && !phaseCtx.ports) {
4632
4621
  const existingPorts = deps.getPortsForIssue(issue.iid);
4633
4622
  if (existingPorts && deps.isPreviewRunning(issue.iid)) {
4634
- logger17.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
4623
+ logger20.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
4635
4624
  phaseCtx.ports = existingPorts;
4636
4625
  ctx.wtCtx.ports = existingPorts;
4637
4626
  serversStarted = true;
4638
4627
  } else {
4639
4628
  if (existingPorts) {
4640
- logger17.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
4629
+ logger20.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
4641
4630
  } else {
4642
- logger17.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
4631
+ logger20.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
4643
4632
  }
4644
4633
  const ports = await deps.startPreviewServers(ctx.wtCtx, issue);
4645
4634
  if (ports) {
@@ -4651,93 +4640,25 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4651
4640
  }
4652
4641
  }
4653
4642
  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
- }
4643
+ healStaleProgress(ctx, deps, wtPlan, startIdx);
4690
4644
  }
4645
+ const hooks = new DefaultLifecycleHook();
4691
4646
  for (let i = startIdx; i < pipelineDef.phases.length; i++) {
4692
- if (isShuttingDown()) {
4693
- throw new ServiceShutdownError();
4694
- }
4647
+ if (isShuttingDown()) throw new ServiceShutdownError();
4695
4648
  const spec = pipelineDef.phases[i];
4696
4649
  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 });
4650
+ if (pendingAction) throw new PhaseAbortedError(spec.name, pendingAction);
4651
+ const strategy = resolveStrategy(spec, deps.config);
4652
+ const execCtx = {
4653
+ spec,
4654
+ issueCtx: ctx,
4655
+ deps,
4656
+ wtGit,
4657
+ wtPlan,
4658
+ wtGitMap,
4659
+ displayId: issue.iid
4660
+ };
4661
+ if (strategy.shouldSkip(execCtx)) {
4741
4662
  deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4742
4663
  wtPlan.updatePhaseProgress(spec.name, "completed");
4743
4664
  deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
@@ -4746,49 +4667,12 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4746
4667
  });
4747
4668
  continue;
4748
4669
  }
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 };
4670
+ const result = await strategy.execute(execCtx, hooks);
4671
+ if (result.paused) {
4672
+ return { serversStarted: serversStarted || !!result.serversStarted, paused: true };
4784
4673
  }
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 };
4674
+ if (result.serversStarted) {
4675
+ serversStarted = true;
4792
4676
  }
4793
4677
  if (needsDeployment && !serversStarted && lifecycleManager.shouldDeployPreview(spec.name)) {
4794
4678
  const ports = await deps.startPreviewServers(ctx.wtCtx, issue);
@@ -4801,191 +4685,48 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4801
4685
  }
4802
4686
  return { serversStarted, paused: false };
4803
4687
  }
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);
4688
+ function healStaleProgress(ctx, deps, wtPlan, startIdx) {
4689
+ const { issue, pipelineDef, record } = ctx;
4690
+ const currentProgress = wtPlan.readProgress();
4691
+ if (currentProgress) {
4692
+ let patched = false;
4693
+ for (let i = 0; i < startIdx; i++) {
4694
+ const prevSpec = pipelineDef.phases[i];
4695
+ const pp = currentProgress.phases[prevSpec.name];
4696
+ if (pp && pp.status !== "completed") {
4697
+ logger20.warn("Fixing stale phase progress", {
4698
+ iid: issue.iid,
4699
+ phase: prevSpec.name,
4700
+ was: pp.status,
4701
+ now: "completed"
4702
+ });
4703
+ pp.status = "completed";
4704
+ if (!pp.completedAt) {
4705
+ pp.completedAt = (/* @__PURE__ */ new Date()).toISOString();
4706
+ }
4707
+ patched = true;
4911
4708
  }
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
4709
  }
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
- }
4948
- if (buildPhaseIdx >= 0) {
4949
- await executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, {
4950
- iteration,
4951
- verifyFailures: report?.failureReasons ?? [],
4952
- rawReport: report?.rawReport ?? ""
4953
- }, wtGitMap);
4710
+ if (patched) {
4711
+ wtPlan.writeProgress(currentProgress);
4954
4712
  }
4955
4713
  }
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);
4714
+ if (record.phaseProgress) {
4715
+ for (let i = 0; i < startIdx; i++) {
4716
+ const prevSpec = pipelineDef.phases[i];
4717
+ const tp = record.phaseProgress[prevSpec.name];
4718
+ if (tp && tp.status !== "completed") {
4719
+ deps.tracker.updatePhaseProgress(issue.iid, prevSpec.name, {
4720
+ status: "completed",
4721
+ completedAt: tp.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
4722
+ });
4723
+ }
4971
4724
  }
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
4725
  }
4985
4726
  }
4986
4727
 
4987
4728
  // src/orchestrator/steps/CompletionStep.ts
4988
- var logger18 = logger.child("CompletionStep");
4729
+ var logger21 = logger.child("CompletionStep");
4989
4730
  async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
4990
4731
  const { issue, branchName, wtCtx } = ctx;
4991
4732
  deps.emitProgress(issue.iid, "create_mr", t("orchestrator.createMrProgress"));
@@ -5017,7 +4758,7 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
5017
4758
  mrIid: void 0
5018
4759
  });
5019
4760
  } catch (err) {
5020
- logger18.warn("Failed to publish E2E screenshots", {
4761
+ logger21.warn("Failed to publish E2E screenshots", {
5021
4762
  iid: issue.iid,
5022
4763
  error: err.message
5023
4764
  });
@@ -5037,19 +4778,19 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
5037
4778
  await deps.claimer.releaseClaim(issue.id, issue.iid, "completed");
5038
4779
  }
5039
4780
  if (phaseResult.serversStarted && deps.config.preview.keepAfterComplete) {
5040
- logger18.info("Preview servers kept running after completion", { iid: issue.iid });
4781
+ logger21.info("Preview servers kept running after completion", { iid: issue.iid });
5041
4782
  } else {
5042
4783
  deps.stopPreviewServers(issue.iid);
5043
4784
  await deps.mainGitMutex.runExclusive(async () => {
5044
4785
  if (wtCtx.workspace) {
5045
4786
  await deps.workspaceManager.cleanupWorkspace(wtCtx.workspace);
5046
- logger18.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
4787
+ logger21.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
5047
4788
  } else {
5048
4789
  try {
5049
4790
  await deps.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
5050
- logger18.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
4791
+ logger21.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5051
4792
  } catch (err) {
5052
- logger18.warn("Failed to cleanup worktree", {
4793
+ logger21.warn("Failed to cleanup worktree", {
5053
4794
  dir: wtCtx.gitRootDir,
5054
4795
  error: err.message
5055
4796
  });
@@ -5057,16 +4798,16 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
5057
4798
  }
5058
4799
  });
5059
4800
  }
5060
- logger18.info("Issue processing completed", { iid: issue.iid });
4801
+ logger21.info("Issue processing completed", { iid: issue.iid });
5061
4802
  }
5062
4803
 
5063
4804
  // src/orchestrator/steps/FailureHandler.ts
5064
- var logger19 = logger.child("FailureHandler");
4805
+ var logger22 = logger.child("FailureHandler");
5065
4806
  async function handleFailure(err, issue, wtCtx, deps) {
5066
4807
  const errorMsg = err.message;
5067
4808
  const isRetryable = err instanceof AIExecutionError ? err.isRetryable : true;
5068
4809
  const wasActiveAtTimeout = err instanceof AIExecutionError && err.wasActiveAtTimeout;
5069
- logger19.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
4810
+ logger22.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
5070
4811
  metrics.incCounter("iaf_issues_failed_total");
5071
4812
  const currentRecord = deps.tracker.get(issue.iid);
5072
4813
  const failedAtState = currentRecord?.state || "pending" /* Pending */;
@@ -5079,11 +4820,11 @@ async function handleFailure(err, issue, wtCtx, deps) {
5079
4820
  }
5080
4821
  }
5081
4822
  if (wasReset) {
5082
- logger19.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
4823
+ logger22.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
5083
4824
  throw err;
5084
4825
  }
5085
4826
  if (failedAtState === "paused" /* Paused */) {
5086
- logger19.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
4827
+ logger22.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
5087
4828
  throw err;
5088
4829
  }
5089
4830
  try {
@@ -5105,7 +4846,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
5105
4846
  try {
5106
4847
  await deps.claimer.releaseClaim(issue.id, issue.iid, "failed");
5107
4848
  } catch (releaseErr) {
5108
- logger19.warn("Failed to release lock on failure", {
4849
+ logger22.warn("Failed to release lock on failure", {
5109
4850
  iid: issue.iid,
5110
4851
  error: releaseErr.message
5111
4852
  });
@@ -5113,7 +4854,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
5113
4854
  }
5114
4855
  deps.stopPreviewServers(issue.iid);
5115
4856
  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", {
4857
+ logger22.info("Worktree(s) preserved for debugging", {
5117
4858
  primary: wtCtx.gitRootDir,
5118
4859
  all: preservedDirs
5119
4860
  });
@@ -5122,7 +4863,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
5122
4863
 
5123
4864
  // src/orchestrator/PipelineOrchestrator.ts
5124
4865
  var execFileAsync2 = promisify2(execFile2);
5125
- var logger20 = logger.child("PipelineOrchestrator");
4866
+ var logger23 = logger.child("PipelineOrchestrator");
5126
4867
  var PipelineOrchestrator = class {
5127
4868
  config;
5128
4869
  gongfeng;
@@ -5152,7 +4893,7 @@ var PipelineOrchestrator = class {
5152
4893
  setAIRunner(runner) {
5153
4894
  this.aiRunner = runner;
5154
4895
  this.conflictResolver = new ConflictResolver(runner);
5155
- logger20.info("AIRunner replaced via hot-reload");
4896
+ logger23.info("AIRunner replaced via hot-reload");
5156
4897
  }
5157
4898
  constructor(config, gongfeng, git, aiRunner, tracker, supplementStore, mainGitMutex, eventBusInstance, wsConfig, tenantId, e2eAiRunner) {
5158
4899
  this.config = config;
@@ -5170,14 +4911,14 @@ var PipelineOrchestrator = class {
5170
4911
  this.pipelineDef = mode === "plan-mode" ? buildPlanModePipeline({ releaseEnabled: config.release.enabled, e2eEnabled: config.e2e.enabled }) : getPipelineDef(mode);
5171
4912
  registerPipeline(this.pipelineDef);
5172
4913
  this.lifecycleManager = createLifecycleManager(this.pipelineDef);
5173
- logger20.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
4914
+ logger23.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
5174
4915
  this.portAllocator = new PortAllocator({
5175
4916
  backendPortBase: config.e2e.backendPortBase,
5176
4917
  frontendPortBase: config.e2e.frontendPortBase
5177
4918
  });
5178
4919
  this.devServerManager = new DevServerManager();
5179
4920
  this.screenshotPublisher = new ScreenshotPublisher(gongfeng);
5180
- this.effectiveWorktreeBaseDir = this.tenantId === "default" ? config.project.worktreeBaseDir : path12.join(config.project.worktreeBaseDir, this.tenantId);
4921
+ this.effectiveWorktreeBaseDir = this.tenantId === "default" ? config.project.worktreeBaseDir : path11.join(config.project.worktreeBaseDir, this.tenantId);
5181
4922
  const effectiveWsConfig = wsConfig ?? buildSingleRepoWorkspace(config.project, config.gongfeng.projectPath);
5182
4923
  this.workspaceManager = new WorkspaceManager({
5183
4924
  wsConfig: effectiveWsConfig,
@@ -5186,7 +4927,7 @@ var PipelineOrchestrator = class {
5186
4927
  mainGitMutex: this.mainGitMutex,
5187
4928
  gongfengApiUrl: config.gongfeng.apiUrl
5188
4929
  });
5189
- logger20.info("WorkspaceManager initialized", {
4930
+ logger23.info("WorkspaceManager initialized", {
5190
4931
  tenantId: this.tenantId,
5191
4932
  primary: effectiveWsConfig.primary.name,
5192
4933
  associates: effectiveWsConfig.associates.map((a) => a.name)
@@ -5207,7 +4948,7 @@ var PipelineOrchestrator = class {
5207
4948
  this.claimer = claimer;
5208
4949
  }
5209
4950
  async cleanupStaleState() {
5210
- logger20.info("Cleaning up stale worktree state...");
4951
+ logger23.info("Cleaning up stale worktree state...");
5211
4952
  let cleaned = 0;
5212
4953
  const repoGitRoot = this.config.project.gitRootDir;
5213
4954
  try {
@@ -5216,11 +4957,11 @@ var PipelineOrchestrator = class {
5216
4957
  if (wtDir === repoGitRoot) continue;
5217
4958
  if (!wtDir.includes("/issue-")) continue;
5218
4959
  try {
5219
- const gitFile = path12.join(wtDir, ".git");
4960
+ const gitFile = path11.join(wtDir, ".git");
5220
4961
  try {
5221
- await fs11.access(gitFile);
4962
+ await fs9.access(gitFile);
5222
4963
  } catch {
5223
- logger20.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
4964
+ logger23.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
5224
4965
  await this.mainGit.worktreeRemove(wtDir, true).catch(() => {
5225
4966
  });
5226
4967
  await this.mainGit.worktreePrune();
@@ -5229,32 +4970,32 @@ var PipelineOrchestrator = class {
5229
4970
  }
5230
4971
  const wtGit = new GitOperations(wtDir);
5231
4972
  if (await wtGit.isRebaseInProgress()) {
5232
- logger20.warn("Aborting residual rebase in worktree", { dir: wtDir });
4973
+ logger23.warn("Aborting residual rebase in worktree", { dir: wtDir });
5233
4974
  await wtGit.rebaseAbort();
5234
4975
  cleaned++;
5235
4976
  }
5236
- const indexLock = path12.join(wtDir, ".git", "index.lock");
4977
+ const indexLock = path11.join(wtDir, ".git", "index.lock");
5237
4978
  try {
5238
- await fs11.unlink(indexLock);
5239
- logger20.warn("Removed stale index.lock", { path: indexLock });
4979
+ await fs9.unlink(indexLock);
4980
+ logger23.warn("Removed stale index.lock", { path: indexLock });
5240
4981
  cleaned++;
5241
4982
  } catch {
5242
4983
  }
5243
4984
  } catch (err) {
5244
- logger20.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
4985
+ logger23.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
5245
4986
  }
5246
4987
  }
5247
4988
  } catch (err) {
5248
- logger20.warn("Failed to list worktrees for cleanup", { error: err.message });
4989
+ logger23.warn("Failed to list worktrees for cleanup", { error: err.message });
5249
4990
  }
5250
- const mainIndexLock = path12.join(repoGitRoot, ".git", "index.lock");
4991
+ const mainIndexLock = path11.join(repoGitRoot, ".git", "index.lock");
5251
4992
  try {
5252
- await fs11.unlink(mainIndexLock);
5253
- logger20.warn("Removed stale main repo index.lock", { path: mainIndexLock });
4993
+ await fs9.unlink(mainIndexLock);
4994
+ logger23.warn("Removed stale main repo index.lock", { path: mainIndexLock });
5254
4995
  cleaned++;
5255
4996
  } catch {
5256
4997
  }
5257
- logger20.info("Stale state cleanup complete", { cleaned });
4998
+ logger23.info("Stale state cleanup complete", { cleaned });
5258
4999
  }
5259
5000
  /**
5260
5001
  * 重启后清理幽灵端口分配。
@@ -5267,7 +5008,7 @@ var PipelineOrchestrator = class {
5267
5008
  for (const record of this.tracker.getAll()) {
5268
5009
  if (record.ports) {
5269
5010
  const iid = getIid(record);
5270
- logger20.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
5011
+ logger23.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
5271
5012
  this.tracker.updateState(iid, record.state, {
5272
5013
  ports: void 0,
5273
5014
  previewStartedAt: void 0
@@ -5312,20 +5053,20 @@ var PipelineOrchestrator = class {
5312
5053
  }
5313
5054
  try {
5314
5055
  await this.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
5315
- logger20.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5056
+ logger23.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5316
5057
  } catch (err) {
5317
- logger20.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
5058
+ logger23.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
5318
5059
  }
5319
5060
  }
5320
5061
  async installDependencies(workDir) {
5321
- logger20.info("Installing dependencies in worktree", { workDir });
5062
+ logger23.info("Installing dependencies in worktree", { workDir });
5322
5063
  const knowledge = getProjectKnowledge() ?? KNOWLEDGE_DEFAULTS;
5323
5064
  const pkgMgr = knowledge.toolchain.packageManager.toLowerCase();
5324
5065
  const isNodeProject = ["npm", "pnpm", "yarn", "bun"].some((m) => pkgMgr.includes(m));
5325
5066
  if (isNodeProject) {
5326
5067
  const ready = await this.ensureNodeModules(workDir);
5327
5068
  if (ready) {
5328
- logger20.info("node_modules ready \u2014 skipping install");
5069
+ logger23.info("node_modules ready \u2014 skipping install");
5329
5070
  return;
5330
5071
  }
5331
5072
  }
@@ -5338,10 +5079,10 @@ var PipelineOrchestrator = class {
5338
5079
  maxBuffer: 10 * 1024 * 1024,
5339
5080
  timeout: 3e5
5340
5081
  });
5341
- logger20.info("Dependencies installed");
5082
+ logger23.info("Dependencies installed");
5342
5083
  } catch (err) {
5343
5084
  if (fallbackCmd) {
5344
- logger20.warn(`${installCmd} failed, retrying with fallback command`, {
5085
+ logger23.warn(`${installCmd} failed, retrying with fallback command`, {
5345
5086
  error: err.message
5346
5087
  });
5347
5088
  const [fallbackBin, ...fallbackArgs] = fallbackCmd.split(/\s+/);
@@ -5351,45 +5092,45 @@ var PipelineOrchestrator = class {
5351
5092
  maxBuffer: 10 * 1024 * 1024,
5352
5093
  timeout: 3e5
5353
5094
  });
5354
- logger20.info("Dependencies installed (fallback)");
5095
+ logger23.info("Dependencies installed (fallback)");
5355
5096
  } catch (retryErr) {
5356
- logger20.warn("Fallback install also failed", {
5097
+ logger23.warn("Fallback install also failed", {
5357
5098
  error: retryErr.message
5358
5099
  });
5359
5100
  }
5360
5101
  } else {
5361
- logger20.warn("Install failed, no fallback configured", {
5102
+ logger23.warn("Install failed, no fallback configured", {
5362
5103
  error: err.message
5363
5104
  });
5364
5105
  }
5365
5106
  }
5366
5107
  }
5367
5108
  async ensureNodeModules(workDir) {
5368
- const targetBin = path12.join(workDir, "node_modules", ".bin");
5109
+ const targetBin = path11.join(workDir, "node_modules", ".bin");
5369
5110
  try {
5370
- await fs11.access(targetBin);
5371
- logger20.info("node_modules already complete (has .bin/)");
5111
+ await fs9.access(targetBin);
5112
+ logger23.info("node_modules already complete (has .bin/)");
5372
5113
  return true;
5373
5114
  } catch {
5374
5115
  }
5375
- const sourceNM = path12.join(this.config.project.workDir, "node_modules");
5376
- const targetNM = path12.join(workDir, "node_modules");
5116
+ const sourceNM = path11.join(this.config.project.workDir, "node_modules");
5117
+ const targetNM = path11.join(workDir, "node_modules");
5377
5118
  try {
5378
- await fs11.access(sourceNM);
5119
+ await fs9.access(sourceNM);
5379
5120
  } catch {
5380
- logger20.warn("Main repo node_modules not found, skipping seed", { sourceNM });
5121
+ logger23.warn("Main repo node_modules not found, skipping seed", { sourceNM });
5381
5122
  return false;
5382
5123
  }
5383
- logger20.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
5124
+ logger23.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
5384
5125
  try {
5385
5126
  await execFileAsync2("rm", ["-rf", targetNM], { timeout: 6e4 });
5386
5127
  await execFileAsync2("cp", ["-a", "--reflink=auto", sourceNM, targetNM], {
5387
5128
  timeout: 12e4
5388
5129
  });
5389
- logger20.info("node_modules seeded from main repo");
5130
+ logger23.info("node_modules seeded from main repo");
5390
5131
  return true;
5391
5132
  } catch (err) {
5392
- logger20.warn("Failed to seed node_modules from main repo", {
5133
+ logger23.warn("Failed to seed node_modules from main repo", {
5393
5134
  error: err.message
5394
5135
  });
5395
5136
  return false;
@@ -5399,16 +5140,16 @@ var PipelineOrchestrator = class {
5399
5140
  const record = this.tracker.get(issueIid);
5400
5141
  if (!record) throw new IssueNotFoundError(issueIid);
5401
5142
  const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
5402
- logger20.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
5143
+ logger23.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
5403
5144
  this.pendingActions.set(issueIid, "restart");
5404
5145
  this.aiRunner.killByWorkDir(wtCtx.workDir);
5405
5146
  this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
5406
5147
  this.stopPreviewServers(issueIid);
5407
5148
  try {
5408
5149
  const deleted = await this.gongfeng.cleanupAgentNotes(getExternalId(record));
5409
- logger20.info("Agent notes cleaned up", { issueIid, deleted });
5150
+ logger23.info("Agent notes cleaned up", { issueIid, deleted });
5410
5151
  } catch (err) {
5411
- logger20.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
5152
+ logger23.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
5412
5153
  }
5413
5154
  await this.mainGitMutex.runExclusive(async () => {
5414
5155
  await this.cleanupWorktree(wtCtx);
@@ -5425,19 +5166,19 @@ var PipelineOrchestrator = class {
5425
5166
  await this.cleanupE2eOutputs(issueIid);
5426
5167
  this.tracker.resetFull(issueIid);
5427
5168
  this.pendingActions.delete(issueIid);
5428
- logger20.info("Issue restarted", { issueIid });
5169
+ logger23.info("Issue restarted", { issueIid });
5429
5170
  }
5430
5171
  async cancelIssue(issueIid) {
5431
5172
  const record = this.tracker.get(issueIid);
5432
5173
  if (!record) throw new IssueNotFoundError(issueIid);
5433
5174
  const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
5434
- logger20.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
5175
+ logger23.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
5435
5176
  this.aiRunner.killByWorkDir(wtCtx.workDir);
5436
5177
  this.stopPreviewServers(issueIid);
5437
5178
  try {
5438
5179
  await this.gongfeng.removeLabelsWithPrefix(getExternalId(record), "auto-finish");
5439
5180
  } catch (err) {
5440
- logger20.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
5181
+ logger23.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
5441
5182
  }
5442
5183
  await this.mainGitMutex.runExclusive(async () => {
5443
5184
  await this.cleanupWorktree(wtCtx);
@@ -5454,7 +5195,7 @@ var PipelineOrchestrator = class {
5454
5195
  this.tracker.clearProcessingLock(issueIid);
5455
5196
  this.tracker.updateState(issueIid, "skipped" /* Skipped */);
5456
5197
  await this.cleanupE2eOutputs(issueIid);
5457
- logger20.info("Issue cancelled", { issueIid });
5198
+ logger23.info("Issue cancelled", { issueIid });
5458
5199
  }
5459
5200
  /**
5460
5201
  * Remove the E2E output directory for an issue: {uatVendorDir}/outputs/issue-{iid}
@@ -5462,13 +5203,13 @@ var PipelineOrchestrator = class {
5462
5203
  async cleanupE2eOutputs(issueIid) {
5463
5204
  const vendorDir = this.config.e2e.uatVendorDir;
5464
5205
  if (!vendorDir) return;
5465
- const abs = path12.isAbsolute(vendorDir) ? vendorDir : path12.resolve(this.config.project.workDir, vendorDir);
5466
- const outputDir = path12.join(abs, "outputs", `issue-${issueIid}`);
5206
+ const abs = path11.isAbsolute(vendorDir) ? vendorDir : path11.resolve(this.config.project.workDir, vendorDir);
5207
+ const outputDir = path11.join(abs, "outputs", `issue-${issueIid}`);
5467
5208
  try {
5468
- await fs11.rm(outputDir, { recursive: true, force: true });
5469
- logger20.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
5209
+ await fs9.rm(outputDir, { recursive: true, force: true });
5210
+ logger23.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
5470
5211
  } catch (err) {
5471
- logger20.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
5212
+ logger23.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
5472
5213
  }
5473
5214
  }
5474
5215
  /**
@@ -5480,10 +5221,10 @@ var PipelineOrchestrator = class {
5480
5221
  if (!this.workspaceManager) return;
5481
5222
  const wsRoot = this.workspaceManager.getWorkspaceRoot(issueIid);
5482
5223
  try {
5483
- await fs11.rm(wsRoot, { recursive: true, force: true });
5484
- logger20.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
5224
+ await fs9.rm(wsRoot, { recursive: true, force: true });
5225
+ logger23.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
5485
5226
  } catch (err) {
5486
- logger20.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
5227
+ logger23.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
5487
5228
  }
5488
5229
  }
5489
5230
  retryFromPhase(issueIid, phase) {
@@ -5499,7 +5240,7 @@ var PipelineOrchestrator = class {
5499
5240
  this.aiRunner.killByWorkDir(wtCtx.workDir);
5500
5241
  }
5501
5242
  this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
5502
- logger20.info("Retrying issue from phase", { issueIid, phase });
5243
+ logger23.info("Retrying issue from phase", { issueIid, phase });
5503
5244
  const ok = this.tracker.resetToPhase(issueIid, phase, issueDef);
5504
5245
  if (!ok) {
5505
5246
  throw new InvalidPhaseError(phase);
@@ -5526,7 +5267,7 @@ var PipelineOrchestrator = class {
5526
5267
  } else {
5527
5268
  this.tracker.pauseIssue(issueIid, record.currentPhase ?? "");
5528
5269
  }
5529
- logger20.info("Issue abort requested", { issueIid, state: record.state });
5270
+ logger23.info("Issue abort requested", { issueIid, state: record.state });
5530
5271
  }
5531
5272
  continueIssue(issueIid) {
5532
5273
  const record = this.tracker.get(issueIid);
@@ -5536,7 +5277,7 @@ var PipelineOrchestrator = class {
5536
5277
  }
5537
5278
  const issueDef = this.getIssueSpecificPipelineDef(record);
5538
5279
  this.tracker.resumeFromPause(issueIid, issueDef, false);
5539
- logger20.info("Issue continued from pause", { issueIid });
5280
+ logger23.info("Issue continued from pause", { issueIid });
5540
5281
  }
5541
5282
  redoPhase(issueIid) {
5542
5283
  const record = this.tracker.get(issueIid);
@@ -5580,7 +5321,7 @@ var PipelineOrchestrator = class {
5580
5321
  }
5581
5322
  this.eventBus.emitTyped("issue:redone", { issueIid });
5582
5323
  }
5583
- logger20.info("Issue redo requested", { issueIid, state: record.state });
5324
+ logger23.info("Issue redo requested", { issueIid, state: record.state });
5584
5325
  }
5585
5326
  /**
5586
5327
  * 处理中止/重做的共享逻辑:
@@ -5653,7 +5394,7 @@ var PipelineOrchestrator = class {
5653
5394
  async _processIssueImpl(issue) {
5654
5395
  const branchName = `${this.config.project.branchPrefix}-${issue.iid}`;
5655
5396
  const wtCtx = this.computeWorktreeContext(issue.iid, branchName);
5656
- logger20.info("Processing issue", {
5397
+ logger23.info("Processing issue", {
5657
5398
  iid: issue.iid,
5658
5399
  title: issue.title,
5659
5400
  branchName,
@@ -5760,7 +5501,7 @@ var PipelineOrchestrator = class {
5760
5501
  title,
5761
5502
  description
5762
5503
  });
5763
- logger20.info("Merge request created successfully", {
5504
+ logger23.info("Merge request created successfully", {
5764
5505
  iid: issue.iid,
5765
5506
  mrIid: mr.iid,
5766
5507
  mrUrl: mr.web_url
@@ -5768,7 +5509,7 @@ var PipelineOrchestrator = class {
5768
5509
  return { url: mr.web_url, iid: mr.iid };
5769
5510
  } catch (err) {
5770
5511
  const errorMsg = err.message;
5771
- logger20.warn("Failed to create merge request, trying to find existing one", {
5512
+ logger23.warn("Failed to create merge request, trying to find existing one", {
5772
5513
  iid: issue.iid,
5773
5514
  error: errorMsg
5774
5515
  });
@@ -5785,7 +5526,7 @@ var PipelineOrchestrator = class {
5785
5526
  this.config.project.baseBranch
5786
5527
  );
5787
5528
  if (existing) {
5788
- logger20.info("Found existing merge request", {
5529
+ logger23.info("Found existing merge request", {
5789
5530
  iid: issueIid,
5790
5531
  mrIid: existing.iid,
5791
5532
  mrUrl: existing.web_url
@@ -5793,7 +5534,7 @@ var PipelineOrchestrator = class {
5793
5534
  return { url: existing.web_url, iid: existing.iid };
5794
5535
  }
5795
5536
  } catch (findErr) {
5796
- logger20.warn("Failed to find existing merge request", {
5537
+ logger23.warn("Failed to find existing merge request", {
5797
5538
  iid: issueIid,
5798
5539
  error: findErr.message
5799
5540
  });
@@ -5838,7 +5579,7 @@ var PipelineOrchestrator = class {
5838
5579
  });
5839
5580
  return ports;
5840
5581
  } catch (err) {
5841
- logger20.error("Failed to start preview servers", {
5582
+ logger23.error("Failed to start preview servers", {
5842
5583
  iid: issue.iid,
5843
5584
  error: err.message
5844
5585
  });
@@ -5873,7 +5614,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5873
5614
  await this.mainGitMutex.runExclusive(async () => {
5874
5615
  await this.cleanupWorktree(wtCtx);
5875
5616
  });
5876
- logger20.info("Preview stopped and worktree cleaned", { iid: issueIid });
5617
+ logger23.info("Preview stopped and worktree cleaned", { iid: issueIid });
5877
5618
  }
5878
5619
  async markDeployed(issueIid) {
5879
5620
  const record = this.tracker.get(issueIid);
@@ -5890,7 +5631,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5890
5631
  try {
5891
5632
  await this.gongfeng.closeIssue(externalId);
5892
5633
  } catch (err) {
5893
- logger20.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
5634
+ logger23.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
5894
5635
  }
5895
5636
  try {
5896
5637
  const issue = await this.gongfeng.getIssueDetail(externalId);
@@ -5898,10 +5639,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5898
5639
  labels.push("auto-finish:deployed");
5899
5640
  await this.gongfeng.updateIssueLabels(externalId, labels);
5900
5641
  } catch (err) {
5901
- logger20.warn("Failed to update labels", { iid: issueIid, error: err.message });
5642
+ logger23.warn("Failed to update labels", { iid: issueIid, error: err.message });
5902
5643
  }
5903
5644
  this.tracker.updateState(issueIid, "deployed" /* Deployed */);
5904
- logger20.info("Issue marked as deployed", { iid: issueIid });
5645
+ logger23.info("Issue marked as deployed", { iid: issueIid });
5905
5646
  }
5906
5647
  async restartPreview(issueIid) {
5907
5648
  const record = this.tracker.get(issueIid);
@@ -5928,7 +5669,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5928
5669
  throw err;
5929
5670
  }
5930
5671
  const url = this.buildPreviewUrl(issueIid);
5931
- logger20.info("Preview restarted", { iid: issueIid, url });
5672
+ logger23.info("Preview restarted", { iid: issueIid, url });
5932
5673
  return url;
5933
5674
  }
5934
5675
  getPreviewHost() {
@@ -5961,7 +5702,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5961
5702
  if (!record) throw new IssueNotFoundError(issueIid);
5962
5703
  const baseBranch = this.config.project.baseBranch;
5963
5704
  const branchName = record.branchName;
5964
- logger20.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
5705
+ logger23.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
5965
5706
  this.tracker.updateState(issueIid, "resolving_conflict" /* ResolvingConflict */);
5966
5707
  this.eventBus.emitTyped("conflict:started", { issueIid });
5967
5708
  try {
@@ -5994,7 +5735,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5994
5735
  });
5995
5736
  }
5996
5737
  });
5997
- logger20.info("Running verification after conflict resolution", { issueIid });
5738
+ logger23.info("Running verification after conflict resolution", { issueIid });
5998
5739
  const wtPlan = new PlanPersistence(wtCtx.workDir, issueIid);
5999
5740
  wtPlan.ensureDir();
6000
5741
  const verifyPhase = createPhase("verify", this.aiRunner, wtGit, wtPlan, this.config);
@@ -6032,10 +5773,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
6032
5773
  } catch {
6033
5774
  }
6034
5775
  await this.commentOnMr(record.mrUrl, t("conflict.mrResolvedComment"));
6035
- logger20.info("Conflict resolution completed", { issueIid });
5776
+ logger23.info("Conflict resolution completed", { issueIid });
6036
5777
  } catch (err) {
6037
5778
  const errorMsg = err.message;
6038
- logger20.error("Conflict resolution failed", { issueIid, error: errorMsg });
5779
+ logger23.error("Conflict resolution failed", { issueIid, error: errorMsg });
6039
5780
  try {
6040
5781
  const wtGit = new GitOperations(wtCtx.gitRootDir);
6041
5782
  if (await wtGit.isRebaseInProgress()) {
@@ -6065,7 +5806,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
6065
5806
  try {
6066
5807
  await this.gongfeng.createMergeRequestNote(mrIid, body);
6067
5808
  } catch (err) {
6068
- logger20.warn("Failed to comment on MR", { mrIid, error: err.message });
5809
+ logger23.warn("Failed to comment on MR", { mrIid, error: err.message });
6069
5810
  }
6070
5811
  }
6071
5812
  };
@@ -6141,7 +5882,7 @@ ${questions}
6141
5882
  }
6142
5883
 
6143
5884
  // src/services/BrainstormService.ts
6144
- var logger21 = logger.child("Brainstorm");
5885
+ var logger24 = logger.child("Brainstorm");
6145
5886
  function agentConfigToAIConfig(agentCfg, timeoutMs) {
6146
5887
  return {
6147
5888
  mode: agentCfg.mode,
@@ -6177,7 +5918,7 @@ var BrainstormService = class {
6177
5918
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6178
5919
  };
6179
5920
  this.sessions.set(session.id, session);
6180
- logger21.info("Created brainstorm session", { sessionId: session.id });
5921
+ logger24.info("Created brainstorm session", { sessionId: session.id });
6181
5922
  return session;
6182
5923
  }
6183
5924
  getSession(id) {
@@ -6186,7 +5927,7 @@ var BrainstormService = class {
6186
5927
  async generate(sessionId, onEvent) {
6187
5928
  const session = this.requireSession(sessionId);
6188
5929
  session.status = "generating";
6189
- logger21.info("Generating SDD", { sessionId });
5930
+ logger24.info("Generating SDD", { sessionId });
6190
5931
  const prompt = buildGeneratePrompt(session.transcript);
6191
5932
  const result = await this.generatorRunner.run({
6192
5933
  prompt,
@@ -6212,7 +5953,7 @@ var BrainstormService = class {
6212
5953
  const session = this.requireSession(sessionId);
6213
5954
  const roundNum = session.rounds.length + 1;
6214
5955
  session.status = "reviewing";
6215
- logger21.info("Reviewing SDD", { sessionId, round: roundNum });
5956
+ logger24.info("Reviewing SDD", { sessionId, round: roundNum });
6216
5957
  onEvent?.({ type: "round:start", data: { round: roundNum, phase: "review" }, round: roundNum });
6217
5958
  const prompt = buildReviewPrompt(session.currentSdd, roundNum);
6218
5959
  const result = await this.reviewerRunner.run({
@@ -6245,7 +5986,7 @@ var BrainstormService = class {
6245
5986
  throw new Error("No review round to refine from");
6246
5987
  }
6247
5988
  session.status = "refining";
6248
- logger21.info("Refining SDD", { sessionId, round: currentRound.round });
5989
+ logger24.info("Refining SDD", { sessionId, round: currentRound.round });
6249
5990
  const prompt = buildRefinePrompt(currentRound.questions);
6250
5991
  const result = await this.generatorRunner.run({
6251
5992
  prompt,
@@ -6332,4 +6073,4 @@ export {
6332
6073
  PipelineOrchestrator,
6333
6074
  BrainstormService
6334
6075
  };
6335
- //# sourceMappingURL=chunk-LDGK5NMS.js.map
6076
+ //# sourceMappingURL=chunk-OPWP73PW.js.map