@xdevops/issue-auto-finish 1.0.92 → 1.0.94
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.
- package/dist/{PtyRunner-NYASBTRP.js → PtyRunner-6RSDKUMM.js} +4 -2
- package/dist/ai-runner/DialogClassifier.d.ts +44 -0
- package/dist/ai-runner/DialogClassifier.d.ts.map +1 -0
- package/dist/ai-runner/PlanFileResolver.d.ts +1 -0
- package/dist/ai-runner/PlanFileResolver.d.ts.map +1 -1
- package/dist/ai-runner/PtyRunner.d.ts +17 -0
- package/dist/ai-runner/PtyRunner.d.ts.map +1 -1
- package/dist/{ai-runner-TOHVJJ76.js → ai-runner-45IRCBIR.js} +2 -2
- package/dist/{analyze-DBH4K3J7.js → analyze-7TY5DYBT.js} +2 -2
- package/dist/{braindump-RYI4BGMG.js → braindump-FLX6HEVB.js} +2 -2
- package/dist/{chunk-4XMYOXGZ.js → chunk-36G3DPO3.js} +944 -93
- package/dist/chunk-36G3DPO3.js.map +1 -0
- package/dist/{chunk-6T7ZHAV2.js → chunk-4JI5AJEA.js} +9 -9
- package/dist/{chunk-WZGEYHCC.js → chunk-MTXTSSBH.js} +271 -716
- package/dist/chunk-MTXTSSBH.js.map +1 -0
- package/dist/{chunk-ENF24C44.js → chunk-RR65A7J4.js} +2 -2
- package/dist/{chunk-2WDVTLVF.js → chunk-ZDY5NCP3.js} +1 -1
- package/dist/cli.js +5 -5
- package/dist/hooks/HookInjector.d.ts +20 -0
- package/dist/hooks/HookInjector.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{init-UKTP7LXS.js → init-O7XJLCP3.js} +2 -2
- package/dist/lib.js +2 -2
- package/dist/{restart-5D3ZDD5L.js → restart-4LNDGOOU.js} +2 -2
- package/dist/run.js +4 -4
- package/dist/{start-IQBNXLEI.js → start-Z4ODDTJ5.js} +2 -2
- package/package.json +1 -1
- package/src/web/frontend/dist/assets/index-BrvoaFSK.css +1 -0
- package/src/web/frontend/dist/assets/{index-BR0UoQER.js → index-CmyxgdS_.js} +54 -54
- package/src/web/frontend/dist/index.html +2 -2
- package/dist/chunk-4XMYOXGZ.js.map +0 -1
- package/dist/chunk-WZGEYHCC.js.map +0 -1
- package/src/web/frontend/dist/assets/index-DWOHf3bd.css +0 -1
- /package/dist/{PtyRunner-NYASBTRP.js.map → PtyRunner-6RSDKUMM.js.map} +0 -0
- /package/dist/{ai-runner-TOHVJJ76.js.map → ai-runner-45IRCBIR.js.map} +0 -0
- /package/dist/{analyze-DBH4K3J7.js.map → analyze-7TY5DYBT.js.map} +0 -0
- /package/dist/{braindump-RYI4BGMG.js.map → braindump-FLX6HEVB.js.map} +0 -0
- /package/dist/{chunk-6T7ZHAV2.js.map → chunk-4JI5AJEA.js.map} +0 -0
- /package/dist/{chunk-ENF24C44.js.map → chunk-RR65A7J4.js.map} +0 -0
- /package/dist/{chunk-2WDVTLVF.js.map → chunk-ZDY5NCP3.js.map} +0 -0
- /package/dist/{init-UKTP7LXS.js.map → init-O7XJLCP3.js.map} +0 -0
- /package/dist/{restart-5D3ZDD5L.js.map → restart-4LNDGOOU.js.map} +0 -0
- /package/dist/{start-IQBNXLEI.js.map → start-Z4ODDTJ5.js.map} +0 -0
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
rePlanPrompt,
|
|
19
19
|
verifyPrompt
|
|
20
20
|
} from "./chunk-GPZX4DSY.js";
|
|
21
|
+
import {
|
|
22
|
+
HookInjector
|
|
23
|
+
} from "./chunk-36G3DPO3.js";
|
|
21
24
|
import {
|
|
22
25
|
getProjectKnowledge
|
|
23
26
|
} from "./chunk-ACVOOHAR.js";
|
|
@@ -233,8 +236,8 @@ var GongfengClient = class {
|
|
|
233
236
|
const encoded = encodeURIComponent(this.projectPath);
|
|
234
237
|
return `${this.apiUrl}/api/v3/projects/${encoded}`;
|
|
235
238
|
}
|
|
236
|
-
async requestRaw(
|
|
237
|
-
const url = `${this.projectApiBase}${
|
|
239
|
+
async requestRaw(path12, options = {}) {
|
|
240
|
+
const url = `${this.projectApiBase}${path12}`;
|
|
238
241
|
logger4.debug("API request", { method: options.method || "GET", url });
|
|
239
242
|
return this.circuitBreaker.execute(
|
|
240
243
|
() => this.retryPolicy.execute(async () => {
|
|
@@ -251,11 +254,11 @@ var GongfengClient = class {
|
|
|
251
254
|
throw new GongfengApiError(resp.status, `Gongfeng API error ${resp.status}: ${body}`, body);
|
|
252
255
|
}
|
|
253
256
|
return resp;
|
|
254
|
-
}, `requestRaw ${options.method || "GET"} ${
|
|
257
|
+
}, `requestRaw ${options.method || "GET"} ${path12}`)
|
|
255
258
|
);
|
|
256
259
|
}
|
|
257
|
-
async request(
|
|
258
|
-
const resp = await this.requestRaw(
|
|
260
|
+
async request(path12, options = {}) {
|
|
261
|
+
const resp = await this.requestRaw(path12, options);
|
|
259
262
|
return resp.json();
|
|
260
263
|
}
|
|
261
264
|
async createIssue(title, description, labels) {
|
|
@@ -434,8 +437,8 @@ var GongfengClient = class {
|
|
|
434
437
|
}
|
|
435
438
|
return mr;
|
|
436
439
|
}
|
|
437
|
-
async requestGlobal(
|
|
438
|
-
const url = `${this.apiUrl}${
|
|
440
|
+
async requestGlobal(path12, options = {}) {
|
|
441
|
+
const url = `${this.apiUrl}${path12}`;
|
|
439
442
|
logger4.debug("API request (global)", { method: options.method || "GET", url });
|
|
440
443
|
const resp = await this.circuitBreaker.execute(
|
|
441
444
|
() => this.retryPolicy.execute(async () => {
|
|
@@ -452,7 +455,7 @@ var GongfengClient = class {
|
|
|
452
455
|
throw new GongfengApiError(r.status, `Gongfeng API error ${r.status}: ${body}`, body);
|
|
453
456
|
}
|
|
454
457
|
return r;
|
|
455
|
-
}, `requestGlobal ${options.method || "GET"} ${
|
|
458
|
+
}, `requestGlobal ${options.method || "GET"} ${path12}`)
|
|
456
459
|
);
|
|
457
460
|
return resp.json();
|
|
458
461
|
}
|
|
@@ -1646,7 +1649,7 @@ var PlanPersistence = class _PlanPersistence {
|
|
|
1646
1649
|
};
|
|
1647
1650
|
|
|
1648
1651
|
// src/phases/BasePhase.ts
|
|
1649
|
-
import
|
|
1652
|
+
import path4 from "path";
|
|
1650
1653
|
|
|
1651
1654
|
// src/rules/RuleResolver.ts
|
|
1652
1655
|
import { readdir, readFile } from "fs/promises";
|
|
@@ -1743,454 +1746,6 @@ ${rule.content}`;
|
|
|
1743
1746
|
}
|
|
1744
1747
|
};
|
|
1745
1748
|
|
|
1746
|
-
// src/hooks/HookInjector.ts
|
|
1747
|
-
import fs3 from "fs";
|
|
1748
|
-
import path4 from "path";
|
|
1749
|
-
var logger7 = logger.child("HookInjector");
|
|
1750
|
-
var HOOKS_DIR = ".claude-plan/.hooks";
|
|
1751
|
-
var EVENTS_FILE_NAME = ".hook-events.jsonl";
|
|
1752
|
-
var MANIFEST_FILE_NAME = ".artifact-manifest.jsonl";
|
|
1753
|
-
var CONTEXT_FILE_NAME = ".hook-context.json";
|
|
1754
|
-
var HookInjector = class {
|
|
1755
|
-
inject(ctx) {
|
|
1756
|
-
this.writeHookScripts(ctx);
|
|
1757
|
-
this.writeContextFile(ctx);
|
|
1758
|
-
this.writeSettingsLocal(ctx);
|
|
1759
|
-
this.initEventsFile(ctx);
|
|
1760
|
-
logger7.info("Hooks injected", {
|
|
1761
|
-
workDir: ctx.workDir,
|
|
1762
|
-
issueIid: ctx.issueIid,
|
|
1763
|
-
phase: ctx.phaseName,
|
|
1764
|
-
artifacts: ctx.expectedArtifacts
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
/**
|
|
1768
|
-
* 阶段切换时更新 hooks 配置(重写脚本 + settings.local.json)。
|
|
1769
|
-
* 保留 events/manifest 文件(不截断),仅更新脚本和配置。
|
|
1770
|
-
*/
|
|
1771
|
-
updateForPhase(ctx) {
|
|
1772
|
-
this.writeHookScripts(ctx);
|
|
1773
|
-
this.writeContextFile(ctx);
|
|
1774
|
-
this.writeSettingsLocal(ctx);
|
|
1775
|
-
logger7.info("Hooks updated for phase", {
|
|
1776
|
-
workDir: ctx.workDir,
|
|
1777
|
-
issueIid: ctx.issueIid,
|
|
1778
|
-
phase: ctx.phaseName
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
readManifest(workDir) {
|
|
1782
|
-
const manifestPath = path4.join(workDir, ".claude-plan", MANIFEST_FILE_NAME);
|
|
1783
|
-
return readJsonl(manifestPath);
|
|
1784
|
-
}
|
|
1785
|
-
readEvents(workDir) {
|
|
1786
|
-
const eventsPath = path4.join(workDir, ".claude-plan", EVENTS_FILE_NAME);
|
|
1787
|
-
return readJsonl(eventsPath);
|
|
1788
|
-
}
|
|
1789
|
-
getEventsFilePath(workDir) {
|
|
1790
|
-
return path4.join(workDir, ".claude-plan", EVENTS_FILE_NAME);
|
|
1791
|
-
}
|
|
1792
|
-
getManifestFilePath(workDir) {
|
|
1793
|
-
return path4.join(workDir, ".claude-plan", MANIFEST_FILE_NAME);
|
|
1794
|
-
}
|
|
1795
|
-
cleanup(workDir) {
|
|
1796
|
-
const hooksDir = path4.join(workDir, HOOKS_DIR);
|
|
1797
|
-
try {
|
|
1798
|
-
if (fs3.existsSync(hooksDir)) {
|
|
1799
|
-
fs3.rmSync(hooksDir, { recursive: true });
|
|
1800
|
-
}
|
|
1801
|
-
} catch (err) {
|
|
1802
|
-
logger7.warn("Failed to cleanup hooks", { error: err.message });
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
// ---------------------------------------------------------------------------
|
|
1806
|
-
// Private
|
|
1807
|
-
// ---------------------------------------------------------------------------
|
|
1808
|
-
writeHookScripts(ctx) {
|
|
1809
|
-
const hooksDir = path4.join(ctx.workDir, HOOKS_DIR);
|
|
1810
|
-
fs3.mkdirSync(hooksDir, { recursive: true });
|
|
1811
|
-
const eventsFile = path4.join(ctx.workDir, ".claude-plan", EVENTS_FILE_NAME);
|
|
1812
|
-
const manifestFile = path4.join(ctx.workDir, ".claude-plan", MANIFEST_FILE_NAME);
|
|
1813
|
-
const contextFile = path4.join(ctx.workDir, ".claude-plan", CONTEXT_FILE_NAME);
|
|
1814
|
-
const expected = ctx.expectedArtifacts.join(",");
|
|
1815
|
-
const phaseExpected = (ctx.phaseExpectedArtifacts ?? ctx.expectedArtifacts).join(",");
|
|
1816
|
-
const scripts = [
|
|
1817
|
-
{ name: "session-start.sh", content: buildSessionStartScript(eventsFile) },
|
|
1818
|
-
{ name: "compact-restore.sh", content: buildCompactRestoreScript(eventsFile, contextFile) },
|
|
1819
|
-
{ name: "post-tool-use.sh", content: buildPostToolUseScript(eventsFile, manifestFile, expected) },
|
|
1820
|
-
{ name: "post-artifact.sh", content: buildPostArtifactScript(manifestFile, expected) },
|
|
1821
|
-
{ name: "exit-plan-mode.sh", content: buildExitPlanModeScript(eventsFile) },
|
|
1822
|
-
{ name: "permission.sh", content: buildPermissionScript(eventsFile) },
|
|
1823
|
-
{ name: "protect-files.sh", content: buildProtectFilesScript(eventsFile, ctx.phaseName, ctx.planDir) },
|
|
1824
|
-
{ name: "stop.sh", content: buildStopScript(eventsFile, ctx.planDir, phaseExpected) }
|
|
1825
|
-
];
|
|
1826
|
-
for (const { name, content } of scripts) {
|
|
1827
|
-
const scriptPath = path4.join(hooksDir, name);
|
|
1828
|
-
fs3.writeFileSync(scriptPath, content, { mode: 493 });
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
writeContextFile(ctx) {
|
|
1832
|
-
const contextPath = path4.join(ctx.workDir, ".claude-plan", CONTEXT_FILE_NAME);
|
|
1833
|
-
const context = {
|
|
1834
|
-
issueIid: ctx.issueIid,
|
|
1835
|
-
issueTitle: ctx.issueTitle ?? "",
|
|
1836
|
-
issueDescription: ctx.issueDescription ?? "",
|
|
1837
|
-
phaseName: ctx.phaseName ?? "",
|
|
1838
|
-
expectedArtifacts: ctx.expectedArtifacts,
|
|
1839
|
-
planDir: ctx.planDir
|
|
1840
|
-
};
|
|
1841
|
-
fs3.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
|
|
1842
|
-
}
|
|
1843
|
-
writeSettingsLocal(ctx) {
|
|
1844
|
-
const claudeDir = path4.join(ctx.workDir, ".claude");
|
|
1845
|
-
fs3.mkdirSync(claudeDir, { recursive: true });
|
|
1846
|
-
const settingsPath = path4.join(claudeDir, "settings.local.json");
|
|
1847
|
-
let existing = {};
|
|
1848
|
-
if (fs3.existsSync(settingsPath)) {
|
|
1849
|
-
try {
|
|
1850
|
-
existing = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
|
|
1851
|
-
} catch {
|
|
1852
|
-
logger7.warn("Failed to parse existing settings.local.json, overwriting");
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
const hooksDir = path4.join(ctx.workDir, HOOKS_DIR);
|
|
1856
|
-
const hooks = buildHooksConfig(hooksDir, ctx);
|
|
1857
|
-
const merged = { ...existing, hooks };
|
|
1858
|
-
fs3.writeFileSync(settingsPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
1859
|
-
}
|
|
1860
|
-
initEventsFile(ctx) {
|
|
1861
|
-
const eventsPath = path4.join(ctx.workDir, ".claude-plan", EVENTS_FILE_NAME);
|
|
1862
|
-
const manifestPath = path4.join(ctx.workDir, ".claude-plan", MANIFEST_FILE_NAME);
|
|
1863
|
-
fs3.writeFileSync(eventsPath, "", "utf-8");
|
|
1864
|
-
fs3.writeFileSync(manifestPath, "", "utf-8");
|
|
1865
|
-
}
|
|
1866
|
-
};
|
|
1867
|
-
function buildHooksConfig(hooksDir, ctx) {
|
|
1868
|
-
const isPlanPhase = ctx.phaseName === "plan";
|
|
1869
|
-
const artifactIfPatterns = buildArtifactIfPatterns(ctx.expectedArtifacts);
|
|
1870
|
-
const config = {
|
|
1871
|
-
SessionStart: [
|
|
1872
|
-
{
|
|
1873
|
-
hooks: [{
|
|
1874
|
-
type: "command",
|
|
1875
|
-
command: path4.join(hooksDir, "session-start.sh"),
|
|
1876
|
-
timeout: 5
|
|
1877
|
-
}]
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
matcher: "compact",
|
|
1881
|
-
hooks: [{
|
|
1882
|
-
type: "command",
|
|
1883
|
-
command: path4.join(hooksDir, "compact-restore.sh"),
|
|
1884
|
-
timeout: 5
|
|
1885
|
-
}]
|
|
1886
|
-
}
|
|
1887
|
-
],
|
|
1888
|
-
PreToolUse: [
|
|
1889
|
-
{
|
|
1890
|
-
matcher: "Edit|Write",
|
|
1891
|
-
hooks: [{
|
|
1892
|
-
type: "command",
|
|
1893
|
-
command: path4.join(hooksDir, "protect-files.sh"),
|
|
1894
|
-
timeout: 5,
|
|
1895
|
-
...buildProtectIfClause(ctx.phaseName)
|
|
1896
|
-
}]
|
|
1897
|
-
}
|
|
1898
|
-
],
|
|
1899
|
-
PostToolUse: buildPostToolUseConfig(hooksDir, artifactIfPatterns),
|
|
1900
|
-
PermissionRequest: buildPermissionRequestConfig(hooksDir, isPlanPhase),
|
|
1901
|
-
Stop: [{
|
|
1902
|
-
hooks: [{
|
|
1903
|
-
type: "command",
|
|
1904
|
-
command: path4.join(hooksDir, "stop.sh"),
|
|
1905
|
-
timeout: 15
|
|
1906
|
-
}]
|
|
1907
|
-
}]
|
|
1908
|
-
};
|
|
1909
|
-
return config;
|
|
1910
|
-
}
|
|
1911
|
-
function buildPermissionRequestConfig(hooksDir, isPlanPhase) {
|
|
1912
|
-
const groups = [];
|
|
1913
|
-
if (isPlanPhase) {
|
|
1914
|
-
groups.push({
|
|
1915
|
-
matcher: "ExitPlanMode",
|
|
1916
|
-
hooks: [{
|
|
1917
|
-
type: "command",
|
|
1918
|
-
command: path4.join(hooksDir, "exit-plan-mode.sh"),
|
|
1919
|
-
timeout: 5
|
|
1920
|
-
}]
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
groups.push({
|
|
1924
|
-
matcher: "Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch|mcp__.*",
|
|
1925
|
-
hooks: [{
|
|
1926
|
-
type: "command",
|
|
1927
|
-
command: path4.join(hooksDir, "permission.sh"),
|
|
1928
|
-
timeout: 5
|
|
1929
|
-
}]
|
|
1930
|
-
});
|
|
1931
|
-
return groups;
|
|
1932
|
-
}
|
|
1933
|
-
function buildPostToolUseConfig(hooksDir, artifactIfPatterns) {
|
|
1934
|
-
const groups = [];
|
|
1935
|
-
if (artifactIfPatterns) {
|
|
1936
|
-
groups.push({
|
|
1937
|
-
matcher: "Write|Edit",
|
|
1938
|
-
hooks: [{
|
|
1939
|
-
type: "command",
|
|
1940
|
-
command: path4.join(hooksDir, "post-artifact.sh"),
|
|
1941
|
-
timeout: 10,
|
|
1942
|
-
if: artifactIfPatterns
|
|
1943
|
-
}]
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
groups.push({
|
|
1947
|
-
matcher: "Write|Edit",
|
|
1948
|
-
hooks: [{
|
|
1949
|
-
type: "command",
|
|
1950
|
-
command: path4.join(hooksDir, "post-tool-use.sh"),
|
|
1951
|
-
timeout: 10
|
|
1952
|
-
}]
|
|
1953
|
-
});
|
|
1954
|
-
return groups;
|
|
1955
|
-
}
|
|
1956
|
-
function buildArtifactIfPatterns(artifacts) {
|
|
1957
|
-
if (artifacts.length === 0) return void 0;
|
|
1958
|
-
return artifacts.flatMap((f) => [`Write(*${f})`, `Edit(*${f})`]).join("|");
|
|
1959
|
-
}
|
|
1960
|
-
function buildProtectIfClause(phaseName) {
|
|
1961
|
-
const alwaysProtected = [".env", ".env.*", "package-lock.json", "pnpm-lock.yaml"];
|
|
1962
|
-
const patterns = [...alwaysProtected];
|
|
1963
|
-
if (phaseName === "build") {
|
|
1964
|
-
patterns.push("01-plan.md");
|
|
1965
|
-
}
|
|
1966
|
-
if (phaseName === "verify") {
|
|
1967
|
-
patterns.push("01-plan.md");
|
|
1968
|
-
}
|
|
1969
|
-
const ifValue = patterns.flatMap((f) => [`Edit(*${f})`, `Write(*${f})`]).join("|");
|
|
1970
|
-
return { if: ifValue };
|
|
1971
|
-
}
|
|
1972
|
-
function buildSessionStartScript(eventsFile) {
|
|
1973
|
-
return `#!/bin/bash
|
|
1974
|
-
set -euo pipefail
|
|
1975
|
-
INPUT=$(cat)
|
|
1976
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
1977
|
-
printf '{"ts":"%s","event":"session_start","session_id":"%s"}\\n' \\
|
|
1978
|
-
"$(date -u +%FT%TZ)" "$SESSION_ID" >> ${quote(eventsFile)}
|
|
1979
|
-
exit 0
|
|
1980
|
-
`;
|
|
1981
|
-
}
|
|
1982
|
-
function buildCompactRestoreScript(eventsFile, contextFile) {
|
|
1983
|
-
return `#!/bin/bash
|
|
1984
|
-
set -euo pipefail
|
|
1985
|
-
|
|
1986
|
-
CONTEXT_FILE=${quote(contextFile)}
|
|
1987
|
-
if [ ! -f "$CONTEXT_FILE" ]; then
|
|
1988
|
-
exit 0
|
|
1989
|
-
fi
|
|
1990
|
-
|
|
1991
|
-
ISSUE_IID=$(jq -r '.issueIid // empty' < "$CONTEXT_FILE")
|
|
1992
|
-
ISSUE_TITLE=$(jq -r '.issueTitle // empty' < "$CONTEXT_FILE")
|
|
1993
|
-
ISSUE_DESC=$(jq -r '.issueDescription // empty' < "$CONTEXT_FILE")
|
|
1994
|
-
PHASE=$(jq -r '.phaseName // empty' < "$CONTEXT_FILE")
|
|
1995
|
-
PLAN_DIR=$(jq -r '.planDir // empty' < "$CONTEXT_FILE")
|
|
1996
|
-
ARTIFACTS=$(jq -r '.expectedArtifacts | join(", ") // empty' < "$CONTEXT_FILE")
|
|
1997
|
-
|
|
1998
|
-
READY=""
|
|
1999
|
-
MISSING=""
|
|
2000
|
-
for f in $(jq -r '.expectedArtifacts[]' < "$CONTEXT_FILE" 2>/dev/null); do
|
|
2001
|
-
FPATH="$PLAN_DIR/$f"
|
|
2002
|
-
if [ -f "$FPATH" ] && [ "$(wc -c < "$FPATH")" -ge 50 ]; then
|
|
2003
|
-
READY="$READY $f"
|
|
2004
|
-
else
|
|
2005
|
-
MISSING="$MISSING $f"
|
|
2006
|
-
fi
|
|
2007
|
-
done
|
|
2008
|
-
READY=$(echo "$READY" | xargs)
|
|
2009
|
-
MISSING=$(echo "$MISSING" | xargs)
|
|
2010
|
-
|
|
2011
|
-
printf '{"ts":"%s","event":"compact_restore"}\\n' "$(date -u +%FT%TZ)" >> ${quote(eventsFile)}
|
|
2012
|
-
|
|
2013
|
-
cat <<CONTEXT
|
|
2014
|
-
[\u4E0A\u4E0B\u6587\u6062\u590D \u2014 compaction \u540E\u81EA\u52A8\u6CE8\u5165]
|
|
2015
|
-
Issue #$ISSUE_IID: $ISSUE_TITLE
|
|
2016
|
-
\u5F53\u524D\u9636\u6BB5: $PHASE
|
|
2017
|
-
\u9884\u671F\u4EA7\u7269: $ARTIFACTS
|
|
2018
|
-
\u5DF2\u5C31\u7EEA: \${READY:-\u65E0}
|
|
2019
|
-
\u672A\u5B8C\u6210: \${MISSING:-\u65E0}
|
|
2020
|
-
|
|
2021
|
-
\u9700\u6C42\u63CF\u8FF0:
|
|
2022
|
-
$ISSUE_DESC
|
|
2023
|
-
CONTEXT
|
|
2024
|
-
exit 0
|
|
2025
|
-
`;
|
|
2026
|
-
}
|
|
2027
|
-
function buildPostToolUseScript(eventsFile, manifestFile, expected) {
|
|
2028
|
-
return `#!/bin/bash
|
|
2029
|
-
set -euo pipefail
|
|
2030
|
-
INPUT=$(cat)
|
|
2031
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
2032
|
-
[ -z "$FILE_PATH" ] && exit 0
|
|
2033
|
-
|
|
2034
|
-
EXPECTED=${quote(expected)}
|
|
2035
|
-
BASENAME=$(basename "$FILE_PATH")
|
|
2036
|
-
|
|
2037
|
-
if echo "$EXPECTED" | tr ',' '\\n' | grep -qx "$BASENAME"; then
|
|
2038
|
-
BYTES=$(wc -c < "$FILE_PATH" 2>/dev/null || echo 0)
|
|
2039
|
-
printf '{"ts":"%s","event":"artifact_write","file":"%s","path":"%s","bytes":%s}\\n' \\
|
|
2040
|
-
"$(date -u +%FT%TZ)" "$BASENAME" "$FILE_PATH" "$BYTES" >> ${quote(manifestFile)}
|
|
2041
|
-
fi
|
|
2042
|
-
|
|
2043
|
-
printf '{"ts":"%s","event":"artifact_write","file":"%s","path":"%s","bytes":0}\\n' \\
|
|
2044
|
-
"$(date -u +%FT%TZ)" "$BASENAME" "$FILE_PATH" >> ${quote(eventsFile)}
|
|
2045
|
-
exit 0
|
|
2046
|
-
`;
|
|
2047
|
-
}
|
|
2048
|
-
function buildPostArtifactScript(manifestFile, expected) {
|
|
2049
|
-
return `#!/bin/bash
|
|
2050
|
-
set -euo pipefail
|
|
2051
|
-
INPUT=$(cat)
|
|
2052
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
2053
|
-
[ -z "$FILE_PATH" ] && exit 0
|
|
2054
|
-
|
|
2055
|
-
EXPECTED=${quote(expected)}
|
|
2056
|
-
BASENAME=$(basename "$FILE_PATH")
|
|
2057
|
-
|
|
2058
|
-
if echo "$EXPECTED" | tr ',' '\\n' | grep -qx "$BASENAME"; then
|
|
2059
|
-
BYTES=$(wc -c < "$FILE_PATH" 2>/dev/null || echo 0)
|
|
2060
|
-
printf '{"ts":"%s","event":"write","file":"%s","path":"%s","bytes":%s}\\n' \\
|
|
2061
|
-
"$(date -u +%FT%TZ)" "$BASENAME" "$FILE_PATH" "$BYTES" >> ${quote(manifestFile)}
|
|
2062
|
-
fi
|
|
2063
|
-
exit 0
|
|
2064
|
-
`;
|
|
2065
|
-
}
|
|
2066
|
-
function buildExitPlanModeScript(eventsFile) {
|
|
2067
|
-
return `#!/bin/bash
|
|
2068
|
-
set -euo pipefail
|
|
2069
|
-
INPUT=$(cat)
|
|
2070
|
-
|
|
2071
|
-
printf '{"ts":"%s","event":"exit_plan_mode"}\\n' "$(date -u +%FT%TZ)" >> ${quote(eventsFile)}
|
|
2072
|
-
|
|
2073
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}'
|
|
2074
|
-
exit 0
|
|
2075
|
-
`;
|
|
2076
|
-
}
|
|
2077
|
-
function buildPermissionScript(eventsFile) {
|
|
2078
|
-
return `#!/bin/bash
|
|
2079
|
-
set -euo pipefail
|
|
2080
|
-
INPUT=$(cat)
|
|
2081
|
-
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
2082
|
-
printf '{"ts":"%s","event":"permission_request","tool":"%s"}\\n' \\
|
|
2083
|
-
"$(date -u +%FT%TZ)" "$TOOL" >> ${quote(eventsFile)}
|
|
2084
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}'
|
|
2085
|
-
exit 0
|
|
2086
|
-
`;
|
|
2087
|
-
}
|
|
2088
|
-
function buildProtectFilesScript(eventsFile, phaseName, planDir) {
|
|
2089
|
-
return `#!/bin/bash
|
|
2090
|
-
set -euo pipefail
|
|
2091
|
-
INPUT=$(cat)
|
|
2092
|
-
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
2093
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
2094
|
-
[ -z "$FILE_PATH" ] && exit 0
|
|
2095
|
-
|
|
2096
|
-
BASENAME=$(basename "$FILE_PATH")
|
|
2097
|
-
PHASE=${quote(phaseName ?? "")}
|
|
2098
|
-
|
|
2099
|
-
blocked_reason() {
|
|
2100
|
-
printf '{"ts":"%s","event":"protect_blocked","tool":"%s","file":"%s"}\\n' \\
|
|
2101
|
-
"$(date -u +%FT%TZ)" "$TOOL" "$BASENAME" >> ${quote(eventsFile)}
|
|
2102
|
-
echo "$1" >&2
|
|
2103
|
-
exit 2
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
case "$BASENAME" in
|
|
2107
|
-
.env|.env.*)
|
|
2108
|
-
blocked_reason "\u7981\u6B62\u4FEE\u6539\u73AF\u5883\u914D\u7F6E\u6587\u4EF6 $BASENAME\uFF0C\u8BF7\u901A\u8FC7 .env.example \u6216\u6587\u6863\u8BF4\u660E\u914D\u7F6E\u53D8\u66F4\u3002"
|
|
2109
|
-
;;
|
|
2110
|
-
package-lock.json|pnpm-lock.yaml)
|
|
2111
|
-
blocked_reason "\u7981\u6B62\u76F4\u63A5\u4FEE\u6539\u9501\u6587\u4EF6 $BASENAME\uFF0C\u8BF7\u901A\u8FC7 npm install / pnpm install \u66F4\u65B0\u4F9D\u8D56\u3002"
|
|
2112
|
-
;;
|
|
2113
|
-
esac
|
|
2114
|
-
|
|
2115
|
-
if [ "$PHASE" = "build" ] || [ "$PHASE" = "verify" ]; then
|
|
2116
|
-
case "$BASENAME" in
|
|
2117
|
-
01-plan.md)
|
|
2118
|
-
blocked_reason "\u5728 $PHASE \u9636\u6BB5\u7981\u6B62\u4FEE\u6539\u89C4\u5212\u6587\u6863 01-plan.md\uFF0C\u8BE5\u6587\u4EF6\u5728 plan \u9636\u6BB5\u5DF2\u786E\u5B9A\u3002"
|
|
2119
|
-
;;
|
|
2120
|
-
esac
|
|
2121
|
-
fi
|
|
2122
|
-
|
|
2123
|
-
exit 0
|
|
2124
|
-
`;
|
|
2125
|
-
}
|
|
2126
|
-
function buildStopScript(eventsFile, planDir, phaseExpected) {
|
|
2127
|
-
return `#!/bin/bash
|
|
2128
|
-
set -euo pipefail
|
|
2129
|
-
INPUT=$(cat)
|
|
2130
|
-
STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
2131
|
-
|
|
2132
|
-
PLAN_DIR=${quote(planDir)}
|
|
2133
|
-
MIN_BYTES=50
|
|
2134
|
-
PHASE_EXPECTED=${quote(phaseExpected)}
|
|
2135
|
-
|
|
2136
|
-
MISSING=""
|
|
2137
|
-
READY=""
|
|
2138
|
-
for f in $(echo "$PHASE_EXPECTED" | tr ',' ' '); do
|
|
2139
|
-
[ -z "$f" ] && continue
|
|
2140
|
-
FPATH="$PLAN_DIR/$f"
|
|
2141
|
-
if [ -f "$FPATH" ] && [ "$(wc -c < "$FPATH")" -ge "$MIN_BYTES" ]; then
|
|
2142
|
-
BYTES=$(wc -c < "$FPATH")
|
|
2143
|
-
READY="$READY $f(\${BYTES} bytes)"
|
|
2144
|
-
else
|
|
2145
|
-
MISSING="$MISSING $f"
|
|
2146
|
-
fi
|
|
2147
|
-
done
|
|
2148
|
-
|
|
2149
|
-
MISSING=$(echo "$MISSING" | xargs)
|
|
2150
|
-
READY=$(echo "$READY" | xargs)
|
|
2151
|
-
|
|
2152
|
-
if [ -n "$MISSING" ] && [ "$STOP_ACTIVE" != "true" ]; then
|
|
2153
|
-
printf '{"ts":"%s","event":"stop","blocked":true,"missing":"%s"}\\n' \\
|
|
2154
|
-
"$(date -u +%FT%TZ)" "$MISSING" >> ${quote(eventsFile)}
|
|
2155
|
-
|
|
2156
|
-
REASON="\u4EA7\u7269\u672A\u5C31\u7EEA: $MISSING\u3002\u8BF7\u5199\u5165 $PLAN_DIR/ \u4E0B\u7684\u5BF9\u5E94\u6587\u4EF6\u3002\u5DF2\u5C31\u7EEA: \${READY:-\u65E0}"
|
|
2157
|
-
|
|
2158
|
-
printf '{"decision":"block","reason":"%s"}' "$REASON"
|
|
2159
|
-
exit 0
|
|
2160
|
-
fi
|
|
2161
|
-
|
|
2162
|
-
printf '{"ts":"%s","event":"stop","blocked":false,"missing":"%s"}\\n' \\
|
|
2163
|
-
"$(date -u +%FT%TZ)" "\${MISSING:-none}" >> ${quote(eventsFile)}
|
|
2164
|
-
exit 0
|
|
2165
|
-
`;
|
|
2166
|
-
}
|
|
2167
|
-
function quote(s) {
|
|
2168
|
-
return `"${s.replace(/"/g, '\\"')}"`;
|
|
2169
|
-
}
|
|
2170
|
-
function readJsonl(filePath) {
|
|
2171
|
-
if (!fs3.existsSync(filePath)) return [];
|
|
2172
|
-
try {
|
|
2173
|
-
const content = fs3.readFileSync(filePath, "utf-8").trim();
|
|
2174
|
-
if (!content) return [];
|
|
2175
|
-
return content.split("\n").reduce((acc, line) => {
|
|
2176
|
-
const trimmed = line.trim();
|
|
2177
|
-
if (!trimmed) return acc;
|
|
2178
|
-
try {
|
|
2179
|
-
acc.push(JSON.parse(trimmed));
|
|
2180
|
-
} catch {
|
|
2181
|
-
logger7.debug("Skipping malformed JSONL line", { line: trimmed });
|
|
2182
|
-
}
|
|
2183
|
-
return acc;
|
|
2184
|
-
}, []);
|
|
2185
|
-
} catch {
|
|
2186
|
-
return [];
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
// src/hooks/HookEventWatcher.ts
|
|
2191
|
-
import fs4 from "fs";
|
|
2192
|
-
var logger8 = logger.child("HookEventWatcher");
|
|
2193
|
-
|
|
2194
1749
|
// src/phases/BasePhase.ts
|
|
2195
1750
|
var BasePhase = class _BasePhase {
|
|
2196
1751
|
static MIN_ARTIFACT_BYTES = 50;
|
|
@@ -2335,7 +1890,7 @@ ${t("basePhase.rulesSection", { rules })}`;
|
|
|
2335
1890
|
const resultFiles = this.getResultFiles(ctx);
|
|
2336
1891
|
const snapshotFilenames = resultFiles.map((f) => f.filename);
|
|
2337
1892
|
const artifactCheck = snapshotFilenames.length > 0 ? () => snapshotFilenames.every((fn) => this.plan.isArtifactReady(fn, _BasePhase.MIN_ARTIFACT_BYTES)) : void 0;
|
|
2338
|
-
const artifactPaths = snapshotFilenames.length > 0 ? snapshotFilenames.map((fn) =>
|
|
1893
|
+
const artifactPaths = snapshotFilenames.length > 0 ? snapshotFilenames.map((fn) => path4.join(this.plan.planDir, fn)) : void 0;
|
|
2339
1894
|
let capturedSessionId;
|
|
2340
1895
|
const result = await this.aiRunner.run({
|
|
2341
1896
|
prompt,
|
|
@@ -2432,14 +1987,14 @@ ${t("basePhase.rulesSection", { rules })}`;
|
|
|
2432
1987
|
const context = `${ctx.demand.title} ${ctx.demand.description} ${ctx.demand.supplement ? JSON.stringify(ctx.demand.supplement) : ""}`;
|
|
2433
1988
|
if (ctx.workspace && ctx.workspace.repos.length > 1) {
|
|
2434
1989
|
for (const repo of ctx.workspace.repos) {
|
|
2435
|
-
const rulesDir =
|
|
1990
|
+
const rulesDir = path4.join(repo.gitRootDir, ".cursor", "rules");
|
|
2436
1991
|
try {
|
|
2437
1992
|
await resolver.loadRules(rulesDir);
|
|
2438
1993
|
} catch {
|
|
2439
1994
|
}
|
|
2440
1995
|
}
|
|
2441
1996
|
} else {
|
|
2442
|
-
const rulesDir =
|
|
1997
|
+
const rulesDir = path4.join(this.plan.baseDir, ".cursor", "rules");
|
|
2443
1998
|
await resolver.loadRules(rulesDir);
|
|
2444
1999
|
}
|
|
2445
2000
|
const matched = resolver.matchRules(context);
|
|
@@ -2713,20 +2268,20 @@ var BuildPhase = class extends BasePhase {
|
|
|
2713
2268
|
};
|
|
2714
2269
|
|
|
2715
2270
|
// src/release/ReleaseDetectCache.ts
|
|
2716
|
-
import
|
|
2717
|
-
import
|
|
2271
|
+
import fs3 from "fs";
|
|
2272
|
+
import path5 from "path";
|
|
2718
2273
|
import { createHash } from "crypto";
|
|
2719
|
-
var
|
|
2274
|
+
var logger7 = logger.child("ReleaseDetectCache");
|
|
2720
2275
|
function hashProjectPath(projectPath) {
|
|
2721
2276
|
return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
|
|
2722
2277
|
}
|
|
2723
2278
|
var ReleaseDetectCache = class {
|
|
2724
2279
|
cacheDir;
|
|
2725
2280
|
constructor(dataDir) {
|
|
2726
|
-
this.cacheDir =
|
|
2281
|
+
this.cacheDir = path5.join(dataDir, "release-detect");
|
|
2727
2282
|
}
|
|
2728
2283
|
filePath(projectPath) {
|
|
2729
|
-
return
|
|
2284
|
+
return path5.join(this.cacheDir, `${hashProjectPath(projectPath)}.json`);
|
|
2730
2285
|
}
|
|
2731
2286
|
/**
|
|
2732
2287
|
* 读取缓存。返回 null 如果不存在、已过期或校验失败。
|
|
@@ -2734,21 +2289,21 @@ var ReleaseDetectCache = class {
|
|
|
2734
2289
|
get(projectPath, ttlMs) {
|
|
2735
2290
|
const fp = this.filePath(projectPath);
|
|
2736
2291
|
try {
|
|
2737
|
-
if (!
|
|
2738
|
-
const raw =
|
|
2292
|
+
if (!fs3.existsSync(fp)) return null;
|
|
2293
|
+
const raw = fs3.readFileSync(fp, "utf-8");
|
|
2739
2294
|
const data = JSON.parse(raw);
|
|
2740
2295
|
if (data.projectPath !== projectPath) {
|
|
2741
|
-
|
|
2296
|
+
logger7.warn("Cache projectPath mismatch, ignoring", { expected: projectPath, got: data.projectPath });
|
|
2742
2297
|
return null;
|
|
2743
2298
|
}
|
|
2744
2299
|
const age = Date.now() - new Date(data.detectedAt).getTime();
|
|
2745
2300
|
if (age > ttlMs) {
|
|
2746
|
-
|
|
2301
|
+
logger7.debug("Cache expired", { projectPath, ageMs: age, ttlMs });
|
|
2747
2302
|
return null;
|
|
2748
2303
|
}
|
|
2749
2304
|
return data;
|
|
2750
2305
|
} catch (err) {
|
|
2751
|
-
|
|
2306
|
+
logger7.warn("Failed to read release detect cache", { path: fp, error: err.message });
|
|
2752
2307
|
return null;
|
|
2753
2308
|
}
|
|
2754
2309
|
}
|
|
@@ -2758,13 +2313,13 @@ var ReleaseDetectCache = class {
|
|
|
2758
2313
|
set(result) {
|
|
2759
2314
|
const fp = this.filePath(result.projectPath);
|
|
2760
2315
|
try {
|
|
2761
|
-
if (!
|
|
2762
|
-
|
|
2316
|
+
if (!fs3.existsSync(this.cacheDir)) {
|
|
2317
|
+
fs3.mkdirSync(this.cacheDir, { recursive: true });
|
|
2763
2318
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2319
|
+
fs3.writeFileSync(fp, JSON.stringify(result, null, 2), "utf-8");
|
|
2320
|
+
logger7.debug("Release detect cache written", { projectPath: result.projectPath, path: fp });
|
|
2766
2321
|
} catch (err) {
|
|
2767
|
-
|
|
2322
|
+
logger7.warn("Failed to write release detect cache", { path: fp, error: err.message });
|
|
2768
2323
|
}
|
|
2769
2324
|
}
|
|
2770
2325
|
/**
|
|
@@ -2773,14 +2328,14 @@ var ReleaseDetectCache = class {
|
|
|
2773
2328
|
invalidate(projectPath) {
|
|
2774
2329
|
const fp = this.filePath(projectPath);
|
|
2775
2330
|
try {
|
|
2776
|
-
if (
|
|
2777
|
-
|
|
2778
|
-
|
|
2331
|
+
if (fs3.existsSync(fp)) {
|
|
2332
|
+
fs3.unlinkSync(fp);
|
|
2333
|
+
logger7.info("Release detect cache invalidated", { projectPath });
|
|
2779
2334
|
return true;
|
|
2780
2335
|
}
|
|
2781
2336
|
return false;
|
|
2782
2337
|
} catch (err) {
|
|
2783
|
-
|
|
2338
|
+
logger7.warn("Failed to invalidate release detect cache", { path: fp, error: err.message });
|
|
2784
2339
|
return false;
|
|
2785
2340
|
}
|
|
2786
2341
|
}
|
|
@@ -3231,9 +2786,9 @@ function createLifecycleManager(def) {
|
|
|
3231
2786
|
|
|
3232
2787
|
// src/workspace/WorkspaceConfig.ts
|
|
3233
2788
|
import { z } from "zod";
|
|
3234
|
-
import
|
|
3235
|
-
import
|
|
3236
|
-
var
|
|
2789
|
+
import fs4 from "fs";
|
|
2790
|
+
import path6 from "path";
|
|
2791
|
+
var logger8 = logger.child("WorkspaceConfig");
|
|
3237
2792
|
var repoConfigSchema = z.object({
|
|
3238
2793
|
name: z.string().min(1, "Repo name is required"),
|
|
3239
2794
|
projectPath: z.string().min(1, "Gongfeng project path is required"),
|
|
@@ -3249,29 +2804,29 @@ var workspaceConfigSchema = z.object({
|
|
|
3249
2804
|
});
|
|
3250
2805
|
function loadWorkspaceConfig(configPath) {
|
|
3251
2806
|
if (!configPath) return null;
|
|
3252
|
-
if (!
|
|
3253
|
-
|
|
2807
|
+
if (!fs4.existsSync(configPath)) {
|
|
2808
|
+
logger8.warn("Workspace config file not found, falling back to single-repo mode", {
|
|
3254
2809
|
path: configPath
|
|
3255
2810
|
});
|
|
3256
2811
|
return null;
|
|
3257
2812
|
}
|
|
3258
2813
|
try {
|
|
3259
|
-
const raw =
|
|
2814
|
+
const raw = fs4.readFileSync(configPath, "utf-8");
|
|
3260
2815
|
const json = JSON.parse(raw);
|
|
3261
2816
|
const result = workspaceConfigSchema.safeParse(json);
|
|
3262
2817
|
if (!result.success) {
|
|
3263
2818
|
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
3264
|
-
|
|
2819
|
+
logger8.error(`Workspace config validation failed:
|
|
3265
2820
|
${issues}`);
|
|
3266
2821
|
return null;
|
|
3267
2822
|
}
|
|
3268
|
-
|
|
2823
|
+
logger8.info("Workspace config loaded", {
|
|
3269
2824
|
primary: result.data.primary.name,
|
|
3270
2825
|
associates: result.data.associates.map((a) => a.name)
|
|
3271
2826
|
});
|
|
3272
2827
|
return result.data;
|
|
3273
2828
|
} catch (err) {
|
|
3274
|
-
|
|
2829
|
+
logger8.error("Failed to parse workspace config", {
|
|
3275
2830
|
path: configPath,
|
|
3276
2831
|
error: err.message
|
|
3277
2832
|
});
|
|
@@ -3297,14 +2852,14 @@ function isMultiRepo(ws) {
|
|
|
3297
2852
|
}
|
|
3298
2853
|
function persistWorkspaceConfig(ws, filePath) {
|
|
3299
2854
|
try {
|
|
3300
|
-
const dir =
|
|
3301
|
-
if (!
|
|
3302
|
-
|
|
2855
|
+
const dir = path6.dirname(filePath);
|
|
2856
|
+
if (!fs4.existsSync(dir)) {
|
|
2857
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
3303
2858
|
}
|
|
3304
|
-
|
|
3305
|
-
|
|
2859
|
+
fs4.writeFileSync(filePath, JSON.stringify(ws, null, 2) + "\n", "utf-8");
|
|
2860
|
+
logger8.info("Workspace config auto-generated from .env", { path: filePath });
|
|
3306
2861
|
} catch (err) {
|
|
3307
|
-
|
|
2862
|
+
logger8.warn("Failed to persist workspace config", {
|
|
3308
2863
|
path: filePath,
|
|
3309
2864
|
error: err.message
|
|
3310
2865
|
});
|
|
@@ -3312,12 +2867,12 @@ function persistWorkspaceConfig(ws, filePath) {
|
|
|
3312
2867
|
}
|
|
3313
2868
|
|
|
3314
2869
|
// src/workspace/WorkspaceManager.ts
|
|
3315
|
-
import
|
|
3316
|
-
import
|
|
2870
|
+
import path7 from "path";
|
|
2871
|
+
import fs5 from "fs/promises";
|
|
3317
2872
|
import { execFile } from "child_process";
|
|
3318
2873
|
import { promisify } from "util";
|
|
3319
2874
|
var execFileAsync = promisify(execFile);
|
|
3320
|
-
var
|
|
2875
|
+
var logger9 = logger.child("WorkspaceManager");
|
|
3321
2876
|
var WorkspaceManager = class {
|
|
3322
2877
|
wsConfig;
|
|
3323
2878
|
worktreeBaseDir;
|
|
@@ -3346,7 +2901,7 @@ var WorkspaceManager = class {
|
|
|
3346
2901
|
*/
|
|
3347
2902
|
async prepareWorkspace(issueIid, branchName, globalBaseBranch, globalBranchPrefix) {
|
|
3348
2903
|
const wsRoot = this.getWorkspaceRoot(issueIid);
|
|
3349
|
-
await
|
|
2904
|
+
await fs5.mkdir(wsRoot, { recursive: true });
|
|
3350
2905
|
const primaryCtx = await this.preparePrimaryRepo(
|
|
3351
2906
|
issueIid,
|
|
3352
2907
|
branchName,
|
|
@@ -3365,7 +2920,7 @@ var WorkspaceManager = class {
|
|
|
3365
2920
|
);
|
|
3366
2921
|
associateCtxs.push(ctx);
|
|
3367
2922
|
}
|
|
3368
|
-
|
|
2923
|
+
logger9.info("Workspace prepared", {
|
|
3369
2924
|
issueIid,
|
|
3370
2925
|
wsRoot,
|
|
3371
2926
|
repos: [primaryCtx.name, ...associateCtxs.map((a) => a.name)]
|
|
@@ -3390,7 +2945,7 @@ var WorkspaceManager = class {
|
|
|
3390
2945
|
await git.commit(message);
|
|
3391
2946
|
await git.push(wsCtx.branchName);
|
|
3392
2947
|
committed.push(repo.name);
|
|
3393
|
-
|
|
2948
|
+
logger9.info("Committed and pushed changes", {
|
|
3394
2949
|
repo: repo.name,
|
|
3395
2950
|
branch: wsCtx.branchName
|
|
3396
2951
|
});
|
|
@@ -3404,19 +2959,19 @@ var WorkspaceManager = class {
|
|
|
3404
2959
|
async cleanupWorkspace(wsCtx) {
|
|
3405
2960
|
try {
|
|
3406
2961
|
await this.mainGit.worktreeRemove(wsCtx.primary.gitRootDir, true);
|
|
3407
|
-
|
|
2962
|
+
logger9.info("Primary worktree removed", { dir: wsCtx.primary.gitRootDir });
|
|
3408
2963
|
} catch (err) {
|
|
3409
|
-
|
|
2964
|
+
logger9.warn("Failed to remove primary worktree", {
|
|
3410
2965
|
dir: wsCtx.primary.gitRootDir,
|
|
3411
2966
|
error: err.message
|
|
3412
2967
|
});
|
|
3413
2968
|
}
|
|
3414
2969
|
for (const assoc of wsCtx.associates) {
|
|
3415
2970
|
try {
|
|
3416
|
-
await
|
|
3417
|
-
|
|
2971
|
+
await fs5.rm(assoc.gitRootDir, { recursive: true, force: true });
|
|
2972
|
+
logger9.info("Associate repo dir removed", { name: assoc.name, dir: assoc.gitRootDir });
|
|
3418
2973
|
} catch (err) {
|
|
3419
|
-
|
|
2974
|
+
logger9.warn("Failed to remove associate repo dir", {
|
|
3420
2975
|
name: assoc.name,
|
|
3421
2976
|
dir: assoc.gitRootDir,
|
|
3422
2977
|
error: err.message
|
|
@@ -3424,9 +2979,9 @@ var WorkspaceManager = class {
|
|
|
3424
2979
|
}
|
|
3425
2980
|
}
|
|
3426
2981
|
try {
|
|
3427
|
-
const entries = await
|
|
2982
|
+
const entries = await fs5.readdir(wsCtx.workspaceRoot);
|
|
3428
2983
|
if (entries.length === 0) {
|
|
3429
|
-
await
|
|
2984
|
+
await fs5.rmdir(wsCtx.workspaceRoot);
|
|
3430
2985
|
}
|
|
3431
2986
|
} catch {
|
|
3432
2987
|
}
|
|
@@ -3438,13 +2993,13 @@ var WorkspaceManager = class {
|
|
|
3438
2993
|
const wsRoot = this.getWorkspaceRoot(issueIid);
|
|
3439
2994
|
const primary = this.wsConfig.primary;
|
|
3440
2995
|
const defaultPrefix = globalBranchPrefix ?? primary.branchPrefix ?? "feat/issue";
|
|
3441
|
-
const primaryDir =
|
|
2996
|
+
const primaryDir = path7.join(wsRoot, primary.name);
|
|
3442
2997
|
const repos = [{
|
|
3443
2998
|
name: primary.name,
|
|
3444
2999
|
projectPath: primary.projectPath,
|
|
3445
3000
|
role: primary.role ?? "",
|
|
3446
3001
|
gitRootDir: primaryDir,
|
|
3447
|
-
workDir:
|
|
3002
|
+
workDir: path7.join(primaryDir, primary.projectSubDir ?? ""),
|
|
3448
3003
|
baseBranch: primary.baseBranch ?? globalBaseBranch,
|
|
3449
3004
|
branchPrefix: primary.branchPrefix ?? defaultPrefix,
|
|
3450
3005
|
isPrimary: true
|
|
@@ -3454,8 +3009,8 @@ var WorkspaceManager = class {
|
|
|
3454
3009
|
name: assoc.name,
|
|
3455
3010
|
projectPath: assoc.projectPath,
|
|
3456
3011
|
role: assoc.role ?? "",
|
|
3457
|
-
gitRootDir:
|
|
3458
|
-
workDir:
|
|
3012
|
+
gitRootDir: path7.join(wsRoot, assoc.name),
|
|
3013
|
+
workDir: path7.join(wsRoot, assoc.name, assoc.projectSubDir ?? ""),
|
|
3459
3014
|
baseBranch: assoc.baseBranch ?? globalBaseBranch,
|
|
3460
3015
|
branchPrefix: assoc.branchPrefix ?? defaultPrefix,
|
|
3461
3016
|
isPrimary: false
|
|
@@ -3464,12 +3019,12 @@ var WorkspaceManager = class {
|
|
|
3464
3019
|
return repos;
|
|
3465
3020
|
}
|
|
3466
3021
|
getWorkspaceRoot(issueIid) {
|
|
3467
|
-
return
|
|
3022
|
+
return path7.join(this.worktreeBaseDir, `issue-${issueIid}`);
|
|
3468
3023
|
}
|
|
3469
3024
|
// ── Internal helpers ──
|
|
3470
3025
|
async preparePrimaryRepo(issueIid, branchName, wsRoot, globalBaseBranch) {
|
|
3471
3026
|
const primary = this.wsConfig.primary;
|
|
3472
|
-
const repoDir =
|
|
3027
|
+
const repoDir = path7.join(wsRoot, primary.name);
|
|
3473
3028
|
const baseBranch = primary.baseBranch ?? globalBaseBranch;
|
|
3474
3029
|
await this.ensurePrimaryWorktree(repoDir, branchName, baseBranch);
|
|
3475
3030
|
return {
|
|
@@ -3477,18 +3032,18 @@ var WorkspaceManager = class {
|
|
|
3477
3032
|
projectPath: primary.projectPath,
|
|
3478
3033
|
role: primary.role ?? "",
|
|
3479
3034
|
gitRootDir: repoDir,
|
|
3480
|
-
workDir:
|
|
3035
|
+
workDir: path7.join(repoDir, primary.projectSubDir ?? ""),
|
|
3481
3036
|
baseBranch,
|
|
3482
3037
|
branchPrefix: primary.branchPrefix ?? "feat/issue",
|
|
3483
3038
|
isPrimary: true
|
|
3484
3039
|
};
|
|
3485
3040
|
}
|
|
3486
3041
|
async ensurePrimaryWorktree(repoDir, branchName, baseBranch) {
|
|
3487
|
-
const wsRoot =
|
|
3042
|
+
const wsRoot = path7.dirname(repoDir);
|
|
3488
3043
|
if (wsRoot !== repoDir) {
|
|
3489
3044
|
try {
|
|
3490
|
-
await
|
|
3491
|
-
|
|
3045
|
+
await fs5.access(path7.join(wsRoot, ".git"));
|
|
3046
|
+
logger9.info("Migrating legacy worktree to primary subdir", { from: wsRoot, to: repoDir });
|
|
3492
3047
|
await this.mainGit.worktreeRemove(wsRoot, true);
|
|
3493
3048
|
await this.mainGit.worktreePrune();
|
|
3494
3049
|
await this.cleanStaleDir(wsRoot);
|
|
@@ -3498,11 +3053,11 @@ var WorkspaceManager = class {
|
|
|
3498
3053
|
const worktrees = await this.mainGit.worktreeList();
|
|
3499
3054
|
if (worktrees.includes(repoDir)) {
|
|
3500
3055
|
try {
|
|
3501
|
-
await
|
|
3502
|
-
|
|
3056
|
+
await fs5.access(path7.join(repoDir, ".git"));
|
|
3057
|
+
logger9.info("Reusing existing primary worktree", { dir: repoDir });
|
|
3503
3058
|
return;
|
|
3504
3059
|
} catch {
|
|
3505
|
-
|
|
3060
|
+
logger9.warn("Primary worktree registered but .git missing, recreating", { dir: repoDir });
|
|
3506
3061
|
await this.mainGit.worktreeRemove(repoDir, true);
|
|
3507
3062
|
await this.mainGit.worktreePrune();
|
|
3508
3063
|
}
|
|
@@ -3521,19 +3076,19 @@ var WorkspaceManager = class {
|
|
|
3521
3076
|
await this.mainGit.worktreeAdd(repoDir, branchName, `origin/${baseBranch}`);
|
|
3522
3077
|
}
|
|
3523
3078
|
async prepareAssociateRepo(assoc, _issueIid, branchName, wsRoot, globalBaseBranch, globalBranchPrefix) {
|
|
3524
|
-
const repoDir =
|
|
3079
|
+
const repoDir = path7.join(wsRoot, assoc.name);
|
|
3525
3080
|
const baseBranch = assoc.baseBranch ?? globalBaseBranch;
|
|
3526
3081
|
const cloneUrl = `${this.gongfengApiUrl}/${assoc.projectPath}.git`;
|
|
3527
|
-
const gitDirExists = await this.dirExists(
|
|
3082
|
+
const gitDirExists = await this.dirExists(path7.join(repoDir, ".git"));
|
|
3528
3083
|
if (!gitDirExists) {
|
|
3529
3084
|
await this.cleanStaleDir(repoDir);
|
|
3530
|
-
|
|
3085
|
+
logger9.info("Cloning associate repo", { name: assoc.name, url: cloneUrl });
|
|
3531
3086
|
await execFileAsync("git", ["clone", "--depth", "50", cloneUrl, repoDir], {
|
|
3532
3087
|
timeout: 3e5,
|
|
3533
3088
|
maxBuffer: 10 * 1024 * 1024
|
|
3534
3089
|
});
|
|
3535
3090
|
} else {
|
|
3536
|
-
|
|
3091
|
+
logger9.info("Reusing existing associate clone", { name: assoc.name, dir: repoDir });
|
|
3537
3092
|
}
|
|
3538
3093
|
const assocGit = new GitOperations(repoDir);
|
|
3539
3094
|
await assocGit.fetch();
|
|
@@ -3556,7 +3111,7 @@ var WorkspaceManager = class {
|
|
|
3556
3111
|
projectPath: assoc.projectPath,
|
|
3557
3112
|
role: assoc.role ?? "",
|
|
3558
3113
|
gitRootDir: repoDir,
|
|
3559
|
-
workDir:
|
|
3114
|
+
workDir: path7.join(repoDir, assoc.projectSubDir ?? ""),
|
|
3560
3115
|
baseBranch,
|
|
3561
3116
|
branchPrefix: assoc.branchPrefix ?? globalBranchPrefix,
|
|
3562
3117
|
isPrimary: false
|
|
@@ -3564,13 +3119,13 @@ var WorkspaceManager = class {
|
|
|
3564
3119
|
}
|
|
3565
3120
|
async cleanStaleDir(dir) {
|
|
3566
3121
|
if (await this.dirExists(dir)) {
|
|
3567
|
-
|
|
3568
|
-
await
|
|
3122
|
+
logger9.warn("Removing stale directory", { dir });
|
|
3123
|
+
await fs5.rm(dir, { recursive: true, force: true });
|
|
3569
3124
|
}
|
|
3570
3125
|
}
|
|
3571
3126
|
async dirExists(dir) {
|
|
3572
3127
|
try {
|
|
3573
|
-
await
|
|
3128
|
+
await fs5.access(dir);
|
|
3574
3129
|
return true;
|
|
3575
3130
|
} catch {
|
|
3576
3131
|
return false;
|
|
@@ -3579,8 +3134,8 @@ var WorkspaceManager = class {
|
|
|
3579
3134
|
};
|
|
3580
3135
|
|
|
3581
3136
|
// src/orchestrator/PipelineOrchestrator.ts
|
|
3582
|
-
import
|
|
3583
|
-
import
|
|
3137
|
+
import path11 from "path";
|
|
3138
|
+
import fs9 from "fs/promises";
|
|
3584
3139
|
import fsSync from "fs";
|
|
3585
3140
|
import { execFile as execFile2 } from "child_process";
|
|
3586
3141
|
import { promisify as promisify2 } from "util";
|
|
@@ -3613,8 +3168,8 @@ function mapSupplement(s) {
|
|
|
3613
3168
|
}
|
|
3614
3169
|
|
|
3615
3170
|
// src/utils/MergeRequestHelper.ts
|
|
3616
|
-
import
|
|
3617
|
-
import
|
|
3171
|
+
import fs6 from "fs";
|
|
3172
|
+
import path8 from "path";
|
|
3618
3173
|
var TAPD_PATTERNS = [
|
|
3619
3174
|
/--story=(\d+)/i,
|
|
3620
3175
|
/--bug=(\d+)/i,
|
|
@@ -3658,9 +3213,9 @@ function generateMRDescription(options) {
|
|
|
3658
3213
|
];
|
|
3659
3214
|
const planSections = [];
|
|
3660
3215
|
for (const { filename, label } of summaryFiles) {
|
|
3661
|
-
const filePath =
|
|
3662
|
-
if (
|
|
3663
|
-
const content =
|
|
3216
|
+
const filePath = path8.join(planDir, ".claude-plan", `issue-${issueIid}`, filename);
|
|
3217
|
+
if (fs6.existsSync(filePath)) {
|
|
3218
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
3664
3219
|
const summary = extractSummary(content);
|
|
3665
3220
|
if (summary) {
|
|
3666
3221
|
planSections.push(`### ${label}
|
|
@@ -3684,7 +3239,7 @@ function extractSummary(content, maxLines = 20) {
|
|
|
3684
3239
|
|
|
3685
3240
|
// src/deploy/PortAllocator.ts
|
|
3686
3241
|
import net from "net";
|
|
3687
|
-
var
|
|
3242
|
+
var logger10 = logger.child("PortAllocator");
|
|
3688
3243
|
var DEFAULT_OPTIONS = {
|
|
3689
3244
|
backendPortBase: 4e3,
|
|
3690
3245
|
frontendPortBase: 9e3,
|
|
@@ -3709,7 +3264,7 @@ var PortAllocator = class {
|
|
|
3709
3264
|
async allocate(issueIid) {
|
|
3710
3265
|
const existing = this.allocated.get(issueIid);
|
|
3711
3266
|
if (existing) {
|
|
3712
|
-
|
|
3267
|
+
logger10.info("Returning already allocated ports", { issueIid, ports: existing });
|
|
3713
3268
|
return existing;
|
|
3714
3269
|
}
|
|
3715
3270
|
const usedBackend = new Set([...this.allocated.values()].map((p) => p.backendPort));
|
|
@@ -3727,10 +3282,10 @@ var PortAllocator = class {
|
|
|
3727
3282
|
if (beOk && feOk) {
|
|
3728
3283
|
const pair = { backendPort, frontendPort };
|
|
3729
3284
|
this.allocated.set(issueIid, pair);
|
|
3730
|
-
|
|
3285
|
+
logger10.info("Ports allocated", { issueIid, ...pair });
|
|
3731
3286
|
return pair;
|
|
3732
3287
|
}
|
|
3733
|
-
|
|
3288
|
+
logger10.debug("Port pair unavailable, trying next", {
|
|
3734
3289
|
backendPort,
|
|
3735
3290
|
frontendPort,
|
|
3736
3291
|
beOk,
|
|
@@ -3745,7 +3300,7 @@ var PortAllocator = class {
|
|
|
3745
3300
|
const pair = this.allocated.get(issueIid);
|
|
3746
3301
|
if (pair) {
|
|
3747
3302
|
this.allocated.delete(issueIid);
|
|
3748
|
-
|
|
3303
|
+
logger10.info("Ports released", { issueIid, ...pair });
|
|
3749
3304
|
}
|
|
3750
3305
|
}
|
|
3751
3306
|
getPortsForIssue(issueIid) {
|
|
@@ -3756,15 +3311,15 @@ var PortAllocator = class {
|
|
|
3756
3311
|
}
|
|
3757
3312
|
restore(issueIid, ports) {
|
|
3758
3313
|
this.allocated.set(issueIid, ports);
|
|
3759
|
-
|
|
3314
|
+
logger10.info("Ports restored from persistence", { issueIid, ...ports });
|
|
3760
3315
|
}
|
|
3761
3316
|
};
|
|
3762
3317
|
|
|
3763
3318
|
// src/deploy/DevServerManager.ts
|
|
3764
3319
|
import { spawn } from "child_process";
|
|
3765
|
-
import
|
|
3766
|
-
import
|
|
3767
|
-
var
|
|
3320
|
+
import fs7 from "fs";
|
|
3321
|
+
import path9 from "path";
|
|
3322
|
+
var logger11 = logger.child("DevServerManager");
|
|
3768
3323
|
var DEFAULT_OPTIONS2 = {};
|
|
3769
3324
|
var DevServerManager = class {
|
|
3770
3325
|
servers = /* @__PURE__ */ new Map();
|
|
@@ -3772,25 +3327,25 @@ var DevServerManager = class {
|
|
|
3772
3327
|
logDir;
|
|
3773
3328
|
constructor(options) {
|
|
3774
3329
|
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
3775
|
-
this.logDir =
|
|
3776
|
-
if (!
|
|
3777
|
-
|
|
3330
|
+
this.logDir = path9.join(resolveDataDir(), "preview-logs");
|
|
3331
|
+
if (!fs7.existsSync(this.logDir)) {
|
|
3332
|
+
fs7.mkdirSync(this.logDir, { recursive: true });
|
|
3778
3333
|
}
|
|
3779
3334
|
}
|
|
3780
3335
|
getLogPath(issueIid, type) {
|
|
3781
|
-
const filePath =
|
|
3782
|
-
return
|
|
3336
|
+
const filePath = path9.join(this.logDir, `${issueIid}-${type}.log`);
|
|
3337
|
+
return fs7.existsSync(filePath) ? filePath : null;
|
|
3783
3338
|
}
|
|
3784
3339
|
async startServers(wtCtx, ports) {
|
|
3785
3340
|
if (this.servers.has(wtCtx.issueIid)) {
|
|
3786
|
-
|
|
3341
|
+
logger11.info("Servers already running for issue", { issueIid: wtCtx.issueIid });
|
|
3787
3342
|
return;
|
|
3788
3343
|
}
|
|
3789
|
-
|
|
3790
|
-
const backendLogPath =
|
|
3791
|
-
const frontendLogPath =
|
|
3792
|
-
const backendLog =
|
|
3793
|
-
const frontendLog =
|
|
3344
|
+
logger11.info("Starting dev servers", { issueIid: wtCtx.issueIid, ...ports });
|
|
3345
|
+
const backendLogPath = path9.join(this.logDir, `${wtCtx.issueIid}-backend.log`);
|
|
3346
|
+
const frontendLogPath = path9.join(this.logDir, `${wtCtx.issueIid}-frontend.log`);
|
|
3347
|
+
const backendLog = fs7.createWriteStream(backendLogPath, { flags: "a" });
|
|
3348
|
+
const frontendLog = fs7.createWriteStream(frontendLogPath, { flags: "a" });
|
|
3794
3349
|
const tsLine = (stream, data) => `[${(/* @__PURE__ */ new Date()).toISOString()}] [${stream}] ${data.toString().trimEnd()}
|
|
3795
3350
|
`;
|
|
3796
3351
|
const backendEnv = {
|
|
@@ -3814,9 +3369,9 @@ var DevServerManager = class {
|
|
|
3814
3369
|
backendLog.write(tsLine("stderr", data));
|
|
3815
3370
|
});
|
|
3816
3371
|
backend.on("exit", (code) => {
|
|
3817
|
-
|
|
3372
|
+
logger11.info("Backend process exited", { issueIid: wtCtx.issueIid, code });
|
|
3818
3373
|
});
|
|
3819
|
-
const frontendDir =
|
|
3374
|
+
const frontendDir = path9.join(wtCtx.workDir, "frontend");
|
|
3820
3375
|
const frontendEnv = {
|
|
3821
3376
|
...process.env,
|
|
3822
3377
|
BACKEND_PORT: String(ports.backendPort),
|
|
@@ -3838,7 +3393,7 @@ var DevServerManager = class {
|
|
|
3838
3393
|
frontendLog.write(tsLine("stderr", data));
|
|
3839
3394
|
});
|
|
3840
3395
|
frontend.on("exit", (code) => {
|
|
3841
|
-
|
|
3396
|
+
logger11.info("Frontend process exited", { issueIid: wtCtx.issueIid, code });
|
|
3842
3397
|
});
|
|
3843
3398
|
const serverSet = {
|
|
3844
3399
|
backend,
|
|
@@ -3850,14 +3405,14 @@ var DevServerManager = class {
|
|
|
3850
3405
|
frontendLog
|
|
3851
3406
|
};
|
|
3852
3407
|
this.servers.set(wtCtx.issueIid, serverSet);
|
|
3853
|
-
|
|
3408
|
+
logger11.info("Dev servers spawned, waiting for startup", { issueIid: wtCtx.issueIid, ...ports });
|
|
3854
3409
|
await new Promise((r) => setTimeout(r, 1e4));
|
|
3855
|
-
|
|
3410
|
+
logger11.info("Dev servers startup grace period done", { issueIid: wtCtx.issueIid });
|
|
3856
3411
|
}
|
|
3857
3412
|
stopServers(issueIid) {
|
|
3858
3413
|
const set = this.servers.get(issueIid);
|
|
3859
3414
|
if (!set) return;
|
|
3860
|
-
|
|
3415
|
+
logger11.info("Stopping dev servers", { issueIid, ports: set.ports });
|
|
3861
3416
|
killProcess(set.backend, `backend #${issueIid}`);
|
|
3862
3417
|
killProcess(set.frontend, `frontend #${issueIid}`);
|
|
3863
3418
|
set.backendLog.end();
|
|
@@ -3894,7 +3449,7 @@ function killProcess(proc, label) {
|
|
|
3894
3449
|
}
|
|
3895
3450
|
setTimeout(() => {
|
|
3896
3451
|
if (!proc.killed && proc.exitCode === null) {
|
|
3897
|
-
|
|
3452
|
+
logger11.warn(`Force killing ${label}`);
|
|
3898
3453
|
try {
|
|
3899
3454
|
process.kill(-pid, "SIGKILL");
|
|
3900
3455
|
} catch {
|
|
@@ -3903,7 +3458,7 @@ function killProcess(proc, label) {
|
|
|
3903
3458
|
}
|
|
3904
3459
|
}, 5e3);
|
|
3905
3460
|
} catch (err) {
|
|
3906
|
-
|
|
3461
|
+
logger11.warn(`Failed to kill ${label}`, { error: err.message });
|
|
3907
3462
|
}
|
|
3908
3463
|
}
|
|
3909
3464
|
|
|
@@ -3922,13 +3477,13 @@ function isE2eEnabledForIssue(issueIid, tracker, cfg) {
|
|
|
3922
3477
|
}
|
|
3923
3478
|
|
|
3924
3479
|
// src/e2e/ScreenshotCollector.ts
|
|
3925
|
-
import
|
|
3926
|
-
import
|
|
3927
|
-
var
|
|
3480
|
+
import fs8 from "fs";
|
|
3481
|
+
import path10 from "path";
|
|
3482
|
+
var logger12 = logger.child("ScreenshotCollector");
|
|
3928
3483
|
var MAX_SCREENSHOTS = 20;
|
|
3929
3484
|
function walkDir(dir, files = []) {
|
|
3930
|
-
for (const entry of
|
|
3931
|
-
const full =
|
|
3485
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
3486
|
+
const full = path10.join(dir, entry.name);
|
|
3932
3487
|
if (entry.isDirectory()) {
|
|
3933
3488
|
walkDir(full, files);
|
|
3934
3489
|
} else if (entry.isFile() && entry.name.endsWith(".png")) {
|
|
@@ -3938,34 +3493,34 @@ function walkDir(dir, files = []) {
|
|
|
3938
3493
|
return files;
|
|
3939
3494
|
}
|
|
3940
3495
|
function collectScreenshots(workDir) {
|
|
3941
|
-
const testResultsDir =
|
|
3942
|
-
if (!
|
|
3943
|
-
|
|
3496
|
+
const testResultsDir = path10.join(workDir, "frontend", "test-results");
|
|
3497
|
+
if (!fs8.existsSync(testResultsDir)) {
|
|
3498
|
+
logger12.debug("test-results directory not found", { dir: testResultsDir });
|
|
3944
3499
|
return [];
|
|
3945
3500
|
}
|
|
3946
3501
|
const pngFiles = walkDir(testResultsDir);
|
|
3947
3502
|
if (pngFiles.length === 0) {
|
|
3948
|
-
|
|
3503
|
+
logger12.debug("No screenshots found");
|
|
3949
3504
|
return [];
|
|
3950
3505
|
}
|
|
3951
3506
|
const screenshots = pngFiles.map((filePath) => {
|
|
3952
|
-
const relative =
|
|
3953
|
-
const testName = relative.split(
|
|
3507
|
+
const relative = path10.relative(testResultsDir, filePath);
|
|
3508
|
+
const testName = relative.split(path10.sep)[0] || path10.basename(filePath, ".png");
|
|
3954
3509
|
return { filePath, testName };
|
|
3955
3510
|
});
|
|
3956
3511
|
if (screenshots.length > MAX_SCREENSHOTS) {
|
|
3957
|
-
|
|
3512
|
+
logger12.warn("Too many screenshots, truncating", {
|
|
3958
3513
|
total: screenshots.length,
|
|
3959
3514
|
max: MAX_SCREENSHOTS
|
|
3960
3515
|
});
|
|
3961
3516
|
return screenshots.slice(0, MAX_SCREENSHOTS);
|
|
3962
3517
|
}
|
|
3963
|
-
|
|
3518
|
+
logger12.info("Screenshots collected", { count: screenshots.length });
|
|
3964
3519
|
return screenshots;
|
|
3965
3520
|
}
|
|
3966
3521
|
|
|
3967
3522
|
// src/e2e/ScreenshotPublisher.ts
|
|
3968
|
-
var
|
|
3523
|
+
var logger13 = logger.child("ScreenshotPublisher");
|
|
3969
3524
|
function buildComment(uploaded, truncated) {
|
|
3970
3525
|
const lines = [t("screenshot.title"), ""];
|
|
3971
3526
|
for (const item of uploaded) {
|
|
@@ -3984,12 +3539,12 @@ var ScreenshotPublisher = class {
|
|
|
3984
3539
|
const { workDir, issueIid, issueId, mrIid } = options;
|
|
3985
3540
|
const screenshots = collectScreenshots(workDir);
|
|
3986
3541
|
if (screenshots.length === 0) {
|
|
3987
|
-
|
|
3542
|
+
logger13.info("No E2E screenshots to publish", { issueIid });
|
|
3988
3543
|
return;
|
|
3989
3544
|
}
|
|
3990
3545
|
const uploaded = await this.uploadAll(screenshots);
|
|
3991
3546
|
if (uploaded.length === 0) {
|
|
3992
|
-
|
|
3547
|
+
logger13.warn("All screenshot uploads failed", { issueIid });
|
|
3993
3548
|
return;
|
|
3994
3549
|
}
|
|
3995
3550
|
const truncated = screenshots.length >= 20;
|
|
@@ -3998,7 +3553,7 @@ var ScreenshotPublisher = class {
|
|
|
3998
3553
|
if (mrIid) {
|
|
3999
3554
|
await this.postToMergeRequest(mrIid, comment);
|
|
4000
3555
|
}
|
|
4001
|
-
|
|
3556
|
+
logger13.info("E2E screenshots published", {
|
|
4002
3557
|
issueIid,
|
|
4003
3558
|
mrIid,
|
|
4004
3559
|
count: uploaded.length
|
|
@@ -4014,7 +3569,7 @@ var ScreenshotPublisher = class {
|
|
|
4014
3569
|
markdown: result.markdown
|
|
4015
3570
|
});
|
|
4016
3571
|
} catch (err) {
|
|
4017
|
-
|
|
3572
|
+
logger13.warn("Failed to upload screenshot", {
|
|
4018
3573
|
filePath: screenshot.filePath,
|
|
4019
3574
|
error: err.message
|
|
4020
3575
|
});
|
|
@@ -4026,7 +3581,7 @@ var ScreenshotPublisher = class {
|
|
|
4026
3581
|
try {
|
|
4027
3582
|
await this.gongfeng.createIssueNote(issueId, comment);
|
|
4028
3583
|
} catch (err) {
|
|
4029
|
-
|
|
3584
|
+
logger13.warn("Failed to post screenshots to issue", {
|
|
4030
3585
|
issueId,
|
|
4031
3586
|
error: err.message
|
|
4032
3587
|
});
|
|
@@ -4036,7 +3591,7 @@ var ScreenshotPublisher = class {
|
|
|
4036
3591
|
try {
|
|
4037
3592
|
await this.gongfeng.createMergeRequestNote(mrIid, comment);
|
|
4038
3593
|
} catch (err) {
|
|
4039
|
-
|
|
3594
|
+
logger13.warn("Failed to post screenshots to merge request", {
|
|
4040
3595
|
mrIid,
|
|
4041
3596
|
error: err.message
|
|
4042
3597
|
});
|
|
@@ -4219,7 +3774,7 @@ metrics.registerCounter("iaf_braindump_batches_total", "Total braindump batches"
|
|
|
4219
3774
|
metrics.registerCounter("iaf_braindump_tasks_total", "Total braindump tasks");
|
|
4220
3775
|
|
|
4221
3776
|
// src/orchestrator/steps/SetupStep.ts
|
|
4222
|
-
var
|
|
3777
|
+
var logger14 = logger.child("SetupStep");
|
|
4223
3778
|
async function executeSetup(ctx, deps) {
|
|
4224
3779
|
const { issue, wtCtx, record, pipelineDef, branchName } = ctx;
|
|
4225
3780
|
try {
|
|
@@ -4228,7 +3783,7 @@ async function executeSetup(ctx, deps) {
|
|
|
4228
3783
|
"auto-finish:processing"
|
|
4229
3784
|
]);
|
|
4230
3785
|
} catch (err) {
|
|
4231
|
-
|
|
3786
|
+
logger14.warn("Failed to update issue labels", { error: err.message });
|
|
4232
3787
|
}
|
|
4233
3788
|
await deps.mainGitMutex.runExclusive(async () => {
|
|
4234
3789
|
deps.emitProgress(issue.iid, "fetch", t("orchestrator.fetchProgress"));
|
|
@@ -4287,7 +3842,7 @@ async function executeSetup(ctx, deps) {
|
|
|
4287
3842
|
issueDescription: issue.description
|
|
4288
3843
|
});
|
|
4289
3844
|
} catch (err) {
|
|
4290
|
-
|
|
3845
|
+
logger14.warn("Failed to inject Claude Code hooks (non-blocking)", {
|
|
4291
3846
|
error: err.message
|
|
4292
3847
|
});
|
|
4293
3848
|
}
|
|
@@ -4399,7 +3954,7 @@ function clearPendingDialog(issueIid) {
|
|
|
4399
3954
|
}
|
|
4400
3955
|
|
|
4401
3956
|
// src/orchestrator/steps/PhaseHelpers.ts
|
|
4402
|
-
var
|
|
3957
|
+
var logger15 = logger.child("PhaseHelpers");
|
|
4403
3958
|
async function safeComment(deps, issueId, message) {
|
|
4404
3959
|
try {
|
|
4405
3960
|
await deps.gongfeng.createIssueNote(issueId, message);
|
|
@@ -4421,7 +3976,7 @@ async function commitPlanFiles(ctx, wtGit, wtGitMap, phaseName, displayId) {
|
|
|
4421
3976
|
for (const repo of ctx.workspace.repos) {
|
|
4422
3977
|
const repoGit = wtGitMap?.get(repo.name);
|
|
4423
3978
|
if (!repoGit) {
|
|
4424
|
-
|
|
3979
|
+
logger15.warn("Missing GitOperations for repo, skipping commit", { repo: repo.name });
|
|
4425
3980
|
continue;
|
|
4426
3981
|
}
|
|
4427
3982
|
const branch = repo.branchPrefix ? `${repo.branchPrefix}-${displayId}` : ctx.branchName;
|
|
@@ -4430,10 +3985,10 @@ async function commitPlanFiles(ctx, wtGit, wtGitMap, phaseName, displayId) {
|
|
|
4430
3985
|
await repoGit.add(["."]);
|
|
4431
3986
|
await repoGit.commit(commitMsg);
|
|
4432
3987
|
await repoGit.push(branch);
|
|
4433
|
-
|
|
3988
|
+
logger15.info("Committed changes for repo", { repo: repo.name, branch });
|
|
4434
3989
|
}
|
|
4435
3990
|
} catch (err) {
|
|
4436
|
-
|
|
3991
|
+
logger15.warn("Failed to commit/push for repo", {
|
|
4437
3992
|
repo: repo.name,
|
|
4438
3993
|
error: err.message
|
|
4439
3994
|
});
|
|
@@ -4471,15 +4026,15 @@ async function syncResultToIssue(phase, ctx, displayId, phaseName, deps, issueId
|
|
|
4471
4026
|
summary
|
|
4472
4027
|
);
|
|
4473
4028
|
await safeComment(deps, issueId, comment);
|
|
4474
|
-
|
|
4029
|
+
logger15.info("Result synced to issue", { issueIid: displayId, file: file.filename });
|
|
4475
4030
|
}
|
|
4476
4031
|
} catch (err) {
|
|
4477
|
-
|
|
4032
|
+
logger15.warn("Failed to sync result to issue", { error: err.message });
|
|
4478
4033
|
await safeComment(deps, issueId, issueProgressComment(phaseName, "completed"));
|
|
4479
4034
|
}
|
|
4480
4035
|
}
|
|
4481
4036
|
function handlePlanApproval(displayId, phaseName, deps) {
|
|
4482
|
-
|
|
4037
|
+
logger15.info("ACP plan-approval requested, delegating to review gate", {
|
|
4483
4038
|
issueIid: displayId,
|
|
4484
4039
|
phase: phaseName
|
|
4485
4040
|
});
|
|
@@ -4489,14 +4044,14 @@ function handlePlanApproval(displayId, phaseName, deps) {
|
|
|
4489
4044
|
const data = payload.data;
|
|
4490
4045
|
if (data.issueIid !== displayId) return;
|
|
4491
4046
|
cleanup();
|
|
4492
|
-
|
|
4047
|
+
logger15.info("ACP plan-approval approved via review gate", { issueIid: displayId });
|
|
4493
4048
|
resolve("allow");
|
|
4494
4049
|
};
|
|
4495
4050
|
const onRejected = (payload) => {
|
|
4496
4051
|
const data = payload.data;
|
|
4497
4052
|
if (data.issueIid !== displayId) return;
|
|
4498
4053
|
cleanup();
|
|
4499
|
-
|
|
4054
|
+
logger15.info("ACP plan-approval rejected via review gate", { issueIid: displayId });
|
|
4500
4055
|
resolve("reject");
|
|
4501
4056
|
};
|
|
4502
4057
|
const cleanup = () => {
|
|
@@ -4508,7 +4063,7 @@ function handlePlanApproval(displayId, phaseName, deps) {
|
|
|
4508
4063
|
});
|
|
4509
4064
|
}
|
|
4510
4065
|
function handleInteractiveDialog(displayId, phaseName, deps, request) {
|
|
4511
|
-
|
|
4066
|
+
logger15.info("Interactive dialog forwarded to frontend", {
|
|
4512
4067
|
issueIid: displayId,
|
|
4513
4068
|
phase: phaseName,
|
|
4514
4069
|
question: request.content.slice(0, 80),
|
|
@@ -4538,7 +4093,7 @@ function handleInteractiveDialog(displayId, phaseName, deps, request) {
|
|
|
4538
4093
|
if (data.issueIid !== displayId) return;
|
|
4539
4094
|
cleanup();
|
|
4540
4095
|
clearPendingDialog(displayId);
|
|
4541
|
-
|
|
4096
|
+
logger15.info("Interactive dialog response received from frontend", {
|
|
4542
4097
|
issueIid: displayId,
|
|
4543
4098
|
response: data.response
|
|
4544
4099
|
});
|
|
@@ -4548,7 +4103,7 @@ function handleInteractiveDialog(displayId, phaseName, deps, request) {
|
|
|
4548
4103
|
const data = payload.data;
|
|
4549
4104
|
if (data.issueIid !== displayId) return;
|
|
4550
4105
|
cleanup();
|
|
4551
|
-
|
|
4106
|
+
logger15.info("Interactive dialog dismissed by user (false positive)", { issueIid: displayId });
|
|
4552
4107
|
resolve("");
|
|
4553
4108
|
};
|
|
4554
4109
|
deps.eventBus.on("agent:inputResponse", onResponse);
|
|
@@ -4572,7 +4127,7 @@ function updateHooksForPhase(spec, pipelineDef, ctx, wtPlan) {
|
|
|
4572
4127
|
issueDescription: ctx.issue.description
|
|
4573
4128
|
});
|
|
4574
4129
|
} catch (err) {
|
|
4575
|
-
|
|
4130
|
+
logger15.warn("Failed to update hooks for phase (non-blocking)", {
|
|
4576
4131
|
phase: spec.name,
|
|
4577
4132
|
error: err.message
|
|
4578
4133
|
});
|
|
@@ -4580,7 +4135,7 @@ function updateHooksForPhase(spec, pipelineDef, ctx, wtPlan) {
|
|
|
4580
4135
|
}
|
|
4581
4136
|
|
|
4582
4137
|
// src/lifecycle/DefaultLifecycleHook.ts
|
|
4583
|
-
var
|
|
4138
|
+
var logger16 = logger.child("DefaultLifecycleHook");
|
|
4584
4139
|
var DefaultLifecycleHook = class {
|
|
4585
4140
|
async beforePhase(ctx) {
|
|
4586
4141
|
const { spec, issueCtx, deps, wtPlan } = ctx;
|
|
@@ -4680,7 +4235,7 @@ function createCallbacksFromHook(hook, ctx) {
|
|
|
4680
4235
|
}
|
|
4681
4236
|
|
|
4682
4237
|
// src/orchestrator/strategies/GateStrategy.ts
|
|
4683
|
-
var
|
|
4238
|
+
var logger17 = logger.child("GateStrategy");
|
|
4684
4239
|
var GateStrategy = class {
|
|
4685
4240
|
name = "gate";
|
|
4686
4241
|
shouldSkip() {
|
|
@@ -4690,7 +4245,7 @@ var GateStrategy = class {
|
|
|
4690
4245
|
const { spec, issueCtx, deps, wtPlan } = ctx;
|
|
4691
4246
|
const { issue } = issueCtx;
|
|
4692
4247
|
if (deps.shouldAutoApprove(issue.labels)) {
|
|
4693
|
-
|
|
4248
|
+
logger17.info("Auto-approving review gate (matched autoApproveLabels)", {
|
|
4694
4249
|
iid: issue.iid,
|
|
4695
4250
|
labels: issue.labels,
|
|
4696
4251
|
autoApproveLabels: deps.config.review.autoApproveLabels
|
|
@@ -4716,19 +4271,19 @@ var GateStrategy = class {
|
|
|
4716
4271
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4717
4272
|
});
|
|
4718
4273
|
deps.eventBus.emitTyped("review:requested", { issueIid: issue.iid });
|
|
4719
|
-
|
|
4274
|
+
logger17.info("Review gate reached, pausing", { iid: issue.iid });
|
|
4720
4275
|
return { paused: true };
|
|
4721
4276
|
}
|
|
4722
4277
|
};
|
|
4723
4278
|
|
|
4724
4279
|
// src/orchestrator/strategies/AiPhaseStrategy.ts
|
|
4725
|
-
var
|
|
4280
|
+
var logger18 = logger.child("AiPhaseStrategy");
|
|
4726
4281
|
var AiPhaseStrategy = class {
|
|
4727
4282
|
name = "ai";
|
|
4728
4283
|
shouldSkip(ctx) {
|
|
4729
4284
|
const { spec, issueCtx, deps } = ctx;
|
|
4730
4285
|
if (spec.name === "uat" && !isE2eEnabledForIssue(issueCtx.issue.iid, deps.tracker, deps.config)) {
|
|
4731
|
-
|
|
4286
|
+
logger18.info("UAT phase skipped (E2E not enabled for this issue)", { iid: issueCtx.issue.iid });
|
|
4732
4287
|
return true;
|
|
4733
4288
|
}
|
|
4734
4289
|
return false;
|
|
@@ -4749,7 +4304,7 @@ var AiPhaseStrategy = class {
|
|
|
4749
4304
|
const runner = this.resolveRunner(ctx);
|
|
4750
4305
|
if (spec.name === "uat") {
|
|
4751
4306
|
const runnerName = runner === deps.e2eAiRunner ? "e2eAiRunner (CodeBuddy)" : "mainRunner";
|
|
4752
|
-
|
|
4307
|
+
logger18.info("UAT phase starting", { iid: issue.iid, runner: runnerName });
|
|
4753
4308
|
}
|
|
4754
4309
|
const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
|
|
4755
4310
|
if (wtGitMap) phase.setWtGitMap(wtGitMap);
|
|
@@ -4783,7 +4338,7 @@ var AiPhaseStrategy = class {
|
|
|
4783
4338
|
const { spec, issueCtx, deps, wtGit, wtPlan, wtGitMap } = ctx;
|
|
4784
4339
|
const { issue, phaseCtx } = issueCtx;
|
|
4785
4340
|
if (outcome.awaitCompletion) {
|
|
4786
|
-
|
|
4341
|
+
logger18.info("Async phase running, awaiting completion", { iid: issue.iid, phase: spec.name });
|
|
4787
4342
|
const finalOutcome = await outcome.awaitCompletion;
|
|
4788
4343
|
if (finalOutcome.sessionId) {
|
|
4789
4344
|
wtPlan.updatePhaseSessionId(spec.name, finalOutcome.sessionId);
|
|
@@ -4799,7 +4354,7 @@ var AiPhaseStrategy = class {
|
|
|
4799
4354
|
const runner = this.resolveRunner(ctx);
|
|
4800
4355
|
const phase = createPhase(spec.name, runner, wtGit, wtPlan, deps.config);
|
|
4801
4356
|
await syncResultToIssue(phase, phaseCtx, issue.iid, spec.name, deps, issue.id, wtPlan);
|
|
4802
|
-
|
|
4357
|
+
logger18.info("Async phase completed successfully", { iid: issue.iid, phase: spec.name });
|
|
4803
4358
|
return { paused: false };
|
|
4804
4359
|
}
|
|
4805
4360
|
const errMsg = finalOutcome.error?.message ?? "Unknown error";
|
|
@@ -4825,7 +4380,7 @@ var AiPhaseStrategy = class {
|
|
|
4825
4380
|
deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
|
|
4826
4381
|
const gateEvent = spec.name === "uat" ? "uat:gateRequested" : "release:gateRequested";
|
|
4827
4382
|
deps.eventBus.emitTyped(gateEvent, { issueIid: issue.iid });
|
|
4828
|
-
|
|
4383
|
+
logger18.info("Async phase running (no awaitCompletion), pausing pipeline", {
|
|
4829
4384
|
iid: issue.iid,
|
|
4830
4385
|
phase: spec.name
|
|
4831
4386
|
});
|
|
@@ -4838,13 +4393,13 @@ var AiPhaseStrategy = class {
|
|
|
4838
4393
|
wtPlan.updatePhaseProgress(spec.name, "gate_waiting");
|
|
4839
4394
|
deps.tracker.updatePhaseProgress(issue.iid, spec.name, { status: "gate_waiting" });
|
|
4840
4395
|
deps.eventBus.emitTyped("release:gateRequested", { issueIid: issue.iid });
|
|
4841
|
-
|
|
4396
|
+
logger18.info("Phase requested gate, pausing", { iid: issue.iid, phase: spec.name });
|
|
4842
4397
|
return { paused: true };
|
|
4843
4398
|
}
|
|
4844
4399
|
};
|
|
4845
4400
|
|
|
4846
4401
|
// src/orchestrator/strategies/VerifyFixStrategy.ts
|
|
4847
|
-
var
|
|
4402
|
+
var logger19 = logger.child("VerifyFixStrategy");
|
|
4848
4403
|
var VerifyFixStrategy = class {
|
|
4849
4404
|
name = "verify-fix";
|
|
4850
4405
|
shouldSkip() {
|
|
@@ -4861,14 +4416,14 @@ var VerifyFixStrategy = class {
|
|
|
4861
4416
|
issueIid: issue.iid,
|
|
4862
4417
|
maxIterations
|
|
4863
4418
|
});
|
|
4864
|
-
|
|
4419
|
+
logger19.info("Verify-fix loop started", {
|
|
4865
4420
|
iid: issue.iid,
|
|
4866
4421
|
maxIterations,
|
|
4867
4422
|
buildPhaseIdx
|
|
4868
4423
|
});
|
|
4869
4424
|
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
4870
4425
|
if (isShuttingDown()) throw new ServiceShutdownError();
|
|
4871
|
-
|
|
4426
|
+
logger19.info("Verify-fix loop iteration", { iteration, maxIterations, iid: issue.iid });
|
|
4872
4427
|
updateHooksForPhase(verifySpec, pipelineDef, issueCtx, wtPlan);
|
|
4873
4428
|
const verifyRunner = resolveVerifyRunner(deps);
|
|
4874
4429
|
const verifyPhase = createPhase("verify", verifyRunner, wtGit, wtPlan, deps.config);
|
|
@@ -4887,7 +4442,7 @@ var VerifyFixStrategy = class {
|
|
|
4887
4442
|
hooks
|
|
4888
4443
|
);
|
|
4889
4444
|
} catch (err) {
|
|
4890
|
-
|
|
4445
|
+
logger19.warn("Verify phase execution failed", {
|
|
4891
4446
|
iteration,
|
|
4892
4447
|
iid: issue.iid,
|
|
4893
4448
|
error: err.message
|
|
@@ -4917,10 +4472,10 @@ var VerifyFixStrategy = class {
|
|
|
4917
4472
|
failures: report?.failureReasons
|
|
4918
4473
|
});
|
|
4919
4474
|
if (passed) {
|
|
4920
|
-
|
|
4475
|
+
logger19.info("Verify-fix loop passed", { iteration, iid: issue.iid });
|
|
4921
4476
|
return { paused: false };
|
|
4922
4477
|
}
|
|
4923
|
-
|
|
4478
|
+
logger19.info("Verify failed, issues found", {
|
|
4924
4479
|
iteration,
|
|
4925
4480
|
iid: issue.iid,
|
|
4926
4481
|
failures: report?.failureReasons,
|
|
@@ -4933,7 +4488,7 @@ var VerifyFixStrategy = class {
|
|
|
4933
4488
|
failures: report?.failureReasons ?? []
|
|
4934
4489
|
});
|
|
4935
4490
|
const failMsg = `Verify-fix loop exhausted after ${maxIterations} iterations. Remaining issues: ${report?.failureReasons?.join("; ") ?? "unknown"}`;
|
|
4936
|
-
|
|
4491
|
+
logger19.warn(failMsg, { iid: issue.iid });
|
|
4937
4492
|
throw new AIExecutionError("verify", failMsg, {
|
|
4938
4493
|
output: report?.rawReport ?? "",
|
|
4939
4494
|
exitCode: 0
|
|
@@ -4952,7 +4507,7 @@ var VerifyFixStrategy = class {
|
|
|
4952
4507
|
async executeBuildFix(ctx, deps, wtGit, wtPlan, buildPhaseIdx, fixContext, wtGitMap, hooks) {
|
|
4953
4508
|
const { issue, phaseCtx, pipelineDef } = ctx;
|
|
4954
4509
|
const buildSpec = pipelineDef.phases[buildPhaseIdx];
|
|
4955
|
-
|
|
4510
|
+
logger19.info("Looping back to build for fix", {
|
|
4956
4511
|
iteration: fixContext.iteration,
|
|
4957
4512
|
iid: issue.iid,
|
|
4958
4513
|
failures: fixContext.verifyFailures
|
|
@@ -5000,7 +4555,7 @@ function resolveStrategy(spec, config) {
|
|
|
5000
4555
|
}
|
|
5001
4556
|
|
|
5002
4557
|
// src/orchestrator/steps/PhaseLoopStep.ts
|
|
5003
|
-
var
|
|
4558
|
+
var logger20 = logger.child("PhaseLoopStep");
|
|
5004
4559
|
async function runPhaseWithLifecycle(phase, phaseCtx, spec, ctx, deps, wtGit, wtPlan, wtGitMap, hook) {
|
|
5005
4560
|
const lifecycleHook = hook ?? new DefaultLifecycleHook();
|
|
5006
4561
|
const execCtx = {
|
|
@@ -5065,15 +4620,15 @@ async function executePhaseLoop(ctx, deps, wtGit, wtPlan, wtGitMap) {
|
|
|
5065
4620
|
if (skippedDeployPhase && !phaseCtx.ports) {
|
|
5066
4621
|
const existingPorts = deps.getPortsForIssue(issue.iid);
|
|
5067
4622
|
if (existingPorts && deps.isPreviewRunning(issue.iid)) {
|
|
5068
|
-
|
|
4623
|
+
logger20.info("Restored preview ports from allocator", { iid: issue.iid, ...existingPorts });
|
|
5069
4624
|
phaseCtx.ports = existingPorts;
|
|
5070
4625
|
ctx.wtCtx.ports = existingPorts;
|
|
5071
4626
|
serversStarted = true;
|
|
5072
4627
|
} else {
|
|
5073
4628
|
if (existingPorts) {
|
|
5074
|
-
|
|
4629
|
+
logger20.info("Ports allocated but servers not running, restarting", { iid: issue.iid });
|
|
5075
4630
|
} else {
|
|
5076
|
-
|
|
4631
|
+
logger20.info("Restarting preview servers for resumed pipeline", { iid: issue.iid });
|
|
5077
4632
|
}
|
|
5078
4633
|
const ports = await deps.startPreviewServers(ctx.wtCtx, issue);
|
|
5079
4634
|
if (ports) {
|
|
@@ -5139,7 +4694,7 @@ function healStaleProgress(ctx, deps, wtPlan, startIdx) {
|
|
|
5139
4694
|
const prevSpec = pipelineDef.phases[i];
|
|
5140
4695
|
const pp = currentProgress.phases[prevSpec.name];
|
|
5141
4696
|
if (pp && pp.status !== "completed") {
|
|
5142
|
-
|
|
4697
|
+
logger20.warn("Fixing stale phase progress", {
|
|
5143
4698
|
iid: issue.iid,
|
|
5144
4699
|
phase: prevSpec.name,
|
|
5145
4700
|
was: pp.status,
|
|
@@ -5171,7 +4726,7 @@ function healStaleProgress(ctx, deps, wtPlan, startIdx) {
|
|
|
5171
4726
|
}
|
|
5172
4727
|
|
|
5173
4728
|
// src/orchestrator/steps/CompletionStep.ts
|
|
5174
|
-
var
|
|
4729
|
+
var logger21 = logger.child("CompletionStep");
|
|
5175
4730
|
async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
|
|
5176
4731
|
const { issue, branchName, wtCtx } = ctx;
|
|
5177
4732
|
deps.emitProgress(issue.iid, "create_mr", t("orchestrator.createMrProgress"));
|
|
@@ -5203,7 +4758,7 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
|
|
|
5203
4758
|
mrIid: void 0
|
|
5204
4759
|
});
|
|
5205
4760
|
} catch (err) {
|
|
5206
|
-
|
|
4761
|
+
logger21.warn("Failed to publish E2E screenshots", {
|
|
5207
4762
|
iid: issue.iid,
|
|
5208
4763
|
error: err.message
|
|
5209
4764
|
});
|
|
@@ -5223,19 +4778,19 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
|
|
|
5223
4778
|
await deps.claimer.releaseClaim(issue.id, issue.iid, "completed");
|
|
5224
4779
|
}
|
|
5225
4780
|
if (phaseResult.serversStarted && deps.config.preview.keepAfterComplete) {
|
|
5226
|
-
|
|
4781
|
+
logger21.info("Preview servers kept running after completion", { iid: issue.iid });
|
|
5227
4782
|
} else {
|
|
5228
4783
|
deps.stopPreviewServers(issue.iid);
|
|
5229
4784
|
await deps.mainGitMutex.runExclusive(async () => {
|
|
5230
4785
|
if (wtCtx.workspace) {
|
|
5231
4786
|
await deps.workspaceManager.cleanupWorkspace(wtCtx.workspace);
|
|
5232
|
-
|
|
4787
|
+
logger21.info("Workspace cleaned up", { dir: wtCtx.workspace.workspaceRoot });
|
|
5233
4788
|
} else {
|
|
5234
4789
|
try {
|
|
5235
4790
|
await deps.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
|
|
5236
|
-
|
|
4791
|
+
logger21.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
|
|
5237
4792
|
} catch (err) {
|
|
5238
|
-
|
|
4793
|
+
logger21.warn("Failed to cleanup worktree", {
|
|
5239
4794
|
dir: wtCtx.gitRootDir,
|
|
5240
4795
|
error: err.message
|
|
5241
4796
|
});
|
|
@@ -5243,16 +4798,16 @@ async function executeCompletion(ctx, deps, phaseResult, _wtGitMap) {
|
|
|
5243
4798
|
}
|
|
5244
4799
|
});
|
|
5245
4800
|
}
|
|
5246
|
-
|
|
4801
|
+
logger21.info("Issue processing completed", { iid: issue.iid });
|
|
5247
4802
|
}
|
|
5248
4803
|
|
|
5249
4804
|
// src/orchestrator/steps/FailureHandler.ts
|
|
5250
|
-
var
|
|
4805
|
+
var logger22 = logger.child("FailureHandler");
|
|
5251
4806
|
async function handleFailure(err, issue, wtCtx, deps) {
|
|
5252
4807
|
const errorMsg = err.message;
|
|
5253
4808
|
const isRetryable = err instanceof AIExecutionError ? err.isRetryable : true;
|
|
5254
4809
|
const wasActiveAtTimeout = err instanceof AIExecutionError && err.wasActiveAtTimeout;
|
|
5255
|
-
|
|
4810
|
+
logger22.error("Issue processing failed", { iid: issue.iid, error: errorMsg, isRetryable, wasActiveAtTimeout });
|
|
5256
4811
|
metrics.incCounter("iaf_issues_failed_total");
|
|
5257
4812
|
const currentRecord = deps.tracker.get(issue.iid);
|
|
5258
4813
|
const failedAtState = currentRecord?.state || "pending" /* Pending */;
|
|
@@ -5265,11 +4820,11 @@ async function handleFailure(err, issue, wtCtx, deps) {
|
|
|
5265
4820
|
}
|
|
5266
4821
|
}
|
|
5267
4822
|
if (wasReset) {
|
|
5268
|
-
|
|
4823
|
+
logger22.info("Issue was reset during processing, skipping failure marking", { iid: issue.iid });
|
|
5269
4824
|
throw err;
|
|
5270
4825
|
}
|
|
5271
4826
|
if (failedAtState === "paused" /* Paused */) {
|
|
5272
|
-
|
|
4827
|
+
logger22.info("Issue was paused during processing, skipping failure handling", { iid: issue.iid });
|
|
5273
4828
|
throw err;
|
|
5274
4829
|
}
|
|
5275
4830
|
try {
|
|
@@ -5291,7 +4846,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
|
|
|
5291
4846
|
try {
|
|
5292
4847
|
await deps.claimer.releaseClaim(issue.id, issue.iid, "failed");
|
|
5293
4848
|
} catch (releaseErr) {
|
|
5294
|
-
|
|
4849
|
+
logger22.warn("Failed to release lock on failure", {
|
|
5295
4850
|
iid: issue.iid,
|
|
5296
4851
|
error: releaseErr.message
|
|
5297
4852
|
});
|
|
@@ -5299,7 +4854,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
|
|
|
5299
4854
|
}
|
|
5300
4855
|
deps.stopPreviewServers(issue.iid);
|
|
5301
4856
|
const preservedDirs = wtCtx.workspace ? [wtCtx.workspace.primary.gitRootDir, ...wtCtx.workspace.associates.map((a) => a.gitRootDir)] : [wtCtx.gitRootDir];
|
|
5302
|
-
|
|
4857
|
+
logger22.info("Worktree(s) preserved for debugging", {
|
|
5303
4858
|
primary: wtCtx.gitRootDir,
|
|
5304
4859
|
all: preservedDirs
|
|
5305
4860
|
});
|
|
@@ -5308,7 +4863,7 @@ async function handleFailure(err, issue, wtCtx, deps) {
|
|
|
5308
4863
|
|
|
5309
4864
|
// src/orchestrator/PipelineOrchestrator.ts
|
|
5310
4865
|
var execFileAsync2 = promisify2(execFile2);
|
|
5311
|
-
var
|
|
4866
|
+
var logger23 = logger.child("PipelineOrchestrator");
|
|
5312
4867
|
var PipelineOrchestrator = class {
|
|
5313
4868
|
config;
|
|
5314
4869
|
gongfeng;
|
|
@@ -5338,7 +4893,7 @@ var PipelineOrchestrator = class {
|
|
|
5338
4893
|
setAIRunner(runner) {
|
|
5339
4894
|
this.aiRunner = runner;
|
|
5340
4895
|
this.conflictResolver = new ConflictResolver(runner);
|
|
5341
|
-
|
|
4896
|
+
logger23.info("AIRunner replaced via hot-reload");
|
|
5342
4897
|
}
|
|
5343
4898
|
constructor(config, gongfeng, git, aiRunner, tracker, supplementStore, mainGitMutex, eventBusInstance, wsConfig, tenantId, e2eAiRunner) {
|
|
5344
4899
|
this.config = config;
|
|
@@ -5356,14 +4911,14 @@ var PipelineOrchestrator = class {
|
|
|
5356
4911
|
this.pipelineDef = mode === "plan-mode" ? buildPlanModePipeline({ releaseEnabled: config.release.enabled, e2eEnabled: config.e2e.enabled }) : getPipelineDef(mode);
|
|
5357
4912
|
registerPipeline(this.pipelineDef);
|
|
5358
4913
|
this.lifecycleManager = createLifecycleManager(this.pipelineDef);
|
|
5359
|
-
|
|
4914
|
+
logger23.info("Pipeline mode resolved", { tenantId: this.tenantId, mode: this.pipelineDef.mode, aiMode: config.ai.mode });
|
|
5360
4915
|
this.portAllocator = new PortAllocator({
|
|
5361
4916
|
backendPortBase: config.e2e.backendPortBase,
|
|
5362
4917
|
frontendPortBase: config.e2e.frontendPortBase
|
|
5363
4918
|
});
|
|
5364
4919
|
this.devServerManager = new DevServerManager();
|
|
5365
4920
|
this.screenshotPublisher = new ScreenshotPublisher(gongfeng);
|
|
5366
|
-
this.effectiveWorktreeBaseDir = this.tenantId === "default" ? config.project.worktreeBaseDir :
|
|
4921
|
+
this.effectiveWorktreeBaseDir = this.tenantId === "default" ? config.project.worktreeBaseDir : path11.join(config.project.worktreeBaseDir, this.tenantId);
|
|
5367
4922
|
const effectiveWsConfig = wsConfig ?? buildSingleRepoWorkspace(config.project, config.gongfeng.projectPath);
|
|
5368
4923
|
this.workspaceManager = new WorkspaceManager({
|
|
5369
4924
|
wsConfig: effectiveWsConfig,
|
|
@@ -5372,7 +4927,7 @@ var PipelineOrchestrator = class {
|
|
|
5372
4927
|
mainGitMutex: this.mainGitMutex,
|
|
5373
4928
|
gongfengApiUrl: config.gongfeng.apiUrl
|
|
5374
4929
|
});
|
|
5375
|
-
|
|
4930
|
+
logger23.info("WorkspaceManager initialized", {
|
|
5376
4931
|
tenantId: this.tenantId,
|
|
5377
4932
|
primary: effectiveWsConfig.primary.name,
|
|
5378
4933
|
associates: effectiveWsConfig.associates.map((a) => a.name)
|
|
@@ -5393,7 +4948,7 @@ var PipelineOrchestrator = class {
|
|
|
5393
4948
|
this.claimer = claimer;
|
|
5394
4949
|
}
|
|
5395
4950
|
async cleanupStaleState() {
|
|
5396
|
-
|
|
4951
|
+
logger23.info("Cleaning up stale worktree state...");
|
|
5397
4952
|
let cleaned = 0;
|
|
5398
4953
|
const repoGitRoot = this.config.project.gitRootDir;
|
|
5399
4954
|
try {
|
|
@@ -5402,11 +4957,11 @@ var PipelineOrchestrator = class {
|
|
|
5402
4957
|
if (wtDir === repoGitRoot) continue;
|
|
5403
4958
|
if (!wtDir.includes("/issue-")) continue;
|
|
5404
4959
|
try {
|
|
5405
|
-
const gitFile =
|
|
4960
|
+
const gitFile = path11.join(wtDir, ".git");
|
|
5406
4961
|
try {
|
|
5407
|
-
await
|
|
4962
|
+
await fs9.access(gitFile);
|
|
5408
4963
|
} catch {
|
|
5409
|
-
|
|
4964
|
+
logger23.warn("Worktree corrupted (.git missing), force removing", { dir: wtDir });
|
|
5410
4965
|
await this.mainGit.worktreeRemove(wtDir, true).catch(() => {
|
|
5411
4966
|
});
|
|
5412
4967
|
await this.mainGit.worktreePrune();
|
|
@@ -5415,32 +4970,32 @@ var PipelineOrchestrator = class {
|
|
|
5415
4970
|
}
|
|
5416
4971
|
const wtGit = new GitOperations(wtDir);
|
|
5417
4972
|
if (await wtGit.isRebaseInProgress()) {
|
|
5418
|
-
|
|
4973
|
+
logger23.warn("Aborting residual rebase in worktree", { dir: wtDir });
|
|
5419
4974
|
await wtGit.rebaseAbort();
|
|
5420
4975
|
cleaned++;
|
|
5421
4976
|
}
|
|
5422
|
-
const indexLock =
|
|
4977
|
+
const indexLock = path11.join(wtDir, ".git", "index.lock");
|
|
5423
4978
|
try {
|
|
5424
|
-
await
|
|
5425
|
-
|
|
4979
|
+
await fs9.unlink(indexLock);
|
|
4980
|
+
logger23.warn("Removed stale index.lock", { path: indexLock });
|
|
5426
4981
|
cleaned++;
|
|
5427
4982
|
} catch {
|
|
5428
4983
|
}
|
|
5429
4984
|
} catch (err) {
|
|
5430
|
-
|
|
4985
|
+
logger23.warn("Failed to clean worktree state", { dir: wtDir, error: err.message });
|
|
5431
4986
|
}
|
|
5432
4987
|
}
|
|
5433
4988
|
} catch (err) {
|
|
5434
|
-
|
|
4989
|
+
logger23.warn("Failed to list worktrees for cleanup", { error: err.message });
|
|
5435
4990
|
}
|
|
5436
|
-
const mainIndexLock =
|
|
4991
|
+
const mainIndexLock = path11.join(repoGitRoot, ".git", "index.lock");
|
|
5437
4992
|
try {
|
|
5438
|
-
await
|
|
5439
|
-
|
|
4993
|
+
await fs9.unlink(mainIndexLock);
|
|
4994
|
+
logger23.warn("Removed stale main repo index.lock", { path: mainIndexLock });
|
|
5440
4995
|
cleaned++;
|
|
5441
4996
|
} catch {
|
|
5442
4997
|
}
|
|
5443
|
-
|
|
4998
|
+
logger23.info("Stale state cleanup complete", { cleaned });
|
|
5444
4999
|
}
|
|
5445
5000
|
/**
|
|
5446
5001
|
* 重启后清理幽灵端口分配。
|
|
@@ -5453,7 +5008,7 @@ var PipelineOrchestrator = class {
|
|
|
5453
5008
|
for (const record of this.tracker.getAll()) {
|
|
5454
5009
|
if (record.ports) {
|
|
5455
5010
|
const iid = getIid(record);
|
|
5456
|
-
|
|
5011
|
+
logger23.info("Clearing stale port allocation after restart", { iid, ports: record.ports });
|
|
5457
5012
|
this.tracker.updateState(iid, record.state, {
|
|
5458
5013
|
ports: void 0,
|
|
5459
5014
|
previewStartedAt: void 0
|
|
@@ -5498,20 +5053,20 @@ var PipelineOrchestrator = class {
|
|
|
5498
5053
|
}
|
|
5499
5054
|
try {
|
|
5500
5055
|
await this.mainGit.worktreeRemove(wtCtx.gitRootDir, true);
|
|
5501
|
-
|
|
5056
|
+
logger23.info("Worktree cleaned up", { dir: wtCtx.gitRootDir });
|
|
5502
5057
|
} catch (err) {
|
|
5503
|
-
|
|
5058
|
+
logger23.warn("Failed to cleanup worktree", { dir: wtCtx.gitRootDir, error: err.message });
|
|
5504
5059
|
}
|
|
5505
5060
|
}
|
|
5506
5061
|
async installDependencies(workDir) {
|
|
5507
|
-
|
|
5062
|
+
logger23.info("Installing dependencies in worktree", { workDir });
|
|
5508
5063
|
const knowledge = getProjectKnowledge() ?? KNOWLEDGE_DEFAULTS;
|
|
5509
5064
|
const pkgMgr = knowledge.toolchain.packageManager.toLowerCase();
|
|
5510
5065
|
const isNodeProject = ["npm", "pnpm", "yarn", "bun"].some((m) => pkgMgr.includes(m));
|
|
5511
5066
|
if (isNodeProject) {
|
|
5512
5067
|
const ready = await this.ensureNodeModules(workDir);
|
|
5513
5068
|
if (ready) {
|
|
5514
|
-
|
|
5069
|
+
logger23.info("node_modules ready \u2014 skipping install");
|
|
5515
5070
|
return;
|
|
5516
5071
|
}
|
|
5517
5072
|
}
|
|
@@ -5524,10 +5079,10 @@ var PipelineOrchestrator = class {
|
|
|
5524
5079
|
maxBuffer: 10 * 1024 * 1024,
|
|
5525
5080
|
timeout: 3e5
|
|
5526
5081
|
});
|
|
5527
|
-
|
|
5082
|
+
logger23.info("Dependencies installed");
|
|
5528
5083
|
} catch (err) {
|
|
5529
5084
|
if (fallbackCmd) {
|
|
5530
|
-
|
|
5085
|
+
logger23.warn(`${installCmd} failed, retrying with fallback command`, {
|
|
5531
5086
|
error: err.message
|
|
5532
5087
|
});
|
|
5533
5088
|
const [fallbackBin, ...fallbackArgs] = fallbackCmd.split(/\s+/);
|
|
@@ -5537,45 +5092,45 @@ var PipelineOrchestrator = class {
|
|
|
5537
5092
|
maxBuffer: 10 * 1024 * 1024,
|
|
5538
5093
|
timeout: 3e5
|
|
5539
5094
|
});
|
|
5540
|
-
|
|
5095
|
+
logger23.info("Dependencies installed (fallback)");
|
|
5541
5096
|
} catch (retryErr) {
|
|
5542
|
-
|
|
5097
|
+
logger23.warn("Fallback install also failed", {
|
|
5543
5098
|
error: retryErr.message
|
|
5544
5099
|
});
|
|
5545
5100
|
}
|
|
5546
5101
|
} else {
|
|
5547
|
-
|
|
5102
|
+
logger23.warn("Install failed, no fallback configured", {
|
|
5548
5103
|
error: err.message
|
|
5549
5104
|
});
|
|
5550
5105
|
}
|
|
5551
5106
|
}
|
|
5552
5107
|
}
|
|
5553
5108
|
async ensureNodeModules(workDir) {
|
|
5554
|
-
const targetBin =
|
|
5109
|
+
const targetBin = path11.join(workDir, "node_modules", ".bin");
|
|
5555
5110
|
try {
|
|
5556
|
-
await
|
|
5557
|
-
|
|
5111
|
+
await fs9.access(targetBin);
|
|
5112
|
+
logger23.info("node_modules already complete (has .bin/)");
|
|
5558
5113
|
return true;
|
|
5559
5114
|
} catch {
|
|
5560
5115
|
}
|
|
5561
|
-
const sourceNM =
|
|
5562
|
-
const targetNM =
|
|
5116
|
+
const sourceNM = path11.join(this.config.project.workDir, "node_modules");
|
|
5117
|
+
const targetNM = path11.join(workDir, "node_modules");
|
|
5563
5118
|
try {
|
|
5564
|
-
await
|
|
5119
|
+
await fs9.access(sourceNM);
|
|
5565
5120
|
} catch {
|
|
5566
|
-
|
|
5121
|
+
logger23.warn("Main repo node_modules not found, skipping seed", { sourceNM });
|
|
5567
5122
|
return false;
|
|
5568
5123
|
}
|
|
5569
|
-
|
|
5124
|
+
logger23.info("Seeding node_modules from main repo via reflink copy", { sourceNM, targetNM });
|
|
5570
5125
|
try {
|
|
5571
5126
|
await execFileAsync2("rm", ["-rf", targetNM], { timeout: 6e4 });
|
|
5572
5127
|
await execFileAsync2("cp", ["-a", "--reflink=auto", sourceNM, targetNM], {
|
|
5573
5128
|
timeout: 12e4
|
|
5574
5129
|
});
|
|
5575
|
-
|
|
5130
|
+
logger23.info("node_modules seeded from main repo");
|
|
5576
5131
|
return true;
|
|
5577
5132
|
} catch (err) {
|
|
5578
|
-
|
|
5133
|
+
logger23.warn("Failed to seed node_modules from main repo", {
|
|
5579
5134
|
error: err.message
|
|
5580
5135
|
});
|
|
5581
5136
|
return false;
|
|
@@ -5585,16 +5140,16 @@ var PipelineOrchestrator = class {
|
|
|
5585
5140
|
const record = this.tracker.get(issueIid);
|
|
5586
5141
|
if (!record) throw new IssueNotFoundError(issueIid);
|
|
5587
5142
|
const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
|
|
5588
|
-
|
|
5143
|
+
logger23.info("Restarting issue \u2014 cleaning context", { issueIid, branchName: record.branchName });
|
|
5589
5144
|
this.pendingActions.set(issueIid, "restart");
|
|
5590
5145
|
this.aiRunner.killByWorkDir(wtCtx.workDir);
|
|
5591
5146
|
this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
|
|
5592
5147
|
this.stopPreviewServers(issueIid);
|
|
5593
5148
|
try {
|
|
5594
5149
|
const deleted = await this.gongfeng.cleanupAgentNotes(getExternalId(record));
|
|
5595
|
-
|
|
5150
|
+
logger23.info("Agent notes cleaned up", { issueIid, deleted });
|
|
5596
5151
|
} catch (err) {
|
|
5597
|
-
|
|
5152
|
+
logger23.warn("Failed to cleanup agent notes", { issueIid, error: err.message });
|
|
5598
5153
|
}
|
|
5599
5154
|
await this.mainGitMutex.runExclusive(async () => {
|
|
5600
5155
|
await this.cleanupWorktree(wtCtx);
|
|
@@ -5611,19 +5166,19 @@ var PipelineOrchestrator = class {
|
|
|
5611
5166
|
await this.cleanupE2eOutputs(issueIid);
|
|
5612
5167
|
this.tracker.resetFull(issueIid);
|
|
5613
5168
|
this.pendingActions.delete(issueIid);
|
|
5614
|
-
|
|
5169
|
+
logger23.info("Issue restarted", { issueIid });
|
|
5615
5170
|
}
|
|
5616
5171
|
async cancelIssue(issueIid) {
|
|
5617
5172
|
const record = this.tracker.get(issueIid);
|
|
5618
5173
|
if (!record) throw new IssueNotFoundError(issueIid);
|
|
5619
5174
|
const wtCtx = this.computeWorktreeContext(issueIid, record.branchName);
|
|
5620
|
-
|
|
5175
|
+
logger23.info("Cancelling issue \u2014 cleaning all resources", { issueIid, branchName: record.branchName });
|
|
5621
5176
|
this.aiRunner.killByWorkDir(wtCtx.workDir);
|
|
5622
5177
|
this.stopPreviewServers(issueIid);
|
|
5623
5178
|
try {
|
|
5624
5179
|
await this.gongfeng.removeLabelsWithPrefix(getExternalId(record), "auto-finish");
|
|
5625
5180
|
} catch (err) {
|
|
5626
|
-
|
|
5181
|
+
logger23.warn("Failed to remove labels on cancel", { issueIid, error: err.message });
|
|
5627
5182
|
}
|
|
5628
5183
|
await this.mainGitMutex.runExclusive(async () => {
|
|
5629
5184
|
await this.cleanupWorktree(wtCtx);
|
|
@@ -5640,7 +5195,7 @@ var PipelineOrchestrator = class {
|
|
|
5640
5195
|
this.tracker.clearProcessingLock(issueIid);
|
|
5641
5196
|
this.tracker.updateState(issueIid, "skipped" /* Skipped */);
|
|
5642
5197
|
await this.cleanupE2eOutputs(issueIid);
|
|
5643
|
-
|
|
5198
|
+
logger23.info("Issue cancelled", { issueIid });
|
|
5644
5199
|
}
|
|
5645
5200
|
/**
|
|
5646
5201
|
* Remove the E2E output directory for an issue: {uatVendorDir}/outputs/issue-{iid}
|
|
@@ -5648,13 +5203,13 @@ var PipelineOrchestrator = class {
|
|
|
5648
5203
|
async cleanupE2eOutputs(issueIid) {
|
|
5649
5204
|
const vendorDir = this.config.e2e.uatVendorDir;
|
|
5650
5205
|
if (!vendorDir) return;
|
|
5651
|
-
const abs =
|
|
5652
|
-
const outputDir =
|
|
5206
|
+
const abs = path11.isAbsolute(vendorDir) ? vendorDir : path11.resolve(this.config.project.workDir, vendorDir);
|
|
5207
|
+
const outputDir = path11.join(abs, "outputs", `issue-${issueIid}`);
|
|
5653
5208
|
try {
|
|
5654
|
-
await
|
|
5655
|
-
|
|
5209
|
+
await fs9.rm(outputDir, { recursive: true, force: true });
|
|
5210
|
+
logger23.info("E2E outputs cleaned up", { issueIid, dir: outputDir });
|
|
5656
5211
|
} catch (err) {
|
|
5657
|
-
|
|
5212
|
+
logger23.warn("Failed to cleanup E2E outputs", { issueIid, dir: outputDir, error: err.message });
|
|
5658
5213
|
}
|
|
5659
5214
|
}
|
|
5660
5215
|
/**
|
|
@@ -5666,10 +5221,10 @@ var PipelineOrchestrator = class {
|
|
|
5666
5221
|
if (!this.workspaceManager) return;
|
|
5667
5222
|
const wsRoot = this.workspaceManager.getWorkspaceRoot(issueIid);
|
|
5668
5223
|
try {
|
|
5669
|
-
await
|
|
5670
|
-
|
|
5224
|
+
await fs9.rm(wsRoot, { recursive: true, force: true });
|
|
5225
|
+
logger23.info("Workspace root cleaned up", { issueIid, dir: wsRoot });
|
|
5671
5226
|
} catch (err) {
|
|
5672
|
-
|
|
5227
|
+
logger23.warn("Failed to cleanup workspace root", { issueIid, dir: wsRoot, error: err.message });
|
|
5673
5228
|
}
|
|
5674
5229
|
}
|
|
5675
5230
|
retryFromPhase(issueIid, phase) {
|
|
@@ -5685,7 +5240,7 @@ var PipelineOrchestrator = class {
|
|
|
5685
5240
|
this.aiRunner.killByWorkDir(wtCtx.workDir);
|
|
5686
5241
|
}
|
|
5687
5242
|
this.e2eAiRunner?.killByWorkDir(wtCtx.workDir);
|
|
5688
|
-
|
|
5243
|
+
logger23.info("Retrying issue from phase", { issueIid, phase });
|
|
5689
5244
|
const ok = this.tracker.resetToPhase(issueIid, phase, issueDef);
|
|
5690
5245
|
if (!ok) {
|
|
5691
5246
|
throw new InvalidPhaseError(phase);
|
|
@@ -5712,7 +5267,7 @@ var PipelineOrchestrator = class {
|
|
|
5712
5267
|
} else {
|
|
5713
5268
|
this.tracker.pauseIssue(issueIid, record.currentPhase ?? "");
|
|
5714
5269
|
}
|
|
5715
|
-
|
|
5270
|
+
logger23.info("Issue abort requested", { issueIid, state: record.state });
|
|
5716
5271
|
}
|
|
5717
5272
|
continueIssue(issueIid) {
|
|
5718
5273
|
const record = this.tracker.get(issueIid);
|
|
@@ -5722,7 +5277,7 @@ var PipelineOrchestrator = class {
|
|
|
5722
5277
|
}
|
|
5723
5278
|
const issueDef = this.getIssueSpecificPipelineDef(record);
|
|
5724
5279
|
this.tracker.resumeFromPause(issueIid, issueDef, false);
|
|
5725
|
-
|
|
5280
|
+
logger23.info("Issue continued from pause", { issueIid });
|
|
5726
5281
|
}
|
|
5727
5282
|
redoPhase(issueIid) {
|
|
5728
5283
|
const record = this.tracker.get(issueIid);
|
|
@@ -5766,7 +5321,7 @@ var PipelineOrchestrator = class {
|
|
|
5766
5321
|
}
|
|
5767
5322
|
this.eventBus.emitTyped("issue:redone", { issueIid });
|
|
5768
5323
|
}
|
|
5769
|
-
|
|
5324
|
+
logger23.info("Issue redo requested", { issueIid, state: record.state });
|
|
5770
5325
|
}
|
|
5771
5326
|
/**
|
|
5772
5327
|
* 处理中止/重做的共享逻辑:
|
|
@@ -5839,7 +5394,7 @@ var PipelineOrchestrator = class {
|
|
|
5839
5394
|
async _processIssueImpl(issue) {
|
|
5840
5395
|
const branchName = `${this.config.project.branchPrefix}-${issue.iid}`;
|
|
5841
5396
|
const wtCtx = this.computeWorktreeContext(issue.iid, branchName);
|
|
5842
|
-
|
|
5397
|
+
logger23.info("Processing issue", {
|
|
5843
5398
|
iid: issue.iid,
|
|
5844
5399
|
title: issue.title,
|
|
5845
5400
|
branchName,
|
|
@@ -5946,7 +5501,7 @@ var PipelineOrchestrator = class {
|
|
|
5946
5501
|
title,
|
|
5947
5502
|
description
|
|
5948
5503
|
});
|
|
5949
|
-
|
|
5504
|
+
logger23.info("Merge request created successfully", {
|
|
5950
5505
|
iid: issue.iid,
|
|
5951
5506
|
mrIid: mr.iid,
|
|
5952
5507
|
mrUrl: mr.web_url
|
|
@@ -5954,7 +5509,7 @@ var PipelineOrchestrator = class {
|
|
|
5954
5509
|
return { url: mr.web_url, iid: mr.iid };
|
|
5955
5510
|
} catch (err) {
|
|
5956
5511
|
const errorMsg = err.message;
|
|
5957
|
-
|
|
5512
|
+
logger23.warn("Failed to create merge request, trying to find existing one", {
|
|
5958
5513
|
iid: issue.iid,
|
|
5959
5514
|
error: errorMsg
|
|
5960
5515
|
});
|
|
@@ -5971,7 +5526,7 @@ var PipelineOrchestrator = class {
|
|
|
5971
5526
|
this.config.project.baseBranch
|
|
5972
5527
|
);
|
|
5973
5528
|
if (existing) {
|
|
5974
|
-
|
|
5529
|
+
logger23.info("Found existing merge request", {
|
|
5975
5530
|
iid: issueIid,
|
|
5976
5531
|
mrIid: existing.iid,
|
|
5977
5532
|
mrUrl: existing.web_url
|
|
@@ -5979,7 +5534,7 @@ var PipelineOrchestrator = class {
|
|
|
5979
5534
|
return { url: existing.web_url, iid: existing.iid };
|
|
5980
5535
|
}
|
|
5981
5536
|
} catch (findErr) {
|
|
5982
|
-
|
|
5537
|
+
logger23.warn("Failed to find existing merge request", {
|
|
5983
5538
|
iid: issueIid,
|
|
5984
5539
|
error: findErr.message
|
|
5985
5540
|
});
|
|
@@ -6024,7 +5579,7 @@ var PipelineOrchestrator = class {
|
|
|
6024
5579
|
});
|
|
6025
5580
|
return ports;
|
|
6026
5581
|
} catch (err) {
|
|
6027
|
-
|
|
5582
|
+
logger23.error("Failed to start preview servers", {
|
|
6028
5583
|
iid: issue.iid,
|
|
6029
5584
|
error: err.message
|
|
6030
5585
|
});
|
|
@@ -6059,7 +5614,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6059
5614
|
await this.mainGitMutex.runExclusive(async () => {
|
|
6060
5615
|
await this.cleanupWorktree(wtCtx);
|
|
6061
5616
|
});
|
|
6062
|
-
|
|
5617
|
+
logger23.info("Preview stopped and worktree cleaned", { iid: issueIid });
|
|
6063
5618
|
}
|
|
6064
5619
|
async markDeployed(issueIid) {
|
|
6065
5620
|
const record = this.tracker.get(issueIid);
|
|
@@ -6076,7 +5631,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6076
5631
|
try {
|
|
6077
5632
|
await this.gongfeng.closeIssue(externalId);
|
|
6078
5633
|
} catch (err) {
|
|
6079
|
-
|
|
5634
|
+
logger23.warn("Failed to close issue on Gongfeng", { iid: issueIid, error: err.message });
|
|
6080
5635
|
}
|
|
6081
5636
|
try {
|
|
6082
5637
|
const issue = await this.gongfeng.getIssueDetail(externalId);
|
|
@@ -6084,10 +5639,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6084
5639
|
labels.push("auto-finish:deployed");
|
|
6085
5640
|
await this.gongfeng.updateIssueLabels(externalId, labels);
|
|
6086
5641
|
} catch (err) {
|
|
6087
|
-
|
|
5642
|
+
logger23.warn("Failed to update labels", { iid: issueIid, error: err.message });
|
|
6088
5643
|
}
|
|
6089
5644
|
this.tracker.updateState(issueIid, "deployed" /* Deployed */);
|
|
6090
|
-
|
|
5645
|
+
logger23.info("Issue marked as deployed", { iid: issueIid });
|
|
6091
5646
|
}
|
|
6092
5647
|
async restartPreview(issueIid) {
|
|
6093
5648
|
const record = this.tracker.get(issueIid);
|
|
@@ -6114,7 +5669,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6114
5669
|
throw err;
|
|
6115
5670
|
}
|
|
6116
5671
|
const url = this.buildPreviewUrl(issueIid);
|
|
6117
|
-
|
|
5672
|
+
logger23.info("Preview restarted", { iid: issueIid, url });
|
|
6118
5673
|
return url;
|
|
6119
5674
|
}
|
|
6120
5675
|
getPreviewHost() {
|
|
@@ -6147,7 +5702,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6147
5702
|
if (!record) throw new IssueNotFoundError(issueIid);
|
|
6148
5703
|
const baseBranch = this.config.project.baseBranch;
|
|
6149
5704
|
const branchName = record.branchName;
|
|
6150
|
-
|
|
5705
|
+
logger23.info("Starting conflict resolution", { issueIid, branchName, baseBranch });
|
|
6151
5706
|
this.tracker.updateState(issueIid, "resolving_conflict" /* ResolvingConflict */);
|
|
6152
5707
|
this.eventBus.emitTyped("conflict:started", { issueIid });
|
|
6153
5708
|
try {
|
|
@@ -6180,7 +5735,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6180
5735
|
});
|
|
6181
5736
|
}
|
|
6182
5737
|
});
|
|
6183
|
-
|
|
5738
|
+
logger23.info("Running verification after conflict resolution", { issueIid });
|
|
6184
5739
|
const wtPlan = new PlanPersistence(wtCtx.workDir, issueIid);
|
|
6185
5740
|
wtPlan.ensureDir();
|
|
6186
5741
|
const verifyPhase = createPhase("verify", this.aiRunner, wtGit, wtPlan, this.config);
|
|
@@ -6218,10 +5773,10 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6218
5773
|
} catch {
|
|
6219
5774
|
}
|
|
6220
5775
|
await this.commentOnMr(record.mrUrl, t("conflict.mrResolvedComment"));
|
|
6221
|
-
|
|
5776
|
+
logger23.info("Conflict resolution completed", { issueIid });
|
|
6222
5777
|
} catch (err) {
|
|
6223
5778
|
const errorMsg = err.message;
|
|
6224
|
-
|
|
5779
|
+
logger23.error("Conflict resolution failed", { issueIid, error: errorMsg });
|
|
6225
5780
|
try {
|
|
6226
5781
|
const wtGit = new GitOperations(wtCtx.gitRootDir);
|
|
6227
5782
|
if (await wtGit.isRebaseInProgress()) {
|
|
@@ -6251,7 +5806,7 @@ E2E \u6D4B\u8BD5\u5C06\u5C1D\u8BD5\u4F7F\u7528 config.json \u4E2D\u7684\u9ED8\u8
|
|
|
6251
5806
|
try {
|
|
6252
5807
|
await this.gongfeng.createMergeRequestNote(mrIid, body);
|
|
6253
5808
|
} catch (err) {
|
|
6254
|
-
|
|
5809
|
+
logger23.warn("Failed to comment on MR", { mrIid, error: err.message });
|
|
6255
5810
|
}
|
|
6256
5811
|
}
|
|
6257
5812
|
};
|
|
@@ -6327,7 +5882,7 @@ ${questions}
|
|
|
6327
5882
|
}
|
|
6328
5883
|
|
|
6329
5884
|
// src/services/BrainstormService.ts
|
|
6330
|
-
var
|
|
5885
|
+
var logger24 = logger.child("Brainstorm");
|
|
6331
5886
|
function agentConfigToAIConfig(agentCfg, timeoutMs) {
|
|
6332
5887
|
return {
|
|
6333
5888
|
mode: agentCfg.mode,
|
|
@@ -6363,7 +5918,7 @@ var BrainstormService = class {
|
|
|
6363
5918
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6364
5919
|
};
|
|
6365
5920
|
this.sessions.set(session.id, session);
|
|
6366
|
-
|
|
5921
|
+
logger24.info("Created brainstorm session", { sessionId: session.id });
|
|
6367
5922
|
return session;
|
|
6368
5923
|
}
|
|
6369
5924
|
getSession(id) {
|
|
@@ -6372,7 +5927,7 @@ var BrainstormService = class {
|
|
|
6372
5927
|
async generate(sessionId, onEvent) {
|
|
6373
5928
|
const session = this.requireSession(sessionId);
|
|
6374
5929
|
session.status = "generating";
|
|
6375
|
-
|
|
5930
|
+
logger24.info("Generating SDD", { sessionId });
|
|
6376
5931
|
const prompt = buildGeneratePrompt(session.transcript);
|
|
6377
5932
|
const result = await this.generatorRunner.run({
|
|
6378
5933
|
prompt,
|
|
@@ -6398,7 +5953,7 @@ var BrainstormService = class {
|
|
|
6398
5953
|
const session = this.requireSession(sessionId);
|
|
6399
5954
|
const roundNum = session.rounds.length + 1;
|
|
6400
5955
|
session.status = "reviewing";
|
|
6401
|
-
|
|
5956
|
+
logger24.info("Reviewing SDD", { sessionId, round: roundNum });
|
|
6402
5957
|
onEvent?.({ type: "round:start", data: { round: roundNum, phase: "review" }, round: roundNum });
|
|
6403
5958
|
const prompt = buildReviewPrompt(session.currentSdd, roundNum);
|
|
6404
5959
|
const result = await this.reviewerRunner.run({
|
|
@@ -6431,7 +5986,7 @@ var BrainstormService = class {
|
|
|
6431
5986
|
throw new Error("No review round to refine from");
|
|
6432
5987
|
}
|
|
6433
5988
|
session.status = "refining";
|
|
6434
|
-
|
|
5989
|
+
logger24.info("Refining SDD", { sessionId, round: currentRound.round });
|
|
6435
5990
|
const prompt = buildRefinePrompt(currentRound.questions);
|
|
6436
5991
|
const result = await this.generatorRunner.run({
|
|
6437
5992
|
prompt,
|
|
@@ -6518,4 +6073,4 @@ export {
|
|
|
6518
6073
|
PipelineOrchestrator,
|
|
6519
6074
|
BrainstormService
|
|
6520
6075
|
};
|
|
6521
|
-
//# sourceMappingURL=chunk-
|
|
6076
|
+
//# sourceMappingURL=chunk-MTXTSSBH.js.map
|