@xdevops/issue-auto-finish 1.0.89 → 1.0.91

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 (47) hide show
  1. package/dist/{chunk-YNRKPQLS.js → chunk-CQ66LL7P.js} +2 -2
  2. package/dist/{chunk-5JBADEKR.js → chunk-ENF24C44.js} +3 -3
  3. package/dist/{chunk-IWSMQXBL.js → chunk-LDGK5NMS.js} +775 -261
  4. package/dist/chunk-LDGK5NMS.js.map +1 -0
  5. package/dist/{chunk-JMACM7AJ.js → chunk-SEO57UYI.js} +18 -5
  6. package/dist/chunk-SEO57UYI.js.map +1 -0
  7. package/dist/{chunk-XSX3PGQW.js → chunk-UMQYEYLO.js} +3 -3
  8. package/dist/{chunk-DVNAH2GV.js → chunk-Z3OBKODV.js} +11 -10
  9. package/dist/chunk-Z3OBKODV.js.map +1 -0
  10. package/dist/cli/setup/ConfigGenerator.d.ts +2 -0
  11. package/dist/cli/setup/ConfigGenerator.d.ts.map +1 -1
  12. package/dist/cli/setup/DependencyChecker.d.ts +1 -1
  13. package/dist/cli/setup/DependencyChecker.d.ts.map +1 -1
  14. package/dist/cli.js +4 -4
  15. package/dist/{doctor-ZG3DO7J5.js → doctor-LLETZLW2.js} +2 -2
  16. package/dist/hooks/HookEventWatcher.d.ts +34 -0
  17. package/dist/hooks/HookEventWatcher.d.ts.map +1 -0
  18. package/dist/hooks/HookInjector.d.ts +85 -0
  19. package/dist/hooks/HookInjector.d.ts.map +1 -0
  20. package/dist/hooks/index.d.ts +4 -0
  21. package/dist/hooks/index.d.ts.map +1 -0
  22. package/dist/index.js +5 -5
  23. package/dist/{init-37DLQ5AJ.js → init-UKTP7LXS.js} +4 -4
  24. package/dist/lib.js +1 -1
  25. package/dist/orchestrator/steps/PhaseLoopStep.d.ts.map +1 -1
  26. package/dist/orchestrator/steps/SetupStep.d.ts.map +1 -1
  27. package/dist/phases/BasePhase.d.ts +2 -0
  28. package/dist/phases/BasePhase.d.ts.map +1 -1
  29. package/dist/{restart-C7QBXT44.js → restart-MSUWF4ID.js} +3 -3
  30. package/dist/run.js +5 -5
  31. package/dist/{start-66JO56AW.js → start-B4CDZAFG.js} +3 -3
  32. package/package.json +1 -1
  33. package/src/web/frontend/dist/assets/index-BoYtsxGN.js +151 -0
  34. package/src/web/frontend/dist/assets/index-DWOHf3bd.css +1 -0
  35. package/src/web/frontend/dist/index.html +2 -2
  36. package/dist/chunk-DVNAH2GV.js.map +0 -1
  37. package/dist/chunk-IWSMQXBL.js.map +0 -1
  38. package/dist/chunk-JMACM7AJ.js.map +0 -1
  39. package/src/web/frontend/dist/assets/index-DJzC2saL.css +0 -1
  40. package/src/web/frontend/dist/assets/index-Mnu8M3ww.js +0 -151
  41. /package/dist/{chunk-YNRKPQLS.js.map → chunk-CQ66LL7P.js.map} +0 -0
  42. /package/dist/{chunk-5JBADEKR.js.map → chunk-ENF24C44.js.map} +0 -0
  43. /package/dist/{chunk-XSX3PGQW.js.map → chunk-UMQYEYLO.js.map} +0 -0
  44. /package/dist/{doctor-ZG3DO7J5.js.map → doctor-LLETZLW2.js.map} +0 -0
  45. /package/dist/{init-37DLQ5AJ.js.map → init-UKTP7LXS.js.map} +0 -0
  46. /package/dist/{restart-C7QBXT44.js.map → restart-MSUWF4ID.js.map} +0 -0
  47. /package/dist/{start-66JO56AW.js.map → start-B4CDZAFG.js.map} +0 -0
@@ -233,8 +233,8 @@ var GongfengClient = class {
233
233
  const encoded = encodeURIComponent(this.projectPath);
234
234
  return `${this.apiUrl}/api/v3/projects/${encoded}`;
235
235
  }
236
- async requestRaw(path12, options = {}) {
237
- const url = `${this.projectApiBase}${path12}`;
236
+ async requestRaw(path13, options = {}) {
237
+ const url = `${this.projectApiBase}${path13}`;
238
238
  logger4.debug("API request", { method: options.method || "GET", url });
239
239
  return this.circuitBreaker.execute(
240
240
  () => this.retryPolicy.execute(async () => {
@@ -251,11 +251,11 @@ var GongfengClient = class {
251
251
  throw new GongfengApiError(resp.status, `Gongfeng API error ${resp.status}: ${body}`, body);
252
252
  }
253
253
  return resp;
254
- }, `requestRaw ${options.method || "GET"} ${path12}`)
254
+ }, `requestRaw ${options.method || "GET"} ${path13}`)
255
255
  );
256
256
  }
257
- async request(path12, options = {}) {
258
- const resp = await this.requestRaw(path12, options);
257
+ async request(path13, options = {}) {
258
+ const resp = await this.requestRaw(path13, options);
259
259
  return resp.json();
260
260
  }
261
261
  async createIssue(title, description, labels) {
@@ -434,8 +434,8 @@ var GongfengClient = class {
434
434
  }
435
435
  return mr;
436
436
  }
437
- async requestGlobal(path12, options = {}) {
438
- const url = `${this.apiUrl}${path12}`;
437
+ async requestGlobal(path13, options = {}) {
438
+ const url = `${this.apiUrl}${path13}`;
439
439
  logger4.debug("API request (global)", { method: options.method || "GET", url });
440
440
  const resp = await this.circuitBreaker.execute(
441
441
  () => this.retryPolicy.execute(async () => {
@@ -452,7 +452,7 @@ var GongfengClient = class {
452
452
  throw new GongfengApiError(r.status, `Gongfeng API error ${r.status}: ${body}`, body);
453
453
  }
454
454
  return r;
455
- }, `requestGlobal ${options.method || "GET"} ${path12}`)
455
+ }, `requestGlobal ${options.method || "GET"} ${path13}`)
456
456
  );
457
457
  return resp.json();
458
458
  }
@@ -1646,7 +1646,7 @@ var PlanPersistence = class _PlanPersistence {
1646
1646
  };
1647
1647
 
1648
1648
  // src/phases/BasePhase.ts
1649
- import path4 from "path";
1649
+ import path5 from "path";
1650
1650
 
1651
1651
  // src/rules/RuleResolver.ts
1652
1652
  import { readdir, readFile } from "fs/promises";
@@ -1743,6 +1743,454 @@ ${rule.content}`;
1743
1743
  }
1744
1744
  };
1745
1745
 
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
+
1746
2194
  // src/phases/BasePhase.ts
1747
2195
  var BasePhase = class _BasePhase {
1748
2196
  static MIN_ARTIFACT_BYTES = 50;
@@ -1887,7 +2335,7 @@ ${t("basePhase.rulesSection", { rules })}`;
1887
2335
  const resultFiles = this.getResultFiles(ctx);
1888
2336
  const snapshotFilenames = resultFiles.map((f) => f.filename);
1889
2337
  const artifactCheck = snapshotFilenames.length > 0 ? () => snapshotFilenames.every((fn) => this.plan.isArtifactReady(fn, _BasePhase.MIN_ARTIFACT_BYTES)) : void 0;
1890
- const artifactPaths = snapshotFilenames.length > 0 ? snapshotFilenames.map((fn) => path4.join(this.plan.planDir, fn)) : void 0;
2338
+ const artifactPaths = snapshotFilenames.length > 0 ? snapshotFilenames.map((fn) => path5.join(this.plan.planDir, fn)) : void 0;
1891
2339
  let capturedSessionId;
1892
2340
  const result = await this.aiRunner.run({
1893
2341
  prompt,
@@ -1984,14 +2432,14 @@ ${t("basePhase.rulesSection", { rules })}`;
1984
2432
  const context = `${ctx.demand.title} ${ctx.demand.description} ${ctx.demand.supplement ? JSON.stringify(ctx.demand.supplement) : ""}`;
1985
2433
  if (ctx.workspace && ctx.workspace.repos.length > 1) {
1986
2434
  for (const repo of ctx.workspace.repos) {
1987
- const rulesDir = path4.join(repo.gitRootDir, ".cursor", "rules");
2435
+ const rulesDir = path5.join(repo.gitRootDir, ".cursor", "rules");
1988
2436
  try {
1989
2437
  await resolver.loadRules(rulesDir);
1990
2438
  } catch {
1991
2439
  }
1992
2440
  }
1993
2441
  } else {
1994
- const rulesDir = path4.join(this.plan.baseDir, ".cursor", "rules");
2442
+ const rulesDir = path5.join(this.plan.baseDir, ".cursor", "rules");
1995
2443
  await resolver.loadRules(rulesDir);
1996
2444
  }
1997
2445
  const matched = resolver.matchRules(context);
@@ -2024,6 +2472,24 @@ ${t("basePhase.rulesSection", { rules })}`;
2024
2472
  this.logger.error(msg, { phase: this.phaseName, displayId: _displayId });
2025
2473
  throw new AIExecutionError(this.phaseName, msg, { output: "", exitCode: 0 });
2026
2474
  }
2475
+ this.logHookManifest();
2476
+ }
2477
+ /** 读取 hook manifest 并记录产物写入历史(P1 增强层,不影响主流程) */
2478
+ logHookManifest() {
2479
+ try {
2480
+ const injector = new HookInjector();
2481
+ const entries = injector.readManifest(this.plan.baseDir);
2482
+ if (entries.length === 0) return;
2483
+ const artifactWrites = entries.filter((e) => "file" in e && e.event === "write");
2484
+ if (artifactWrites.length > 0) {
2485
+ this.logger.info("Hook manifest: artifact writes recorded", {
2486
+ phase: this.phaseName,
2487
+ writes: artifactWrites.length,
2488
+ files: artifactWrites.map((e) => "file" in e ? e.file : "unknown")
2489
+ });
2490
+ }
2491
+ } catch {
2492
+ }
2027
2493
  }
2028
2494
  };
2029
2495
 
@@ -2247,20 +2713,20 @@ var BuildPhase = class extends BasePhase {
2247
2713
  };
2248
2714
 
2249
2715
  // src/release/ReleaseDetectCache.ts
2250
- import fs3 from "fs";
2251
- import path5 from "path";
2716
+ import fs5 from "fs";
2717
+ import path6 from "path";
2252
2718
  import { createHash } from "crypto";
2253
- var logger7 = logger.child("ReleaseDetectCache");
2719
+ var logger9 = logger.child("ReleaseDetectCache");
2254
2720
  function hashProjectPath(projectPath) {
2255
2721
  return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
2256
2722
  }
2257
2723
  var ReleaseDetectCache = class {
2258
2724
  cacheDir;
2259
2725
  constructor(dataDir) {
2260
- this.cacheDir = path5.join(dataDir, "release-detect");
2726
+ this.cacheDir = path6.join(dataDir, "release-detect");
2261
2727
  }
2262
2728
  filePath(projectPath) {
2263
- return path5.join(this.cacheDir, `${hashProjectPath(projectPath)}.json`);
2729
+ return path6.join(this.cacheDir, `${hashProjectPath(projectPath)}.json`);
2264
2730
  }
2265
2731
  /**
2266
2732
  * 读取缓存。返回 null 如果不存在、已过期或校验失败。
@@ -2268,21 +2734,21 @@ var ReleaseDetectCache = class {
2268
2734
  get(projectPath, ttlMs) {
2269
2735
  const fp = this.filePath(projectPath);
2270
2736
  try {
2271
- if (!fs3.existsSync(fp)) return null;
2272
- const raw = fs3.readFileSync(fp, "utf-8");
2737
+ if (!fs5.existsSync(fp)) return null;
2738
+ const raw = fs5.readFileSync(fp, "utf-8");
2273
2739
  const data = JSON.parse(raw);
2274
2740
  if (data.projectPath !== projectPath) {
2275
- logger7.warn("Cache projectPath mismatch, ignoring", { expected: projectPath, got: data.projectPath });
2741
+ logger9.warn("Cache projectPath mismatch, ignoring", { expected: projectPath, got: data.projectPath });
2276
2742
  return null;
2277
2743
  }
2278
2744
  const age = Date.now() - new Date(data.detectedAt).getTime();
2279
2745
  if (age > ttlMs) {
2280
- logger7.debug("Cache expired", { projectPath, ageMs: age, ttlMs });
2746
+ logger9.debug("Cache expired", { projectPath, ageMs: age, ttlMs });
2281
2747
  return null;
2282
2748
  }
2283
2749
  return data;
2284
2750
  } catch (err) {
2285
- logger7.warn("Failed to read release detect cache", { path: fp, error: err.message });
2751
+ logger9.warn("Failed to read release detect cache", { path: fp, error: err.message });
2286
2752
  return null;
2287
2753
  }
2288
2754
  }
@@ -2292,13 +2758,13 @@ var ReleaseDetectCache = class {
2292
2758
  set(result) {
2293
2759
  const fp = this.filePath(result.projectPath);
2294
2760
  try {
2295
- if (!fs3.existsSync(this.cacheDir)) {
2296
- fs3.mkdirSync(this.cacheDir, { recursive: true });
2761
+ if (!fs5.existsSync(this.cacheDir)) {
2762
+ fs5.mkdirSync(this.cacheDir, { recursive: true });
2297
2763
  }
2298
- fs3.writeFileSync(fp, JSON.stringify(result, null, 2), "utf-8");
2299
- logger7.debug("Release detect cache written", { projectPath: result.projectPath, path: fp });
2764
+ fs5.writeFileSync(fp, JSON.stringify(result, null, 2), "utf-8");
2765
+ logger9.debug("Release detect cache written", { projectPath: result.projectPath, path: fp });
2300
2766
  } catch (err) {
2301
- logger7.warn("Failed to write release detect cache", { path: fp, error: err.message });
2767
+ logger9.warn("Failed to write release detect cache", { path: fp, error: err.message });
2302
2768
  }
2303
2769
  }
2304
2770
  /**
@@ -2307,14 +2773,14 @@ var ReleaseDetectCache = class {
2307
2773
  invalidate(projectPath) {
2308
2774
  const fp = this.filePath(projectPath);
2309
2775
  try {
2310
- if (fs3.existsSync(fp)) {
2311
- fs3.unlinkSync(fp);
2312
- logger7.info("Release detect cache invalidated", { projectPath });
2776
+ if (fs5.existsSync(fp)) {
2777
+ fs5.unlinkSync(fp);
2778
+ logger9.info("Release detect cache invalidated", { projectPath });
2313
2779
  return true;
2314
2780
  }
2315
2781
  return false;
2316
2782
  } catch (err) {
2317
- logger7.warn("Failed to invalidate release detect cache", { path: fp, error: err.message });
2783
+ logger9.warn("Failed to invalidate release detect cache", { path: fp, error: err.message });
2318
2784
  return false;
2319
2785
  }
2320
2786
  }
@@ -2765,9 +3231,9 @@ function createLifecycleManager(def) {
2765
3231
 
2766
3232
  // src/workspace/WorkspaceConfig.ts
2767
3233
  import { z } from "zod";
2768
- import fs4 from "fs";
2769
- import path6 from "path";
2770
- var logger8 = logger.child("WorkspaceConfig");
3234
+ import fs6 from "fs";
3235
+ import path7 from "path";
3236
+ var logger10 = logger.child("WorkspaceConfig");
2771
3237
  var repoConfigSchema = z.object({
2772
3238
  name: z.string().min(1, "Repo name is required"),
2773
3239
  projectPath: z.string().min(1, "Gongfeng project path is required"),
@@ -2783,29 +3249,29 @@ var workspaceConfigSchema = z.object({
2783
3249
  });
2784
3250
  function loadWorkspaceConfig(configPath) {
2785
3251
  if (!configPath) return null;
2786
- if (!fs4.existsSync(configPath)) {
2787
- logger8.warn("Workspace config file not found, falling back to single-repo mode", {
3252
+ if (!fs6.existsSync(configPath)) {
3253
+ logger10.warn("Workspace config file not found, falling back to single-repo mode", {
2788
3254
  path: configPath
2789
3255
  });
2790
3256
  return null;
2791
3257
  }
2792
3258
  try {
2793
- const raw = fs4.readFileSync(configPath, "utf-8");
3259
+ const raw = fs6.readFileSync(configPath, "utf-8");
2794
3260
  const json = JSON.parse(raw);
2795
3261
  const result = workspaceConfigSchema.safeParse(json);
2796
3262
  if (!result.success) {
2797
3263
  const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
2798
- logger8.error(`Workspace config validation failed:
3264
+ logger10.error(`Workspace config validation failed:
2799
3265
  ${issues}`);
2800
3266
  return null;
2801
3267
  }
2802
- logger8.info("Workspace config loaded", {
3268
+ logger10.info("Workspace config loaded", {
2803
3269
  primary: result.data.primary.name,
2804
3270
  associates: result.data.associates.map((a) => a.name)
2805
3271
  });
2806
3272
  return result.data;
2807
3273
  } catch (err) {
2808
- logger8.error("Failed to parse workspace config", {
3274
+ logger10.error("Failed to parse workspace config", {
2809
3275
  path: configPath,
2810
3276
  error: err.message
2811
3277
  });
@@ -2831,14 +3297,14 @@ function isMultiRepo(ws) {
2831
3297
  }
2832
3298
  function persistWorkspaceConfig(ws, filePath) {
2833
3299
  try {
2834
- const dir = path6.dirname(filePath);
2835
- if (!fs4.existsSync(dir)) {
2836
- fs4.mkdirSync(dir, { recursive: true });
3300
+ const dir = path7.dirname(filePath);
3301
+ if (!fs6.existsSync(dir)) {
3302
+ fs6.mkdirSync(dir, { recursive: true });
2837
3303
  }
2838
- fs4.writeFileSync(filePath, JSON.stringify(ws, null, 2) + "\n", "utf-8");
2839
- logger8.info("Workspace config auto-generated from .env", { path: filePath });
3304
+ fs6.writeFileSync(filePath, JSON.stringify(ws, null, 2) + "\n", "utf-8");
3305
+ logger10.info("Workspace config auto-generated from .env", { path: filePath });
2840
3306
  } catch (err) {
2841
- logger8.warn("Failed to persist workspace config", {
3307
+ logger10.warn("Failed to persist workspace config", {
2842
3308
  path: filePath,
2843
3309
  error: err.message
2844
3310
  });
@@ -2846,12 +3312,12 @@ function persistWorkspaceConfig(ws, filePath) {
2846
3312
  }
2847
3313
 
2848
3314
  // src/workspace/WorkspaceManager.ts
2849
- import path7 from "path";
2850
- import fs5 from "fs/promises";
3315
+ import path8 from "path";
3316
+ import fs7 from "fs/promises";
2851
3317
  import { execFile } from "child_process";
2852
3318
  import { promisify } from "util";
2853
3319
  var execFileAsync = promisify(execFile);
2854
- var logger9 = logger.child("WorkspaceManager");
3320
+ var logger11 = logger.child("WorkspaceManager");
2855
3321
  var WorkspaceManager = class {
2856
3322
  wsConfig;
2857
3323
  worktreeBaseDir;
@@ -2880,7 +3346,7 @@ var WorkspaceManager = class {
2880
3346
  */
2881
3347
  async prepareWorkspace(issueIid, branchName, globalBaseBranch, globalBranchPrefix) {
2882
3348
  const wsRoot = this.getWorkspaceRoot(issueIid);
2883
- await fs5.mkdir(wsRoot, { recursive: true });
3349
+ await fs7.mkdir(wsRoot, { recursive: true });
2884
3350
  const primaryCtx = await this.preparePrimaryRepo(
2885
3351
  issueIid,
2886
3352
  branchName,
@@ -2899,7 +3365,7 @@ var WorkspaceManager = class {
2899
3365
  );
2900
3366
  associateCtxs.push(ctx);
2901
3367
  }
2902
- logger9.info("Workspace prepared", {
3368
+ logger11.info("Workspace prepared", {
2903
3369
  issueIid,
2904
3370
  wsRoot,
2905
3371
  repos: [primaryCtx.name, ...associateCtxs.map((a) => a.name)]
@@ -2924,7 +3390,7 @@ var WorkspaceManager = class {
2924
3390
  await git.commit(message);
2925
3391
  await git.push(wsCtx.branchName);
2926
3392
  committed.push(repo.name);
2927
- logger9.info("Committed and pushed changes", {
3393
+ logger11.info("Committed and pushed changes", {
2928
3394
  repo: repo.name,
2929
3395
  branch: wsCtx.branchName
2930
3396
  });
@@ -2938,19 +3404,19 @@ var WorkspaceManager = class {
2938
3404
  async cleanupWorkspace(wsCtx) {
2939
3405
  try {
2940
3406
  await this.mainGit.worktreeRemove(wsCtx.primary.gitRootDir, true);
2941
- logger9.info("Primary worktree removed", { dir: wsCtx.primary.gitRootDir });
3407
+ logger11.info("Primary worktree removed", { dir: wsCtx.primary.gitRootDir });
2942
3408
  } catch (err) {
2943
- logger9.warn("Failed to remove primary worktree", {
3409
+ logger11.warn("Failed to remove primary worktree", {
2944
3410
  dir: wsCtx.primary.gitRootDir,
2945
3411
  error: err.message
2946
3412
  });
2947
3413
  }
2948
3414
  for (const assoc of wsCtx.associates) {
2949
3415
  try {
2950
- await fs5.rm(assoc.gitRootDir, { recursive: true, force: true });
2951
- logger9.info("Associate repo dir removed", { name: assoc.name, dir: assoc.gitRootDir });
3416
+ await fs7.rm(assoc.gitRootDir, { recursive: true, force: true });
3417
+ logger11.info("Associate repo dir removed", { name: assoc.name, dir: assoc.gitRootDir });
2952
3418
  } catch (err) {
2953
- logger9.warn("Failed to remove associate repo dir", {
3419
+ logger11.warn("Failed to remove associate repo dir", {
2954
3420
  name: assoc.name,
2955
3421
  dir: assoc.gitRootDir,
2956
3422
  error: err.message
@@ -2958,9 +3424,9 @@ var WorkspaceManager = class {
2958
3424
  }
2959
3425
  }
2960
3426
  try {
2961
- const entries = await fs5.readdir(wsCtx.workspaceRoot);
3427
+ const entries = await fs7.readdir(wsCtx.workspaceRoot);
2962
3428
  if (entries.length === 0) {
2963
- await fs5.rmdir(wsCtx.workspaceRoot);
3429
+ await fs7.rmdir(wsCtx.workspaceRoot);
2964
3430
  }
2965
3431
  } catch {
2966
3432
  }
@@ -2972,13 +3438,13 @@ var WorkspaceManager = class {
2972
3438
  const wsRoot = this.getWorkspaceRoot(issueIid);
2973
3439
  const primary = this.wsConfig.primary;
2974
3440
  const defaultPrefix = globalBranchPrefix ?? primary.branchPrefix ?? "feat/issue";
2975
- const primaryDir = path7.join(wsRoot, primary.name);
3441
+ const primaryDir = path8.join(wsRoot, primary.name);
2976
3442
  const repos = [{
2977
3443
  name: primary.name,
2978
3444
  projectPath: primary.projectPath,
2979
3445
  role: primary.role ?? "",
2980
3446
  gitRootDir: primaryDir,
2981
- workDir: path7.join(primaryDir, primary.projectSubDir ?? ""),
3447
+ workDir: path8.join(primaryDir, primary.projectSubDir ?? ""),
2982
3448
  baseBranch: primary.baseBranch ?? globalBaseBranch,
2983
3449
  branchPrefix: primary.branchPrefix ?? defaultPrefix,
2984
3450
  isPrimary: true
@@ -2988,8 +3454,8 @@ var WorkspaceManager = class {
2988
3454
  name: assoc.name,
2989
3455
  projectPath: assoc.projectPath,
2990
3456
  role: assoc.role ?? "",
2991
- gitRootDir: path7.join(wsRoot, assoc.name),
2992
- workDir: path7.join(wsRoot, assoc.name, assoc.projectSubDir ?? ""),
3457
+ gitRootDir: path8.join(wsRoot, assoc.name),
3458
+ workDir: path8.join(wsRoot, assoc.name, assoc.projectSubDir ?? ""),
2993
3459
  baseBranch: assoc.baseBranch ?? globalBaseBranch,
2994
3460
  branchPrefix: assoc.branchPrefix ?? defaultPrefix,
2995
3461
  isPrimary: false
@@ -2998,12 +3464,12 @@ var WorkspaceManager = class {
2998
3464
  return repos;
2999
3465
  }
3000
3466
  getWorkspaceRoot(issueIid) {
3001
- return path7.join(this.worktreeBaseDir, `issue-${issueIid}`);
3467
+ return path8.join(this.worktreeBaseDir, `issue-${issueIid}`);
3002
3468
  }
3003
3469
  // ── Internal helpers ──
3004
3470
  async preparePrimaryRepo(issueIid, branchName, wsRoot, globalBaseBranch) {
3005
3471
  const primary = this.wsConfig.primary;
3006
- const repoDir = path7.join(wsRoot, primary.name);
3472
+ const repoDir = path8.join(wsRoot, primary.name);
3007
3473
  const baseBranch = primary.baseBranch ?? globalBaseBranch;
3008
3474
  await this.ensurePrimaryWorktree(repoDir, branchName, baseBranch);
3009
3475
  return {
@@ -3011,18 +3477,18 @@ var WorkspaceManager = class {
3011
3477
  projectPath: primary.projectPath,
3012
3478
  role: primary.role ?? "",
3013
3479
  gitRootDir: repoDir,
3014
- workDir: path7.join(repoDir, primary.projectSubDir ?? ""),
3480
+ workDir: path8.join(repoDir, primary.projectSubDir ?? ""),
3015
3481
  baseBranch,
3016
3482
  branchPrefix: primary.branchPrefix ?? "feat/issue",
3017
3483
  isPrimary: true
3018
3484
  };
3019
3485
  }
3020
3486
  async ensurePrimaryWorktree(repoDir, branchName, baseBranch) {
3021
- const wsRoot = path7.dirname(repoDir);
3487
+ const wsRoot = path8.dirname(repoDir);
3022
3488
  if (wsRoot !== repoDir) {
3023
3489
  try {
3024
- await fs5.access(path7.join(wsRoot, ".git"));
3025
- logger9.info("Migrating legacy worktree to primary subdir", { from: wsRoot, to: repoDir });
3490
+ await fs7.access(path8.join(wsRoot, ".git"));
3491
+ logger11.info("Migrating legacy worktree to primary subdir", { from: wsRoot, to: repoDir });
3026
3492
  await this.mainGit.worktreeRemove(wsRoot, true);
3027
3493
  await this.mainGit.worktreePrune();
3028
3494
  await this.cleanStaleDir(wsRoot);
@@ -3032,11 +3498,11 @@ var WorkspaceManager = class {
3032
3498
  const worktrees = await this.mainGit.worktreeList();
3033
3499
  if (worktrees.includes(repoDir)) {
3034
3500
  try {
3035
- await fs5.access(path7.join(repoDir, ".git"));
3036
- logger9.info("Reusing existing primary worktree", { dir: repoDir });
3501
+ await fs7.access(path8.join(repoDir, ".git"));
3502
+ logger11.info("Reusing existing primary worktree", { dir: repoDir });
3037
3503
  return;
3038
3504
  } catch {
3039
- logger9.warn("Primary worktree registered but .git missing, recreating", { dir: repoDir });
3505
+ logger11.warn("Primary worktree registered but .git missing, recreating", { dir: repoDir });
3040
3506
  await this.mainGit.worktreeRemove(repoDir, true);
3041
3507
  await this.mainGit.worktreePrune();
3042
3508
  }
@@ -3055,19 +3521,19 @@ var WorkspaceManager = class {
3055
3521
  await this.mainGit.worktreeAdd(repoDir, branchName, `origin/${baseBranch}`);
3056
3522
  }
3057
3523
  async prepareAssociateRepo(assoc, _issueIid, branchName, wsRoot, globalBaseBranch, globalBranchPrefix) {
3058
- const repoDir = path7.join(wsRoot, assoc.name);
3524
+ const repoDir = path8.join(wsRoot, assoc.name);
3059
3525
  const baseBranch = assoc.baseBranch ?? globalBaseBranch;
3060
3526
  const cloneUrl = `${this.gongfengApiUrl}/${assoc.projectPath}.git`;
3061
- const gitDirExists = await this.dirExists(path7.join(repoDir, ".git"));
3527
+ const gitDirExists = await this.dirExists(path8.join(repoDir, ".git"));
3062
3528
  if (!gitDirExists) {
3063
3529
  await this.cleanStaleDir(repoDir);
3064
- logger9.info("Cloning associate repo", { name: assoc.name, url: cloneUrl });
3530
+ logger11.info("Cloning associate repo", { name: assoc.name, url: cloneUrl });
3065
3531
  await execFileAsync("git", ["clone", "--depth", "50", cloneUrl, repoDir], {
3066
3532
  timeout: 3e5,
3067
3533
  maxBuffer: 10 * 1024 * 1024
3068
3534
  });
3069
3535
  } else {
3070
- logger9.info("Reusing existing associate clone", { name: assoc.name, dir: repoDir });
3536
+ logger11.info("Reusing existing associate clone", { name: assoc.name, dir: repoDir });
3071
3537
  }
3072
3538
  const assocGit = new GitOperations(repoDir);
3073
3539
  await assocGit.fetch();
@@ -3090,7 +3556,7 @@ var WorkspaceManager = class {
3090
3556
  projectPath: assoc.projectPath,
3091
3557
  role: assoc.role ?? "",
3092
3558
  gitRootDir: repoDir,
3093
- workDir: path7.join(repoDir, assoc.projectSubDir ?? ""),
3559
+ workDir: path8.join(repoDir, assoc.projectSubDir ?? ""),
3094
3560
  baseBranch,
3095
3561
  branchPrefix: assoc.branchPrefix ?? globalBranchPrefix,
3096
3562
  isPrimary: false
@@ -3098,13 +3564,13 @@ var WorkspaceManager = class {
3098
3564
  }
3099
3565
  async cleanStaleDir(dir) {
3100
3566
  if (await this.dirExists(dir)) {
3101
- logger9.warn("Removing stale directory", { dir });
3102
- await fs5.rm(dir, { recursive: true, force: true });
3567
+ logger11.warn("Removing stale directory", { dir });
3568
+ await fs7.rm(dir, { recursive: true, force: true });
3103
3569
  }
3104
3570
  }
3105
3571
  async dirExists(dir) {
3106
3572
  try {
3107
- await fs5.access(dir);
3573
+ await fs7.access(dir);
3108
3574
  return true;
3109
3575
  } catch {
3110
3576
  return false;
@@ -3113,8 +3579,8 @@ var WorkspaceManager = class {
3113
3579
  };
3114
3580
 
3115
3581
  // src/orchestrator/PipelineOrchestrator.ts
3116
- import path11 from "path";
3117
- import fs9 from "fs/promises";
3582
+ import path12 from "path";
3583
+ import fs11 from "fs/promises";
3118
3584
  import fsSync from "fs";
3119
3585
  import { execFile as execFile2 } from "child_process";
3120
3586
  import { promisify as promisify2 } from "util";
@@ -3147,8 +3613,8 @@ function mapSupplement(s) {
3147
3613
  }
3148
3614
 
3149
3615
  // src/utils/MergeRequestHelper.ts
3150
- import fs6 from "fs";
3151
- import path8 from "path";
3616
+ import fs8 from "fs";
3617
+ import path9 from "path";
3152
3618
  var TAPD_PATTERNS = [
3153
3619
  /--story=(\d+)/i,
3154
3620
  /--bug=(\d+)/i,
@@ -3192,9 +3658,9 @@ function generateMRDescription(options) {
3192
3658
  ];
3193
3659
  const planSections = [];
3194
3660
  for (const { filename, label } of summaryFiles) {
3195
- const filePath = path8.join(planDir, ".claude-plan", `issue-${issueIid}`, filename);
3196
- if (fs6.existsSync(filePath)) {
3197
- const content = fs6.readFileSync(filePath, "utf-8");
3661
+ const filePath = path9.join(planDir, ".claude-plan", `issue-${issueIid}`, filename);
3662
+ if (fs8.existsSync(filePath)) {
3663
+ const content = fs8.readFileSync(filePath, "utf-8");
3198
3664
  const summary = extractSummary(content);
3199
3665
  if (summary) {
3200
3666
  planSections.push(`### ${label}
@@ -3218,7 +3684,7 @@ function extractSummary(content, maxLines = 20) {
3218
3684
 
3219
3685
  // src/deploy/PortAllocator.ts
3220
3686
  import net from "net";
3221
- var logger10 = logger.child("PortAllocator");
3687
+ var logger12 = logger.child("PortAllocator");
3222
3688
  var DEFAULT_OPTIONS = {
3223
3689
  backendPortBase: 4e3,
3224
3690
  frontendPortBase: 9e3,
@@ -3243,7 +3709,7 @@ var PortAllocator = class {
3243
3709
  async allocate(issueIid) {
3244
3710
  const existing = this.allocated.get(issueIid);
3245
3711
  if (existing) {
3246
- logger10.info("Returning already allocated ports", { issueIid, ports: existing });
3712
+ logger12.info("Returning already allocated ports", { issueIid, ports: existing });
3247
3713
  return existing;
3248
3714
  }
3249
3715
  const usedBackend = new Set([...this.allocated.values()].map((p) => p.backendPort));
@@ -3261,10 +3727,10 @@ var PortAllocator = class {
3261
3727
  if (beOk && feOk) {
3262
3728
  const pair = { backendPort, frontendPort };
3263
3729
  this.allocated.set(issueIid, pair);
3264
- logger10.info("Ports allocated", { issueIid, ...pair });
3730
+ logger12.info("Ports allocated", { issueIid, ...pair });
3265
3731
  return pair;
3266
3732
  }
3267
- logger10.debug("Port pair unavailable, trying next", {
3733
+ logger12.debug("Port pair unavailable, trying next", {
3268
3734
  backendPort,
3269
3735
  frontendPort,
3270
3736
  beOk,
@@ -3279,7 +3745,7 @@ var PortAllocator = class {
3279
3745
  const pair = this.allocated.get(issueIid);
3280
3746
  if (pair) {
3281
3747
  this.allocated.delete(issueIid);
3282
- logger10.info("Ports released", { issueIid, ...pair });
3748
+ logger12.info("Ports released", { issueIid, ...pair });
3283
3749
  }
3284
3750
  }
3285
3751
  getPortsForIssue(issueIid) {
@@ -3290,15 +3756,15 @@ var PortAllocator = class {
3290
3756
  }
3291
3757
  restore(issueIid, ports) {
3292
3758
  this.allocated.set(issueIid, ports);
3293
- logger10.info("Ports restored from persistence", { issueIid, ...ports });
3759
+ logger12.info("Ports restored from persistence", { issueIid, ...ports });
3294
3760
  }
3295
3761
  };
3296
3762
 
3297
3763
  // src/deploy/DevServerManager.ts
3298
3764
  import { spawn } from "child_process";
3299
- import fs7 from "fs";
3300
- import path9 from "path";
3301
- var logger11 = logger.child("DevServerManager");
3765
+ import fs9 from "fs";
3766
+ import path10 from "path";
3767
+ var logger13 = logger.child("DevServerManager");
3302
3768
  var DEFAULT_OPTIONS2 = {};
3303
3769
  var DevServerManager = class {
3304
3770
  servers = /* @__PURE__ */ new Map();
@@ -3306,25 +3772,25 @@ var DevServerManager = class {
3306
3772
  logDir;
3307
3773
  constructor(options) {
3308
3774
  this.options = { ...DEFAULT_OPTIONS2, ...options };
3309
- this.logDir = path9.join(resolveDataDir(), "preview-logs");
3310
- if (!fs7.existsSync(this.logDir)) {
3311
- fs7.mkdirSync(this.logDir, { recursive: true });
3775
+ this.logDir = path10.join(resolveDataDir(), "preview-logs");
3776
+ if (!fs9.existsSync(this.logDir)) {
3777
+ fs9.mkdirSync(this.logDir, { recursive: true });
3312
3778
  }
3313
3779
  }
3314
3780
  getLogPath(issueIid, type) {
3315
- const filePath = path9.join(this.logDir, `${issueIid}-${type}.log`);
3316
- return fs7.existsSync(filePath) ? filePath : null;
3781
+ const filePath = path10.join(this.logDir, `${issueIid}-${type}.log`);
3782
+ return fs9.existsSync(filePath) ? filePath : null;
3317
3783
  }
3318
3784
  async startServers(wtCtx, ports) {
3319
3785
  if (this.servers.has(wtCtx.issueIid)) {
3320
- logger11.info("Servers already running for issue", { issueIid: wtCtx.issueIid });
3786
+ logger13.info("Servers already running for issue", { issueIid: wtCtx.issueIid });
3321
3787
  return;
3322
3788
  }
3323
- logger11.info("Starting dev servers", { issueIid: wtCtx.issueIid, ...ports });
3324
- const backendLogPath = path9.join(this.logDir, `${wtCtx.issueIid}-backend.log`);
3325
- const frontendLogPath = path9.join(this.logDir, `${wtCtx.issueIid}-frontend.log`);
3326
- const backendLog = fs7.createWriteStream(backendLogPath, { flags: "a" });
3327
- const frontendLog = fs7.createWriteStream(frontendLogPath, { flags: "a" });
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" });
3328
3794
  const tsLine = (stream, data) => `[${(/* @__PURE__ */ new Date()).toISOString()}] [${stream}] ${data.toString().trimEnd()}
3329
3795
  `;
3330
3796
  const backendEnv = {
@@ -3348,9 +3814,9 @@ var DevServerManager = class {
3348
3814
  backendLog.write(tsLine("stderr", data));
3349
3815
  });
3350
3816
  backend.on("exit", (code) => {
3351
- logger11.info("Backend process exited", { issueIid: wtCtx.issueIid, code });
3817
+ logger13.info("Backend process exited", { issueIid: wtCtx.issueIid, code });
3352
3818
  });
3353
- const frontendDir = path9.join(wtCtx.workDir, "frontend");
3819
+ const frontendDir = path10.join(wtCtx.workDir, "frontend");
3354
3820
  const frontendEnv = {
3355
3821
  ...process.env,
3356
3822
  BACKEND_PORT: String(ports.backendPort),
@@ -3372,7 +3838,7 @@ var DevServerManager = class {
3372
3838
  frontendLog.write(tsLine("stderr", data));
3373
3839
  });
3374
3840
  frontend.on("exit", (code) => {
3375
- logger11.info("Frontend process exited", { issueIid: wtCtx.issueIid, code });
3841
+ logger13.info("Frontend process exited", { issueIid: wtCtx.issueIid, code });
3376
3842
  });
3377
3843
  const serverSet = {
3378
3844
  backend,
@@ -3384,14 +3850,14 @@ var DevServerManager = class {
3384
3850
  frontendLog
3385
3851
  };
3386
3852
  this.servers.set(wtCtx.issueIid, serverSet);
3387
- logger11.info("Dev servers spawned, waiting for startup", { issueIid: wtCtx.issueIid, ...ports });
3853
+ logger13.info("Dev servers spawned, waiting for startup", { issueIid: wtCtx.issueIid, ...ports });
3388
3854
  await new Promise((r) => setTimeout(r, 1e4));
3389
- logger11.info("Dev servers startup grace period done", { issueIid: wtCtx.issueIid });
3855
+ logger13.info("Dev servers startup grace period done", { issueIid: wtCtx.issueIid });
3390
3856
  }
3391
3857
  stopServers(issueIid) {
3392
3858
  const set = this.servers.get(issueIid);
3393
3859
  if (!set) return;
3394
- logger11.info("Stopping dev servers", { issueIid, ports: set.ports });
3860
+ logger13.info("Stopping dev servers", { issueIid, ports: set.ports });
3395
3861
  killProcess(set.backend, `backend #${issueIid}`);
3396
3862
  killProcess(set.frontend, `frontend #${issueIid}`);
3397
3863
  set.backendLog.end();
@@ -3428,7 +3894,7 @@ function killProcess(proc, label) {
3428
3894
  }
3429
3895
  setTimeout(() => {
3430
3896
  if (!proc.killed && proc.exitCode === null) {
3431
- logger11.warn(`Force killing ${label}`);
3897
+ logger13.warn(`Force killing ${label}`);
3432
3898
  try {
3433
3899
  process.kill(-pid, "SIGKILL");
3434
3900
  } catch {
@@ -3437,7 +3903,7 @@ function killProcess(proc, label) {
3437
3903
  }
3438
3904
  }, 5e3);
3439
3905
  } catch (err) {
3440
- logger11.warn(`Failed to kill ${label}`, { error: err.message });
3906
+ logger13.warn(`Failed to kill ${label}`, { error: err.message });
3441
3907
  }
3442
3908
  }
3443
3909
 
@@ -3456,13 +3922,13 @@ function isE2eEnabledForIssue(issueIid, tracker, cfg) {
3456
3922
  }
3457
3923
 
3458
3924
  // src/e2e/ScreenshotCollector.ts
3459
- import fs8 from "fs";
3460
- import path10 from "path";
3461
- var logger12 = logger.child("ScreenshotCollector");
3925
+ import fs10 from "fs";
3926
+ import path11 from "path";
3927
+ var logger14 = logger.child("ScreenshotCollector");
3462
3928
  var MAX_SCREENSHOTS = 20;
3463
3929
  function walkDir(dir, files = []) {
3464
- for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
3465
- const full = path10.join(dir, entry.name);
3930
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
3931
+ const full = path11.join(dir, entry.name);
3466
3932
  if (entry.isDirectory()) {
3467
3933
  walkDir(full, files);
3468
3934
  } else if (entry.isFile() && entry.name.endsWith(".png")) {
@@ -3472,34 +3938,34 @@ function walkDir(dir, files = []) {
3472
3938
  return files;
3473
3939
  }
3474
3940
  function collectScreenshots(workDir) {
3475
- const testResultsDir = path10.join(workDir, "frontend", "test-results");
3476
- if (!fs8.existsSync(testResultsDir)) {
3477
- logger12.debug("test-results directory not found", { dir: testResultsDir });
3941
+ const testResultsDir = path11.join(workDir, "frontend", "test-results");
3942
+ if (!fs10.existsSync(testResultsDir)) {
3943
+ logger14.debug("test-results directory not found", { dir: testResultsDir });
3478
3944
  return [];
3479
3945
  }
3480
3946
  const pngFiles = walkDir(testResultsDir);
3481
3947
  if (pngFiles.length === 0) {
3482
- logger12.debug("No screenshots found");
3948
+ logger14.debug("No screenshots found");
3483
3949
  return [];
3484
3950
  }
3485
3951
  const screenshots = pngFiles.map((filePath) => {
3486
- const relative = path10.relative(testResultsDir, filePath);
3487
- const testName = relative.split(path10.sep)[0] || path10.basename(filePath, ".png");
3952
+ const relative = path11.relative(testResultsDir, filePath);
3953
+ const testName = relative.split(path11.sep)[0] || path11.basename(filePath, ".png");
3488
3954
  return { filePath, testName };
3489
3955
  });
3490
3956
  if (screenshots.length > MAX_SCREENSHOTS) {
3491
- logger12.warn("Too many screenshots, truncating", {
3957
+ logger14.warn("Too many screenshots, truncating", {
3492
3958
  total: screenshots.length,
3493
3959
  max: MAX_SCREENSHOTS
3494
3960
  });
3495
3961
  return screenshots.slice(0, MAX_SCREENSHOTS);
3496
3962
  }
3497
- logger12.info("Screenshots collected", { count: screenshots.length });
3963
+ logger14.info("Screenshots collected", { count: screenshots.length });
3498
3964
  return screenshots;
3499
3965
  }
3500
3966
 
3501
3967
  // src/e2e/ScreenshotPublisher.ts
3502
- var logger13 = logger.child("ScreenshotPublisher");
3968
+ var logger15 = logger.child("ScreenshotPublisher");
3503
3969
  function buildComment(uploaded, truncated) {
3504
3970
  const lines = [t("screenshot.title"), ""];
3505
3971
  for (const item of uploaded) {
@@ -3518,12 +3984,12 @@ var ScreenshotPublisher = class {
3518
3984
  const { workDir, issueIid, issueId, mrIid } = options;
3519
3985
  const screenshots = collectScreenshots(workDir);
3520
3986
  if (screenshots.length === 0) {
3521
- logger13.info("No E2E screenshots to publish", { issueIid });
3987
+ logger15.info("No E2E screenshots to publish", { issueIid });
3522
3988
  return;
3523
3989
  }
3524
3990
  const uploaded = await this.uploadAll(screenshots);
3525
3991
  if (uploaded.length === 0) {
3526
- logger13.warn("All screenshot uploads failed", { issueIid });
3992
+ logger15.warn("All screenshot uploads failed", { issueIid });
3527
3993
  return;
3528
3994
  }
3529
3995
  const truncated = screenshots.length >= 20;
@@ -3532,7 +3998,7 @@ var ScreenshotPublisher = class {
3532
3998
  if (mrIid) {
3533
3999
  await this.postToMergeRequest(mrIid, comment);
3534
4000
  }
3535
- logger13.info("E2E screenshots published", {
4001
+ logger15.info("E2E screenshots published", {
3536
4002
  issueIid,
3537
4003
  mrIid,
3538
4004
  count: uploaded.length
@@ -3548,7 +4014,7 @@ var ScreenshotPublisher = class {
3548
4014
  markdown: result.markdown
3549
4015
  });
3550
4016
  } catch (err) {
3551
- logger13.warn("Failed to upload screenshot", {
4017
+ logger15.warn("Failed to upload screenshot", {
3552
4018
  filePath: screenshot.filePath,
3553
4019
  error: err.message
3554
4020
  });
@@ -3560,7 +4026,7 @@ var ScreenshotPublisher = class {
3560
4026
  try {
3561
4027
  await this.gongfeng.createIssueNote(issueId, comment);
3562
4028
  } catch (err) {
3563
- logger13.warn("Failed to post screenshots to issue", {
4029
+ logger15.warn("Failed to post screenshots to issue", {
3564
4030
  issueId,
3565
4031
  error: err.message
3566
4032
  });
@@ -3570,7 +4036,7 @@ var ScreenshotPublisher = class {
3570
4036
  try {
3571
4037
  await this.gongfeng.createMergeRequestNote(mrIid, comment);
3572
4038
  } catch (err) {
3573
- logger13.warn("Failed to post screenshots to merge request", {
4039
+ logger15.warn("Failed to post screenshots to merge request", {
3574
4040
  mrIid,
3575
4041
  error: err.message
3576
4042
  });
@@ -3753,7 +4219,7 @@ metrics.registerCounter("iaf_braindump_batches_total", "Total braindump batches"
3753
4219
  metrics.registerCounter("iaf_braindump_tasks_total", "Total braindump tasks");
3754
4220
 
3755
4221
  // src/orchestrator/steps/SetupStep.ts
3756
- var logger14 = logger.child("SetupStep");
4222
+ var logger16 = logger.child("SetupStep");
3757
4223
  async function executeSetup(ctx, deps) {
3758
4224
  const { issue, wtCtx, record, pipelineDef, branchName } = ctx;
3759
4225
  try {
@@ -3762,7 +4228,7 @@ async function executeSetup(ctx, deps) {
3762
4228
  "auto-finish:processing"
3763
4229
  ]);
3764
4230
  } catch (err) {
3765
- logger14.warn("Failed to update issue labels", { error: err.message });
4231
+ logger16.warn("Failed to update issue labels", { error: err.message });
3766
4232
  }
3767
4233
  await deps.mainGitMutex.runExclusive(async () => {
3768
4234
  deps.emitProgress(issue.iid, "fetch", t("orchestrator.fetchProgress"));
@@ -3804,6 +4270,28 @@ async function executeSetup(ctx, deps) {
3804
4270
  if (!record.phaseProgress) {
3805
4271
  deps.tracker.initPhaseProgress(issue.iid, pipelineDef);
3806
4272
  }
4273
+ const allArtifacts = pipelineDef.phases.flatMap((p) => p.artifacts ?? []).map((a) => a.filename);
4274
+ if (allArtifacts.length > 0) {
4275
+ try {
4276
+ const firstPhase = pipelineDef.phases.find((p) => p.kind === "ai");
4277
+ const firstPhaseArtifacts = (firstPhase?.artifacts ?? []).map((a) => a.filename);
4278
+ const hookInjector = new HookInjector();
4279
+ hookInjector.inject({
4280
+ workDir: primaryWorkDir,
4281
+ planDir: wtPlan.planDir,
4282
+ expectedArtifacts: allArtifacts,
4283
+ phaseExpectedArtifacts: firstPhaseArtifacts,
4284
+ issueIid: issue.iid,
4285
+ phaseName: firstPhase?.name,
4286
+ issueTitle: issue.title,
4287
+ issueDescription: issue.description
4288
+ });
4289
+ } catch (err) {
4290
+ logger16.warn("Failed to inject Claude Code hooks (non-blocking)", {
4291
+ error: err.message
4292
+ });
4293
+ }
4294
+ }
3807
4295
  const wtGitMap = /* @__PURE__ */ new Map();
3808
4296
  if (wtCtx.workspace) {
3809
4297
  wtGitMap.set(wtCtx.workspace.primary.name, wtGit);
@@ -3873,7 +4361,7 @@ function clearPendingDialog(issueIid) {
3873
4361
  }
3874
4362
 
3875
4363
  // src/orchestrator/steps/PhaseLoopStep.ts
3876
- var logger15 = logger.child("PhaseLoopStep");
4364
+ var logger17 = logger.child("PhaseLoopStep");
3877
4365
  function resolveVerifyRunner(deps) {
3878
4366
  return deps.aiRunner;
3879
4367
  }
@@ -3889,7 +4377,7 @@ async function commitPlanFiles(ctx, wtGit, wtGitMap, phaseName, displayId) {
3889
4377
  for (const repo of ctx.workspace.repos) {
3890
4378
  const repoGit = wtGitMap?.get(repo.name);
3891
4379
  if (!repoGit) {
3892
- logger15.warn("Missing GitOperations for repo, skipping commit", { repo: repo.name });
4380
+ logger17.warn("Missing GitOperations for repo, skipping commit", { repo: repo.name });
3893
4381
  continue;
3894
4382
  }
3895
4383
  const branch = repo.branchPrefix ? `${repo.branchPrefix}-${displayId}` : ctx.branchName;
@@ -3898,10 +4386,10 @@ async function commitPlanFiles(ctx, wtGit, wtGitMap, phaseName, displayId) {
3898
4386
  await repoGit.add(["."]);
3899
4387
  await repoGit.commit(commitMsg);
3900
4388
  await repoGit.push(branch);
3901
- logger15.info("Committed changes for repo", { repo: repo.name, branch });
4389
+ logger17.info("Committed changes for repo", { repo: repo.name, branch });
3902
4390
  }
3903
4391
  } catch (err) {
3904
- logger15.warn("Failed to commit/push for repo", {
4392
+ logger17.warn("Failed to commit/push for repo", {
3905
4393
  repo: repo.name,
3906
4394
  error: err.message
3907
4395
  });
@@ -3939,10 +4427,10 @@ async function syncResultToIssue(phase, ctx, displayId, phaseName, deps, issueId
3939
4427
  summary
3940
4428
  );
3941
4429
  await safeComment(deps, issueId, comment);
3942
- logger15.info("Result synced to issue", { issueIid: displayId, file: file.filename });
4430
+ logger17.info("Result synced to issue", { issueIid: displayId, file: file.filename });
3943
4431
  }
3944
4432
  } catch (err) {
3945
- logger15.warn("Failed to sync result to issue", { error: err.message });
4433
+ logger17.warn("Failed to sync result to issue", { error: err.message });
3946
4434
  await safeComment(deps, issueId, issueProgressComment(phaseName, "completed"));
3947
4435
  }
3948
4436
  }
@@ -3959,7 +4447,7 @@ function buildInputHandler(displayId, phaseName, deps) {
3959
4447
  };
3960
4448
  }
3961
4449
  function handlePlanApproval(displayId, phaseName, deps) {
3962
- logger15.info("ACP plan-approval requested, delegating to review gate", {
4450
+ logger17.info("ACP plan-approval requested, delegating to review gate", {
3963
4451
  issueIid: displayId,
3964
4452
  phase: phaseName
3965
4453
  });
@@ -3969,14 +4457,14 @@ function handlePlanApproval(displayId, phaseName, deps) {
3969
4457
  const data = payload.data;
3970
4458
  if (data.issueIid !== displayId) return;
3971
4459
  cleanup();
3972
- logger15.info("ACP plan-approval approved via review gate", { issueIid: displayId });
4460
+ logger17.info("ACP plan-approval approved via review gate", { issueIid: displayId });
3973
4461
  resolve("allow");
3974
4462
  };
3975
4463
  const onRejected = (payload) => {
3976
4464
  const data = payload.data;
3977
4465
  if (data.issueIid !== displayId) return;
3978
4466
  cleanup();
3979
- logger15.info("ACP plan-approval rejected via review gate", { issueIid: displayId });
4467
+ logger17.info("ACP plan-approval rejected via review gate", { issueIid: displayId });
3980
4468
  resolve("reject");
3981
4469
  };
3982
4470
  const cleanup = () => {
@@ -3988,7 +4476,7 @@ function handlePlanApproval(displayId, phaseName, deps) {
3988
4476
  });
3989
4477
  }
3990
4478
  function handleInteractiveDialog(displayId, phaseName, deps, request) {
3991
- logger15.info("Interactive dialog forwarded to frontend", {
4479
+ logger17.info("Interactive dialog forwarded to frontend", {
3992
4480
  issueIid: displayId,
3993
4481
  phase: phaseName,
3994
4482
  question: request.content.slice(0, 80),
@@ -4018,7 +4506,7 @@ function handleInteractiveDialog(displayId, phaseName, deps, request) {
4018
4506
  if (data.issueIid !== displayId) return;
4019
4507
  cleanup();
4020
4508
  clearPendingDialog(displayId);
4021
- logger15.info("Interactive dialog response received from frontend", {
4509
+ logger17.info("Interactive dialog response received from frontend", {
4022
4510
  issueIid: displayId,
4023
4511
  response: data.response
4024
4512
  });
@@ -4028,13 +4516,36 @@ function handleInteractiveDialog(displayId, phaseName, deps, request) {
4028
4516
  const data = payload.data;
4029
4517
  if (data.issueIid !== displayId) return;
4030
4518
  cleanup();
4031
- logger15.info("Interactive dialog dismissed by user (false positive)", { issueIid: displayId });
4519
+ logger17.info("Interactive dialog dismissed by user (false positive)", { issueIid: displayId });
4032
4520
  resolve("");
4033
4521
  };
4034
4522
  deps.eventBus.on("agent:inputResponse", onResponse);
4035
4523
  deps.eventBus.on("agent:dialogDismissed", onDismiss);
4036
4524
  });
4037
4525
  }
4526
+ function updateHooksForPhase(spec, pipelineDef, ctx, wtPlan) {
4527
+ const phaseArtifacts = (spec.artifacts ?? []).map((a) => a.filename);
4528
+ if (phaseArtifacts.length === 0 && spec.kind !== "ai") return;
4529
+ try {
4530
+ const allArtifacts = pipelineDef.phases.flatMap((p) => p.artifacts ?? []).map((a) => a.filename);
4531
+ const hookInjector = new HookInjector();
4532
+ hookInjector.updateForPhase({
4533
+ workDir: wtPlan.baseDir,
4534
+ planDir: wtPlan.planDir,
4535
+ expectedArtifacts: allArtifacts.length > 0 ? allArtifacts : phaseArtifacts,
4536
+ phaseExpectedArtifacts: phaseArtifacts,
4537
+ issueIid: ctx.issue.iid,
4538
+ phaseName: spec.name,
4539
+ issueTitle: ctx.issue.title,
4540
+ issueDescription: ctx.issue.description
4541
+ });
4542
+ } catch (err) {
4543
+ logger17.warn("Failed to update hooks for phase (non-blocking)", {
4544
+ phase: spec.name,
4545
+ error: err.message
4546
+ });
4547
+ }
4548
+ }
4038
4549
  async function safeComment(deps, issueId, message) {
4039
4550
  try {
4040
4551
  await deps.gongfeng.createIssueNote(issueId, message);
@@ -4120,15 +4631,15 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4120
4631
  if (skippedDeployPhase && !phaseCtx.ports) {
4121
4632
  const existingPorts = deps.getPortsForIssue(issue.iid);
4122
4633
  if (existingPorts && deps.isPreviewRunning(issue.iid)) {
4123
- logger15.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
4634
+ logger17.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
4124
4635
  phaseCtx.ports = existingPorts;
4125
4636
  ctx.wtCtx.ports = existingPorts;
4126
4637
  serversStarted = true;
4127
4638
  } else {
4128
4639
  if (existingPorts) {
4129
- logger15.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
4640
+ logger17.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
4130
4641
  } else {
4131
- logger15.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
4642
+ logger17.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
4132
4643
  }
4133
4644
  const ports = await deps.startPreviewServers(ctx.wtCtx, issue);
4134
4645
  if (ports) {
@@ -4147,7 +4658,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4147
4658
  const prevSpec = pipelineDef.phases[i];
4148
4659
  const pp = currentProgress.phases[prevSpec.name];
4149
4660
  if (pp && pp.status !== "completed") {
4150
- logger15.warn("Fixing stale phase progress", {
4661
+ logger17.warn("Fixing stale phase progress", {
4151
4662
  iid: issue.iid,
4152
4663
  phase: prevSpec.name,
4153
4664
  was: pp.status,
@@ -4188,7 +4699,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4188
4699
  }
4189
4700
  if (spec.kind === "gate") {
4190
4701
  if (deps.shouldAutoApprove(issue.labels)) {
4191
- logger15.info("Auto-approving review gate (matched autoApproveLabels)", {
4702
+ logger17.info("Auto-approving review gate (matched autoApproveLabels)", {
4192
4703
  iid: issue.iid,
4193
4704
  labels: issue.labels,
4194
4705
  autoApproveLabels: deps.config.review.autoApproveLabels
@@ -4217,7 +4728,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4217
4728
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
4218
4729
  });
4219
4730
  deps.eventBus.emitTyped("review:requested", { issueIid: issue.iid });
4220
- logger15.info("Review gate reached, pausing", { iid: issue.iid });
4731
+ logger17.info("Review gate reached, pausing", { iid: issue.iid });
4221
4732
  return { serversStarted, paused: true };
4222
4733
  }
4223
4734
  if (spec.name === "verify" && deps.config.verifyFixLoop.enabled) {
@@ -4226,7 +4737,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4226
4737
  continue;
4227
4738
  }
4228
4739
  if (spec.name === "uat" && !isE2eEnabledForIssue(issue.iid, deps.tracker, deps.config)) {
4229
- logger15.info("UAT phase skipped (E2E not enabled for this issue)", { iid: issue.iid });
4740
+ logger17.info("UAT phase skipped (E2E not enabled for this issue)", { iid: issue.iid });
4230
4741
  deps.tracker.updateState(issue.iid, spec.doneState, { currentPhase: spec.name });
4231
4742
  wtPlan.updatePhaseProgress(spec.name, "completed");
4232
4743
  deps.tracker.updatePhaseProgress(issue.iid, spec.name, {
@@ -4235,10 +4746,11 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4235
4746
  });
4236
4747
  continue;
4237
4748
  }
4749
+ updateHooksForPhase(spec, pipelineDef, ctx, wtPlan);
4238
4750
  const runner = spec.name === "verify" ? resolveVerifyRunner(deps) : spec.name === "uat" ? resolveUatRunner(deps, issue.iid) : deps.aiRunner;
4239
4751
  if (spec.name === "uat") {
4240
4752
  const runnerName = runner === deps.e2eAiRunner ? "e2eAiRunner (CodeBuddy)" : "mainRunner";
4241
- logger15.info("UAT phase starting", { iid: issue.iid, runner: runnerName });
4753
+ logger17.info("UAT phase starting", { iid: issue.iid, runner: runnerName });
4242
4754
  }
4243
4755
  const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4244
4756
  if (wtGitMap) {
@@ -4256,7 +4768,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4256
4768
  );
4257
4769
  if (outcome.status === "running") {
4258
4770
  if (outcome.awaitCompletion) {
4259
- logger15.info("Async phase running, awaiting completion", { iid: issue.iid, phase: spec.name });
4771
+ logger17.info("Async phase running, awaiting completion", { iid: issue.iid, phase: spec.name });
4260
4772
  const finalOutcome = await awaitAsyncPhase(outcome, spec, ctx, deps, wtGit, wtPlan, wtGitMap);
4261
4773
  if (finalOutcome.status === "completed") {
4262
4774
  continue;
@@ -4267,7 +4779,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4267
4779
  deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4268
4780
  const gateEvent = spec.name === "uat" ? "uat:gateRequested" : "release:gateRequested";
4269
4781
  deps.eventBus.emitTyped(gateEvent, { issueIid: issue.iid });
4270
- logger15.info("Async phase running (no awaitCompletion), pausing pipeline", { iid: issue.iid, phase: spec.name });
4782
+ logger17.info("Async phase running (no awaitCompletion), pausing pipeline", { iid: issue.iid, phase: spec.name });
4271
4783
  return { serversStarted, paused: true };
4272
4784
  }
4273
4785
  if (spec.approvedState && outcome.data?.hasReleaseCapability) {
@@ -4275,7 +4787,7 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
4275
4787
  wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
4276
4788
  deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
4277
4789
  deps.eventBus.emitTyped("release:gateRequested", { issueIid: issue.iid });
4278
- logger15.info("Phase requested gate, pausing", { iid: issue.iid, phase: spec.name });
4790
+ logger17.info("Phase requested gate, pausing", { iid: issue.iid, phase: spec.name });
4279
4791
  return { serversStarted, paused: true };
4280
4792
  }
4281
4793
  if (needsDeployment && !serversStarted && lifecycleManager.shouldDeployPreview(spec.name)) {
@@ -4308,7 +4820,7 @@ async function awaitAsyncPhase(outcome, spec, ctx, deps, wtGit, wtPlan, wtGitMap
4308
4820
  const runner = spec.name === "uat" ? resolveUatRunner(deps, displayId) : deps.aiRunner;
4309
4821
  const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
4310
4822
  await syncResultToIssue(phase, phaseCtx, displayId, spec.name, deps, issue.id, wtPlan);
4311
- logger15.info("Async phase completed successfully", { iid: displayId, phase: spec.name });
4823
+ logger17.info("Async phase completed successfully", { iid: displayId, phase: spec.name });
4312
4824
  return finalOutcome;
4313
4825
  }
4314
4826
  const errMsg = finalOutcome.error?.message ?? "Unknown error";
@@ -4343,7 +4855,7 @@ async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, bu
4343
4855
  issueIid: issue.iid,
4344
4856
  maxIterations
4345
4857
  });
4346
- logger15.info("Verify-fix loop started", {
4858
+ logger17.info("Verify-fix loop started", {
4347
4859
  iid: issue.iid,
4348
4860
  maxIterations,
4349
4861
  buildPhaseIdx
@@ -4352,11 +4864,12 @@ async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, bu
4352
4864
  if (isShuttingDown()) {
4353
4865
  throw new ServiceShutdownError();
4354
4866
  }
4355
- logger15.info("Verify-fix loop iteration", {
4867
+ logger17.info("Verify-fix loop iteration", {
4356
4868
  iteration,
4357
4869
  maxIterations,
4358
4870
  iid: issue.iid
4359
4871
  });
4872
+ updateHooksForPhase(verifySpec, ctx.pipelineDef, ctx, wtPlan);
4360
4873
  const verifyRunner = resolveVerifyRunner(deps);
4361
4874
  const verifyPhase = createPhase("verify", verifyRunner, wtGit, wtPlan, deps.config);
4362
4875
  if (wtGitMap) {
@@ -4375,7 +4888,7 @@ async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, bu
4375
4888
  wtGitMap
4376
4889
  );
4377
4890
  } catch (err) {
4378
- logger15.warn("Verify phase execution failed", {
4891
+ logger17.warn("Verify phase execution failed", {
4379
4892
  iteration,
4380
4893
  iid: issue.iid,
4381
4894
  error: err.message
@@ -4407,13 +4920,13 @@ async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, bu
4407
4920
  failures: report?.failureReasons
4408
4921
  });
4409
4922
  if (passed) {
4410
- logger15.info("Verify-fix loop passed", {
4923
+ logger17.info("Verify-fix loop passed", {
4411
4924
  iteration,
4412
4925
  iid: issue.iid
4413
4926
  });
4414
4927
  return;
4415
4928
  }
4416
- logger15.info("Verify failed, issues found", {
4929
+ logger17.info("Verify failed, issues found", {
4417
4930
  iteration,
4418
4931
  iid: issue.iid,
4419
4932
  failures: report?.failureReasons,
@@ -4426,7 +4939,7 @@ async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, bu
4426
4939
  failures: report?.failureReasons ?? []
4427
4940
  });
4428
4941
  const failMsg = `Verify-fix loop exhausted after ${maxIterations} iterations. Remaining issues: ${report?.failureReasons?.join("; ") ?? "unknown"}`;
4429
- logger15.warn(failMsg, { iid: issue.iid });
4942
+ logger17.warn(failMsg, { iid: issue.iid });
4430
4943
  throw new AIExecutionError("verify", failMsg, {
4431
4944
  output: report?.rawReport ?? "",
4432
4945
  exitCode: 0
@@ -4444,13 +4957,14 @@ async function executeVerifyFixLoop(ctx, deps, wtGit, wtPlan, verifyPhaseIdx, bu
4444
4957
  async function executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, fixContext, wtGitMap) {
4445
4958
  const { issue, phaseCtx } = ctx;
4446
4959
  const buildSpec = ctx.pipelineDef.phases[buildPhaseIdx];
4447
- logger15.info("Looping back to build for fix", {
4960
+ logger17.info("Looping back to build for fix", {
4448
4961
  iteration: fixContext.iteration,
4449
4962
  iid: issue.iid,
4450
4963
  failures: fixContext.verifyFailures
4451
4964
  });
4452
4965
  phaseCtx.fixContext = fixContext;
4453
4966
  try {
4967
+ updateHooksForPhase(buildSpec, ctx.pipelineDef, ctx, wtPlan);
4454
4968
  const buildPhase = createPhase("build", deps.aiRunner, wtGit, wtPlan, deps.config);
4455
4969
  if (wtGitMap) {
4456
4970
  buildPhase.setWtGitMap(wtGitMap);
@@ -4471,7 +4985,7 @@ async function executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, fixConte
4471
4985
  }
4472
4986
 
4473
4987
  // src/orchestrator/steps/CompletionStep.ts
4474
- var logger16 = logger.child("CompletionStep");
4988
+ var logger18 = logger.child("CompletionStep");
4475
4989
  async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
4476
4990
  const { issue, branchName, wtCtx } = ctx;
4477
4991
  deps.emitProgress(issue.iid, "create_mr", t("orchestrator.createMrProgress"));
@@ -4503,7 +5017,7 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
4503
5017
  mrIid: void 0
4504
5018
  });
4505
5019
  } catch (err) {
4506
- logger16.warn("Failed to publish E2E screenshots", {
5020
+ logger18.warn("Failed to publish E2E screenshots", {
4507
5021
  iid: issue.iid,
4508
5022
  error: err.message
4509
5023
  });
@@ -4523,19 +5037,19 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
4523
5037
  await deps.claimer.releaseClaim(issue.id, issue.iid, "completed");
4524
5038
  }
4525
5039
  if (phaseResult.serversStarted && deps.config.preview.keepAfterComplete) {
4526
- logger16.info("Preview servers kept running after completion", { iid: issue.iid });
5040
+ logger18.info("Preview servers kept running after completion", { iid: issue.iid });
4527
5041
  } else {
4528
5042
  deps.stopPreviewServers(issue.iid);
4529
5043
  await deps.mainGitMutex.runExclusive(async () => {
4530
5044
  if (wtCtx.workspace) {
4531
5045
  await deps.workspaceManager.cleanupWorkspace(wtCtx.workspace);
4532
- logger16.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
5046
+ logger18.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
4533
5047
  } else {
4534
5048
  try {
4535
5049
  await deps.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
4536
- logger16.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5050
+ logger18.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
4537
5051
  } catch (err) {
4538
- logger16.warn("Failed to cleanup worktree", {
5052
+ logger18.warn("Failed to cleanup worktree", {
4539
5053
  dir: wtCtx.gitRootDir,
4540
5054
  error: err.message
4541
5055
  });
@@ -4543,16 +5057,16 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
4543
5057
  }
4544
5058
  });
4545
5059
  }
4546
- logger16.info("Issue processing completed", { iid: issue.iid });
5060
+ logger18.info("Issue processing completed", { iid: issue.iid });
4547
5061
  }
4548
5062
 
4549
5063
  // src/orchestrator/steps/FailureHandler.ts
4550
- var logger17 = logger.child("FailureHandler");
5064
+ var logger19 = logger.child("FailureHandler");
4551
5065
  async function handleFailure(err, issue, wtCtx, deps) {
4552
5066
  const errorMsg = err.message;
4553
5067
  const isRetryable = err instanceof AIExecutionError ? err.isRetryable : true;
4554
5068
  const wasActiveAtTimeout = err instanceof AIExecutionError && err.wasActiveAtTimeout;
4555
- logger17.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
5069
+ logger19.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
4556
5070
  metrics.incCounter("iaf_issues_failed_total");
4557
5071
  const currentRecord = deps.tracker.get(issue.iid);
4558
5072
  const failedAtState = currentRecord?.state || "pending" /* Pending */;
@@ -4565,11 +5079,11 @@ async function handleFailure(err, issue, wtCtx, deps) {
4565
5079
  }
4566
5080
  }
4567
5081
  if (wasReset) {
4568
- logger17.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
5082
+ logger19.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
4569
5083
  throw err;
4570
5084
  }
4571
5085
  if (failedAtState === "paused" /* Paused */) {
4572
- logger17.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
5086
+ logger19.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
4573
5087
  throw err;
4574
5088
  }
4575
5089
  try {
@@ -4591,7 +5105,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
4591
5105
  try {
4592
5106
  await deps.claimer.releaseClaim(issue.id, issue.iid, "failed");
4593
5107
  } catch (releaseErr) {
4594
- logger17.warn("Failed to release lock on failure", {
5108
+ logger19.warn("Failed to release lock on failure", {
4595
5109
  iid: issue.iid,
4596
5110
  error: releaseErr.message
4597
5111
  });
@@ -4599,7 +5113,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
4599
5113
  }
4600
5114
  deps.stopPreviewServers(issue.iid);
4601
5115
  const preservedDirs = wtCtx.workspace ? [wtCtx.workspace.primary.gitRootDir, ...wtCtx.workspace.associates.map((a) => a.gitRootDir)] : [wtCtx.gitRootDir];
4602
- logger17.info("Worktree(s) preserved for debugging", {
5116
+ logger19.info("Worktree(s) preserved for debugging", {
4603
5117
  primary: wtCtx.gitRootDir,
4604
5118
  all: preservedDirs
4605
5119
  });
@@ -4608,7 +5122,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
4608
5122
 
4609
5123
  // src/orchestrator/PipelineOrchestrator.ts
4610
5124
  var execFileAsync2 = promisify2(execFile2);
4611
- var logger18 = logger.child("PipelineOrchestrator");
5125
+ var logger20 = logger.child("PipelineOrchestrator");
4612
5126
  var PipelineOrchestrator = class {
4613
5127
  config;
4614
5128
  gongfeng;
@@ -4638,7 +5152,7 @@ var PipelineOrchestrator = class {
4638
5152
  setAIRunner(runner) {
4639
5153
  this.aiRunner = runner;
4640
5154
  this.conflictResolver = new ConflictResolver(runner);
4641
- logger18.info("AIRunner replaced via hot-reload");
5155
+ logger20.info("AIRunner replaced via hot-reload");
4642
5156
  }
4643
5157
  constructor(config, gongfeng, git, aiRunner, tracker, supplementStore, mainGitMutex, eventBusInstance, wsConfig, tenantId, e2eAiRunner) {
4644
5158
  this.config = config;
@@ -4656,14 +5170,14 @@ var PipelineOrchestrator = class {
4656
5170
  this.pipelineDef = mode === "plan-mode" ? buildPlanModePipeline({ releaseEnabled: config.release.enabled, e2eEnabled: config.e2e.enabled }) : getPipelineDef(mode);
4657
5171
  registerPipeline(this.pipelineDef);
4658
5172
  this.lifecycleManager = createLifecycleManager(this.pipelineDef);
4659
- logger18.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
5173
+ logger20.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
4660
5174
  this.portAllocator = new PortAllocator({
4661
5175
  backendPortBase: config.e2e.backendPortBase,
4662
5176
  frontendPortBase: config.e2e.frontendPortBase
4663
5177
  });
4664
5178
  this.devServerManager = new DevServerManager();
4665
5179
  this.screenshotPublisher = new ScreenshotPublisher(gongfeng);
4666
- this.effectiveWorktreeBaseDir = this.tenantId === "default" ? config.project.worktreeBaseDir : path11.join(config.project.worktreeBaseDir, this.tenantId);
5180
+ this.effectiveWorktreeBaseDir = this.tenantId === "default" ? config.project.worktreeBaseDir : path12.join(config.project.worktreeBaseDir, this.tenantId);
4667
5181
  const effectiveWsConfig = wsConfig ?? buildSingleRepoWorkspace(config.project, config.gongfeng.projectPath);
4668
5182
  this.workspaceManager = new WorkspaceManager({
4669
5183
  wsConfig: effectiveWsConfig,
@@ -4672,7 +5186,7 @@ var PipelineOrchestrator = class {
4672
5186
  mainGitMutex: this.mainGitMutex,
4673
5187
  gongfengApiUrl: config.gongfeng.apiUrl
4674
5188
  });
4675
- logger18.info("WorkspaceManager initialized", {
5189
+ logger20.info("WorkspaceManager initialized", {
4676
5190
  tenantId: this.tenantId,
4677
5191
  primary: effectiveWsConfig.primary.name,
4678
5192
  associates: effectiveWsConfig.associates.map((a) => a.name)
@@ -4693,7 +5207,7 @@ var PipelineOrchestrator = class {
4693
5207
  this.claimer = claimer;
4694
5208
  }
4695
5209
  async cleanupStaleState() {
4696
- logger18.info("Cleaning up stale worktree state...");
5210
+ logger20.info("Cleaning up stale worktree state...");
4697
5211
  let cleaned = 0;
4698
5212
  const repoGitRoot = this.config.project.gitRootDir;
4699
5213
  try {
@@ -4702,11 +5216,11 @@ var PipelineOrchestrator = class {
4702
5216
  if (wtDir === repoGitRoot) continue;
4703
5217
  if (!wtDir.includes("/issue-")) continue;
4704
5218
  try {
4705
- const gitFile = path11.join(wtDir, ".git");
5219
+ const gitFile = path12.join(wtDir, ".git");
4706
5220
  try {
4707
- await fs9.access(gitFile);
5221
+ await fs11.access(gitFile);
4708
5222
  } catch {
4709
- logger18.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
5223
+ logger20.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
4710
5224
  await this.mainGit.worktreeRemove(wtDir, true).catch(() => {
4711
5225
  });
4712
5226
  await this.mainGit.worktreePrune();
@@ -4715,32 +5229,32 @@ var PipelineOrchestrator = class {
4715
5229
  }
4716
5230
  const wtGit = new GitOperations(wtDir);
4717
5231
  if (await wtGit.isRebaseInProgress()) {
4718
- logger18.warn("Aborting residual rebase in worktree", { dir: wtDir });
5232
+ logger20.warn("Aborting residual rebase in worktree", { dir: wtDir });
4719
5233
  await wtGit.rebaseAbort();
4720
5234
  cleaned++;
4721
5235
  }
4722
- const indexLock = path11.join(wtDir, ".git", "index.lock");
5236
+ const indexLock = path12.join(wtDir, ".git", "index.lock");
4723
5237
  try {
4724
- await fs9.unlink(indexLock);
4725
- logger18.warn("Removed stale index.lock", { path: indexLock });
5238
+ await fs11.unlink(indexLock);
5239
+ logger20.warn("Removed stale index.lock", { path: indexLock });
4726
5240
  cleaned++;
4727
5241
  } catch {
4728
5242
  }
4729
5243
  } catch (err) {
4730
- logger18.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
5244
+ logger20.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
4731
5245
  }
4732
5246
  }
4733
5247
  } catch (err) {
4734
- logger18.warn("Failed to list worktrees for cleanup", { error: err.message });
5248
+ logger20.warn("Failed to list worktrees for cleanup", { error: err.message });
4735
5249
  }
4736
- const mainIndexLock = path11.join(repoGitRoot, ".git", "index.lock");
5250
+ const mainIndexLock = path12.join(repoGitRoot, ".git", "index.lock");
4737
5251
  try {
4738
- await fs9.unlink(mainIndexLock);
4739
- logger18.warn("Removed stale main repo index.lock", { path: mainIndexLock });
5252
+ await fs11.unlink(mainIndexLock);
5253
+ logger20.warn("Removed stale main repo index.lock", { path: mainIndexLock });
4740
5254
  cleaned++;
4741
5255
  } catch {
4742
5256
  }
4743
- logger18.info("Stale state cleanup complete", { cleaned });
5257
+ logger20.info("Stale state cleanup complete", { cleaned });
4744
5258
  }
4745
5259
  /**
4746
5260
  * 重启后清理幽灵端口分配。
@@ -4753,7 +5267,7 @@ var PipelineOrchestrator = class {
4753
5267
  for (const record of this.tracker.getAll()) {
4754
5268
  if (record.ports) {
4755
5269
  const iid = getIid(record);
4756
- logger18.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
5270
+ logger20.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
4757
5271
  this.tracker.updateState(iid, record.state, {
4758
5272
  ports: void 0,
4759
5273
  previewStartedAt: void 0
@@ -4798,20 +5312,20 @@ var PipelineOrchestrator = class {
4798
5312
  }
4799
5313
  try {
4800
5314
  await this.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
4801
- logger18.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
5315
+ logger20.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
4802
5316
  } catch (err) {
4803
- logger18.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
5317
+ logger20.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
4804
5318
  }
4805
5319
  }
4806
5320
  async installDependencies(workDir) {
4807
- logger18.info("Installing dependencies in worktree", { workDir });
5321
+ logger20.info("Installing dependencies in worktree", { workDir });
4808
5322
  const knowledge = getProjectKnowledge() ?? KNOWLEDGE_DEFAULTS;
4809
5323
  const pkgMgr = knowledge.toolchain.packageManager.toLowerCase();
4810
5324
  const isNodeProject = ["npm", "pnpm", "yarn", "bun"].some((m) => pkgMgr.includes(m));
4811
5325
  if (isNodeProject) {
4812
5326
  const ready = await this.ensureNodeModules(workDir);
4813
5327
  if (ready) {
4814
- logger18.info("node_modules ready \u2014 skipping install");
5328
+ logger20.info("node_modules ready \u2014 skipping install");
4815
5329
  return;
4816
5330
  }
4817
5331
  }
@@ -4824,10 +5338,10 @@ var PipelineOrchestrator = class {
4824
5338
  maxBuffer: 10 * 1024 * 1024,
4825
5339
  timeout: 3e5
4826
5340
  });
4827
- logger18.info("Dependencies installed");
5341
+ logger20.info("Dependencies installed");
4828
5342
  } catch (err) {
4829
5343
  if (fallbackCmd) {
4830
- logger18.warn(`${installCmd} failed, retrying with fallback command`, {
5344
+ logger20.warn(`${installCmd} failed, retrying with fallback command`, {
4831
5345
  error: err.message
4832
5346
  });
4833
5347
  const [fallbackBin, ...fallbackArgs] = fallbackCmd.split(/\s+/);
@@ -4837,45 +5351,45 @@ var PipelineOrchestrator = class {
4837
5351
  maxBuffer: 10 * 1024 * 1024,
4838
5352
  timeout: 3e5
4839
5353
  });
4840
- logger18.info("Dependencies installed (fallback)");
5354
+ logger20.info("Dependencies installed (fallback)");
4841
5355
  } catch (retryErr) {
4842
- logger18.warn("Fallback install also failed", {
5356
+ logger20.warn("Fallback install also failed", {
4843
5357
  error: retryErr.message
4844
5358
  });
4845
5359
  }
4846
5360
  } else {
4847
- logger18.warn("Install failed, no fallback configured", {
5361
+ logger20.warn("Install failed, no fallback configured", {
4848
5362
  error: err.message
4849
5363
  });
4850
5364
  }
4851
5365
  }
4852
5366
  }
4853
5367
  async ensureNodeModules(workDir) {
4854
- const targetBin = path11.join(workDir, "node_modules", ".bin");
5368
+ const targetBin = path12.join(workDir, "node_modules", ".bin");
4855
5369
  try {
4856
- await fs9.access(targetBin);
4857
- logger18.info("node_modules already complete (has .bin/)");
5370
+ await fs11.access(targetBin);
5371
+ logger20.info("node_modules already complete (has .bin/)");
4858
5372
  return true;
4859
5373
  } catch {
4860
5374
  }
4861
- const sourceNM = path11.join(this.config.project.workDir, "node_modules");
4862
- const targetNM = path11.join(workDir, "node_modules");
5375
+ const sourceNM = path12.join(this.config.project.workDir, "node_modules");
5376
+ const targetNM = path12.join(workDir, "node_modules");
4863
5377
  try {
4864
- await fs9.access(sourceNM);
5378
+ await fs11.access(sourceNM);
4865
5379
  } catch {
4866
- logger18.warn("Main repo node_modules not found, skipping seed", { sourceNM });
5380
+ logger20.warn("Main repo node_modules not found, skipping seed", { sourceNM });
4867
5381
  return false;
4868
5382
  }
4869
- logger18.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
5383
+ logger20.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
4870
5384
  try {
4871
5385
  await execFileAsync2("rm", ["-rf", targetNM], { timeout: 6e4 });
4872
5386
  await execFileAsync2("cp", ["-a", "--reflink=auto", sourceNM, targetNM], {
4873
5387
  timeout: 12e4
4874
5388
  });
4875
- logger18.info("node_modules seeded from main repo");
5389
+ logger20.info("node_modules seeded from main repo");
4876
5390
  return true;
4877
5391
  } catch (err) {
4878
- logger18.warn("Failed to seed node_modules from main repo", {
5392
+ logger20.warn("Failed to seed node_modules from main repo", {
4879
5393
  error: err.message
4880
5394
  });
4881
5395
  return false;
@@ -4885,16 +5399,16 @@ var PipelineOrchestrator = class {
4885
5399
  const record = this.tracker.get(issueIid);
4886
5400
  if (!record) throw new IssueNotFoundError(issueIid);
4887
5401
  const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
4888
- logger18.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
5402
+ logger20.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
4889
5403
  this.pendingActions.set(issueIid, "restart");
4890
5404
  this.aiRunner.killByWorkDir(wtCtx.workDir);
4891
5405
  this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
4892
5406
  this.stopPreviewServers(issueIid);
4893
5407
  try {
4894
5408
  const deleted = await this.gongfeng.cleanupAgentNotes(getExternalId(record));
4895
- logger18.info("Agent notes cleaned up", { issueIid, deleted });
5409
+ logger20.info("Agent notes cleaned up", { issueIid, deleted });
4896
5410
  } catch (err) {
4897
- logger18.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
5411
+ logger20.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
4898
5412
  }
4899
5413
  await this.mainGitMutex.runExclusive(async () => {
4900
5414
  await this.cleanupWorktree(wtCtx);
@@ -4911,19 +5425,19 @@ var PipelineOrchestrator = class {
4911
5425
  await this.cleanupE2eOutputs(issueIid);
4912
5426
  this.tracker.resetFull(issueIid);
4913
5427
  this.pendingActions.delete(issueIid);
4914
- logger18.info("Issue restarted", { issueIid });
5428
+ logger20.info("Issue restarted", { issueIid });
4915
5429
  }
4916
5430
  async cancelIssue(issueIid) {
4917
5431
  const record = this.tracker.get(issueIid);
4918
5432
  if (!record) throw new IssueNotFoundError(issueIid);
4919
5433
  const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
4920
- logger18.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
5434
+ logger20.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
4921
5435
  this.aiRunner.killByWorkDir(wtCtx.workDir);
4922
5436
  this.stopPreviewServers(issueIid);
4923
5437
  try {
4924
5438
  await this.gongfeng.removeLabelsWithPrefix(getExternalId(record), "auto-finish");
4925
5439
  } catch (err) {
4926
- logger18.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
5440
+ logger20.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
4927
5441
  }
4928
5442
  await this.mainGitMutex.runExclusive(async () => {
4929
5443
  await this.cleanupWorktree(wtCtx);
@@ -4940,7 +5454,7 @@ var PipelineOrchestrator = class {
4940
5454
  this.tracker.clearProcessingLock(issueIid);
4941
5455
  this.tracker.updateState(issueIid, "skipped" /* Skipped */);
4942
5456
  await this.cleanupE2eOutputs(issueIid);
4943
- logger18.info("Issue cancelled", { issueIid });
5457
+ logger20.info("Issue cancelled", { issueIid });
4944
5458
  }
4945
5459
  /**
4946
5460
  * Remove the E2E output directory for an issue: {uatVendorDir}/outputs/issue-{iid}
@@ -4948,13 +5462,13 @@ var PipelineOrchestrator = class {
4948
5462
  async cleanupE2eOutputs(issueIid) {
4949
5463
  const vendorDir = this.config.e2e.uatVendorDir;
4950
5464
  if (!vendorDir) return;
4951
- const abs = path11.isAbsolute(vendorDir) ? vendorDir : path11.resolve(this.config.project.workDir, vendorDir);
4952
- const outputDir = path11.join(abs, "outputs", `issue-${issueIid}`);
5465
+ const abs = path12.isAbsolute(vendorDir) ? vendorDir : path12.resolve(this.config.project.workDir, vendorDir);
5466
+ const outputDir = path12.join(abs, "outputs", `issue-${issueIid}`);
4953
5467
  try {
4954
- await fs9.rm(outputDir, { recursive: true, force: true });
4955
- logger18.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
5468
+ await fs11.rm(outputDir, { recursive: true, force: true });
5469
+ logger20.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
4956
5470
  } catch (err) {
4957
- logger18.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
5471
+ logger20.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
4958
5472
  }
4959
5473
  }
4960
5474
  /**
@@ -4966,10 +5480,10 @@ var PipelineOrchestrator = class {
4966
5480
  if (!this.workspaceManager) return;
4967
5481
  const wsRoot = this.workspaceManager.getWorkspaceRoot(issueIid);
4968
5482
  try {
4969
- await fs9.rm(wsRoot, { recursive: true, force: true });
4970
- logger18.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
5483
+ await fs11.rm(wsRoot, { recursive: true, force: true });
5484
+ logger20.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
4971
5485
  } catch (err) {
4972
- logger18.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
5486
+ logger20.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
4973
5487
  }
4974
5488
  }
4975
5489
  retryFromPhase(issueIid, phase) {
@@ -4985,7 +5499,7 @@ var PipelineOrchestrator = class {
4985
5499
  this.aiRunner.killByWorkDir(wtCtx.workDir);
4986
5500
  }
4987
5501
  this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
4988
- logger18.info("Retrying issue from phase", { issueIid, phase });
5502
+ logger20.info("Retrying issue from phase", { issueIid, phase });
4989
5503
  const ok = this.tracker.resetToPhase(issueIid, phase, issueDef);
4990
5504
  if (!ok) {
4991
5505
  throw new InvalidPhaseError(phase);
@@ -5012,7 +5526,7 @@ var PipelineOrchestrator = class {
5012
5526
  } else {
5013
5527
  this.tracker.pauseIssue(issueIid, record.currentPhase ?? "");
5014
5528
  }
5015
- logger18.info("Issue abort requested", { issueIid, state: record.state });
5529
+ logger20.info("Issue abort requested", { issueIid, state: record.state });
5016
5530
  }
5017
5531
  continueIssue(issueIid) {
5018
5532
  const record = this.tracker.get(issueIid);
@@ -5022,7 +5536,7 @@ var PipelineOrchestrator = class {
5022
5536
  }
5023
5537
  const issueDef = this.getIssueSpecificPipelineDef(record);
5024
5538
  this.tracker.resumeFromPause(issueIid, issueDef, false);
5025
- logger18.info("Issue continued from pause", { issueIid });
5539
+ logger20.info("Issue continued from pause", { issueIid });
5026
5540
  }
5027
5541
  redoPhase(issueIid) {
5028
5542
  const record = this.tracker.get(issueIid);
@@ -5066,7 +5580,7 @@ var PipelineOrchestrator = class {
5066
5580
  }
5067
5581
  this.eventBus.emitTyped("issue:redone", { issueIid });
5068
5582
  }
5069
- logger18.info("Issue redo requested", { issueIid, state: record.state });
5583
+ logger20.info("Issue redo requested", { issueIid, state: record.state });
5070
5584
  }
5071
5585
  /**
5072
5586
  * 处理中止/重做的共享逻辑:
@@ -5139,7 +5653,7 @@ var PipelineOrchestrator = class {
5139
5653
  async _processIssueImpl(issue) {
5140
5654
  const branchName = `${this.config.project.branchPrefix}-${issue.iid}`;
5141
5655
  const wtCtx = this.computeWorktreeContext(issue.iid, branchName);
5142
- logger18.info("Processing issue", {
5656
+ logger20.info("Processing issue", {
5143
5657
  iid: issue.iid,
5144
5658
  title: issue.title,
5145
5659
  branchName,
@@ -5246,7 +5760,7 @@ var PipelineOrchestrator = class {
5246
5760
  title,
5247
5761
  description
5248
5762
  });
5249
- logger18.info("Merge request created successfully", {
5763
+ logger20.info("Merge request created successfully", {
5250
5764
  iid: issue.iid,
5251
5765
  mrIid: mr.iid,
5252
5766
  mrUrl: mr.web_url
@@ -5254,7 +5768,7 @@ var PipelineOrchestrator = class {
5254
5768
  return { url: mr.web_url, iid: mr.iid };
5255
5769
  } catch (err) {
5256
5770
  const errorMsg = err.message;
5257
- logger18.warn("Failed to create merge request, trying to find existing one", {
5771
+ logger20.warn("Failed to create merge request, trying to find existing one", {
5258
5772
  iid: issue.iid,
5259
5773
  error: errorMsg
5260
5774
  });
@@ -5271,7 +5785,7 @@ var PipelineOrchestrator = class {
5271
5785
  this.config.project.baseBranch
5272
5786
  );
5273
5787
  if (existing) {
5274
- logger18.info("Found existing merge request", {
5788
+ logger20.info("Found existing merge request", {
5275
5789
  iid: issueIid,
5276
5790
  mrIid: existing.iid,
5277
5791
  mrUrl: existing.web_url
@@ -5279,7 +5793,7 @@ var PipelineOrchestrator = class {
5279
5793
  return { url: existing.web_url, iid: existing.iid };
5280
5794
  }
5281
5795
  } catch (findErr) {
5282
- logger18.warn("Failed to find existing merge request", {
5796
+ logger20.warn("Failed to find existing merge request", {
5283
5797
  iid: issueIid,
5284
5798
  error: findErr.message
5285
5799
  });
@@ -5324,7 +5838,7 @@ var PipelineOrchestrator = class {
5324
5838
  });
5325
5839
  return ports;
5326
5840
  } catch (err) {
5327
- logger18.error("Failed to start preview servers", {
5841
+ logger20.error("Failed to start preview servers", {
5328
5842
  iid: issue.iid,
5329
5843
  error: err.message
5330
5844
  });
@@ -5359,7 +5873,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5359
5873
  await this.mainGitMutex.runExclusive(async () => {
5360
5874
  await this.cleanupWorktree(wtCtx);
5361
5875
  });
5362
- logger18.info("Preview stopped and worktree cleaned", { iid: issueIid });
5876
+ logger20.info("Preview stopped and worktree cleaned", { iid: issueIid });
5363
5877
  }
5364
5878
  async markDeployed(issueIid) {
5365
5879
  const record = this.tracker.get(issueIid);
@@ -5376,7 +5890,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5376
5890
  try {
5377
5891
  await this.gongfeng.closeIssue(externalId);
5378
5892
  } catch (err) {
5379
- logger18.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
5893
+ logger20.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
5380
5894
  }
5381
5895
  try {
5382
5896
  const issue = await this.gongfeng.getIssueDetail(externalId);
@@ -5384,10 +5898,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5384
5898
  labels.push("auto-finish:deployed");
5385
5899
  await this.gongfeng.updateIssueLabels(externalId, labels);
5386
5900
  } catch (err) {
5387
- logger18.warn("Failed to update labels", { iid: issueIid, error: err.message });
5901
+ logger20.warn("Failed to update labels", { iid: issueIid, error: err.message });
5388
5902
  }
5389
5903
  this.tracker.updateState(issueIid, "deployed" /* Deployed */);
5390
- logger18.info("Issue marked as deployed", { iid: issueIid });
5904
+ logger20.info("Issue marked as deployed", { iid: issueIid });
5391
5905
  }
5392
5906
  async restartPreview(issueIid) {
5393
5907
  const record = this.tracker.get(issueIid);
@@ -5414,7 +5928,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5414
5928
  throw err;
5415
5929
  }
5416
5930
  const url = this.buildPreviewUrl(issueIid);
5417
- logger18.info("Preview restarted", { iid: issueIid, url });
5931
+ logger20.info("Preview restarted", { iid: issueIid, url });
5418
5932
  return url;
5419
5933
  }
5420
5934
  getPreviewHost() {
@@ -5447,7 +5961,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5447
5961
  if (!record) throw new IssueNotFoundError(issueIid);
5448
5962
  const baseBranch = this.config.project.baseBranch;
5449
5963
  const branchName = record.branchName;
5450
- logger18.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
5964
+ logger20.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
5451
5965
  this.tracker.updateState(issueIid, "resolving_conflict" /* ResolvingConflict */);
5452
5966
  this.eventBus.emitTyped("conflict:started", { issueIid });
5453
5967
  try {
@@ -5480,7 +5994,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5480
5994
  });
5481
5995
  }
5482
5996
  });
5483
- logger18.info("Running verification after conflict resolution", { issueIid });
5997
+ logger20.info("Running verification after conflict resolution", { issueIid });
5484
5998
  const wtPlan = new PlanPersistence(wtCtx.workDir, issueIid);
5485
5999
  wtPlan.ensureDir();
5486
6000
  const verifyPhase = createPhase("verify", this.aiRunner, wtGit, wtPlan, this.config);
@@ -5518,10 +6032,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5518
6032
  } catch {
5519
6033
  }
5520
6034
  await this.commentOnMr(record.mrUrl, t("conflict.mrResolvedComment"));
5521
- logger18.info("Conflict resolution completed", { issueIid });
6035
+ logger20.info("Conflict resolution completed", { issueIid });
5522
6036
  } catch (err) {
5523
6037
  const errorMsg = err.message;
5524
- logger18.error("Conflict resolution failed", { issueIid, error: errorMsg });
6038
+ logger20.error("Conflict resolution failed", { issueIid, error: errorMsg });
5525
6039
  try {
5526
6040
  const wtGit = new GitOperations(wtCtx.gitRootDir);
5527
6041
  if (await wtGit.isRebaseInProgress()) {
@@ -5551,7 +6065,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
5551
6065
  try {
5552
6066
  await this.gongfeng.createMergeRequestNote(mrIid, body);
5553
6067
  } catch (err) {
5554
- logger18.warn("Failed to comment on MR", { mrIid, error: err.message });
6068
+ logger20.warn("Failed to comment on MR", { mrIid, error: err.message });
5555
6069
  }
5556
6070
  }
5557
6071
  };
@@ -5627,7 +6141,7 @@ ${questions}
5627
6141
  }
5628
6142
 
5629
6143
  // src/services/BrainstormService.ts
5630
- var logger19 = logger.child("Brainstorm");
6144
+ var logger21 = logger.child("Brainstorm");
5631
6145
  function agentConfigToAIConfig(agentCfg, timeoutMs) {
5632
6146
  return {
5633
6147
  mode: agentCfg.mode,
@@ -5663,7 +6177,7 @@ var BrainstormService = class {
5663
6177
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5664
6178
  };
5665
6179
  this.sessions.set(session.id, session);
5666
- logger19.info("Created brainstorm session", { sessionId: session.id });
6180
+ logger21.info("Created brainstorm session", { sessionId: session.id });
5667
6181
  return session;
5668
6182
  }
5669
6183
  getSession(id) {
@@ -5672,7 +6186,7 @@ var BrainstormService = class {
5672
6186
  async generate(sessionId, onEvent) {
5673
6187
  const session = this.requireSession(sessionId);
5674
6188
  session.status = "generating";
5675
- logger19.info("Generating SDD", { sessionId });
6189
+ logger21.info("Generating SDD", { sessionId });
5676
6190
  const prompt = buildGeneratePrompt(session.transcript);
5677
6191
  const result = await this.generatorRunner.run({
5678
6192
  prompt,
@@ -5698,7 +6212,7 @@ var BrainstormService = class {
5698
6212
  const session = this.requireSession(sessionId);
5699
6213
  const roundNum = session.rounds.length + 1;
5700
6214
  session.status = "reviewing";
5701
- logger19.info("Reviewing SDD", { sessionId, round: roundNum });
6215
+ logger21.info("Reviewing SDD", { sessionId, round: roundNum });
5702
6216
  onEvent?.({ type: "round:start", data: { round: roundNum, phase: "review" }, round: roundNum });
5703
6217
  const prompt = buildReviewPrompt(session.currentSdd, roundNum);
5704
6218
  const result = await this.reviewerRunner.run({
@@ -5731,7 +6245,7 @@ var BrainstormService = class {
5731
6245
  throw new Error("No review round to refine from");
5732
6246
  }
5733
6247
  session.status = "refining";
5734
- logger19.info("Refining SDD", { sessionId, round: currentRound.round });
6248
+ logger21.info("Refining SDD", { sessionId, round: currentRound.round });
5735
6249
  const prompt = buildRefinePrompt(currentRound.questions);
5736
6250
  const result = await this.generatorRunner.run({
5737
6251
  prompt,
@@ -5818,4 +6332,4 @@ export {
5818
6332
  PipelineOrchestrator,
5819
6333
  BrainstormService
5820
6334
  };
5821
- //# sourceMappingURL=chunk-IWSMQXBL.js.map
6335
+ //# sourceMappingURL=chunk-LDGK5NMS.js.map