fifony 0.1.35 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/app/dist/assets/{CommandPalette-Du7aS97R.js → CommandPalette-jM1LVTly.js} +1 -1
  2. package/app/dist/assets/{KeyboardShortcutsHelp-mguoBIXk.js → KeyboardShortcutsHelp-DuMbbWoK.js} +1 -1
  3. package/app/dist/assets/OnboardingWizard-CRNV-Tiy.js +1 -0
  4. package/app/dist/assets/{analytics.lazy-CKd9S45z.js → analytics.lazy-BoFSI6cI.js} +1 -1
  5. package/app/dist/assets/index-BWB0OQnx.css +1 -0
  6. package/app/dist/assets/index-ChQVBIk9.js +54 -0
  7. package/app/dist/index.html +2 -2
  8. package/app/dist/service-worker.js +1 -1
  9. package/dist/agent/run-local.js +6 -6
  10. package/dist/{agent-QWN3JVX5.js → agent-B3GQHWDL.js} +7 -7
  11. package/dist/{chunk-OONOOWNC.js → chunk-37N5OFHM.js} +4 -2
  12. package/dist/{chunk-KRH4LWCW.js → chunk-4RUGXGUX.js} +44 -9
  13. package/dist/{chunk-2ZK3IUJQ.js → chunk-6BUKDXKZ.js} +12 -12
  14. package/dist/{chunk-GW7LVDP3.js → chunk-AZYLLJVZ.js} +44 -12
  15. package/dist/{chunk-O5AEQXUV.js → chunk-T2YJOZ6N.js} +2 -2
  16. package/dist/{chunk-TSSVMTCS.js → chunk-YJHD4BE4.js} +541 -866
  17. package/dist/cli.js +6 -6
  18. package/dist/issue-runner-PNYGUY3S.js +15 -0
  19. package/dist/{issue-state-machine-6Y2OTUGI.js → issue-state-machine-NCD3ZFOI.js} +5 -5
  20. package/dist/{issues-QSMDQQFO.js → issues-IW6CFOSR.js} +7 -9
  21. package/dist/mcp/server.js +2 -2
  22. package/dist/{queue-workers-TDJWHJMJ.js → queue-workers-2NJZE6L2.js} +3 -3
  23. package/dist/{scheduler-LWCF7U6Q.js → scheduler-YMQ3JZUU.js} +7 -7
  24. package/dist/{store-DSMN6IKR.js → store-GZVWNJ6V.js} +7 -7
  25. package/dist/{workspace-S5F5JFM6.js → workspace-HJEJZMTO.js} +4 -4
  26. package/package.json +1 -1
  27. package/app/dist/assets/OnboardingWizard-dOrVz-hX.js +0 -1
  28. package/app/dist/assets/index-nWJMQsCn.js +0 -49
  29. package/app/dist/assets/index-rLcPCr9E.css +0 -1
  30. package/dist/issue-runner-QGJY5COY.js +0 -15
@@ -21,7 +21,7 @@ import {
21
21
  snapshotAndClearDirtyEventIds,
22
22
  snapshotAndClearDirtyIssueIds,
23
23
  snapshotAndClearDirtyIssuePlanIds
24
- } from "./chunk-KRH4LWCW.js";
24
+ } from "./chunk-4RUGXGUX.js";
25
25
  import {
26
26
  ADAPTERS,
27
27
  assertIssueHasGitWorktree,
@@ -49,10 +49,9 @@ import {
49
49
  parseDiffStats,
50
50
  prepareWorkspace,
51
51
  readCodexConfig,
52
- resolveAgentCommand,
53
52
  runCommandWithTimeout,
54
53
  runHook
55
- } from "./chunk-GW7LVDP3.js";
54
+ } from "./chunk-AZYLLJVZ.js";
56
55
  import {
57
56
  appendFileTail,
58
57
  clamp,
@@ -74,15 +73,14 @@ import {
74
73
  toStringArray,
75
74
  toStringValue,
76
75
  withRetryBackoff
77
- } from "./chunk-O5AEQXUV.js";
76
+ } from "./chunk-T2YJOZ6N.js";
78
77
  import {
79
78
  enqueue
80
- } from "./chunk-2ZK3IUJQ.js";
79
+ } from "./chunk-6BUKDXKZ.js";
81
80
  import {
82
81
  logger
83
82
  } from "./chunk-DVU3CXWA.js";
84
83
  import {
85
- ALLOWED_STATES,
86
84
  ATTACHMENTS_ROOT,
87
85
  COMPLETED_STATES,
88
86
  EXECUTING_STATES,
@@ -94,6 +92,7 @@ import {
94
92
  FRONTEND_OFFLINE_HTML,
95
93
  FRONTEND_SERVICE_WORKER_JS,
96
94
  PERSIST_EVENTS_MAX,
95
+ QUIET_MODE,
97
96
  S3DB_AGENT_PIPELINE_RESOURCE,
98
97
  S3DB_AGENT_SESSION_RESOURCE,
99
98
  S3DB_DATABASE_PATH,
@@ -109,7 +108,7 @@ import {
109
108
  TARGET_ROOT,
110
109
  TERMINAL_STATES,
111
110
  WORKSPACE_ROOT
112
- } from "./chunk-OONOOWNC.js";
111
+ } from "./chunk-37N5OFHM.js";
113
112
 
114
113
  // src/agents/issue-runner.ts
115
114
  import {
@@ -293,20 +292,17 @@ function getAnalytics(topN = 20) {
293
292
  }
294
293
 
295
294
  // src/domains/project.ts
296
- import { execFileSync as execFileSync2, spawn as spawn3 } from "child_process";
295
+ import { execFileSync as execFileSync2 } from "child_process";
297
296
  import { createHash } from "crypto";
298
297
  import {
299
298
  existsSync as existsSync13,
300
299
  mkdirSync as mkdirSync7,
301
- mkdtempSync as mkdtempSync4,
302
300
  readdirSync as readdirSync4,
303
301
  readFileSync as readFileSync10,
304
- rmSync as rmSync5,
305
302
  writeFileSync as writeFileSync11
306
303
  } from "fs";
307
- import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
304
+ import { homedir as homedir3 } from "os";
308
305
  import { basename as basename4, dirname as dirname2, join as join16, relative as relativePath, resolve as resolve2 } from "path";
309
- import { env as env3 } from "process";
310
306
 
311
307
  // src/persistence/plugins/api-runtime-context.ts
312
308
  var context = null;
@@ -1726,9 +1722,279 @@ async function cancelIssueCommand(input, deps) {
1726
1722
  { issue, target: "Cancelled", note: "Manual cancel requested." },
1727
1723
  deps
1728
1724
  );
1725
+ if (deps.state) {
1726
+ try {
1727
+ await cleanWorkspace(issue.id, issue, deps.state);
1728
+ issue.workspacePath = void 0;
1729
+ issue.worktreePath = void 0;
1730
+ } catch (error) {
1731
+ logger.warn({ issueId: issue.id, err: String(error) }, "[Command] Failed to clean workspace during cancel");
1732
+ }
1733
+ }
1729
1734
  deps.eventStore.addEvent(issue.id, "manual", `Manual cancel requested for ${issue.id}.`);
1730
1735
  }
1731
1736
 
1737
+ // src/commands/merge-workspace.command.ts
1738
+ import { existsSync as existsSync6 } from "fs";
1739
+ import { execSync } from "child_process";
1740
+
1741
+ // src/domains/validation.ts
1742
+ import { execFile } from "child_process";
1743
+ async function runValidationGate(issue, config) {
1744
+ if (!config.testCommand) return null;
1745
+ const cwd = issue.worktreePath ?? issue.workspacePath;
1746
+ if (!cwd) {
1747
+ logger.warn({ issueId: issue.id }, "[Validation] No workspace path \u2014 skipping gate");
1748
+ return null;
1749
+ }
1750
+ const command = config.testCommand;
1751
+ logger.info({ issueId: issue.id, command, cwd }, "[Validation] Running validation gate");
1752
+ return new Promise((resolve3) => {
1753
+ const child = execFile("sh", ["-c", command], {
1754
+ cwd,
1755
+ encoding: "utf8",
1756
+ timeout: 3e5,
1757
+ maxBuffer: 2 * 1024 * 1024
1758
+ }, (err, stdout, stderr) => {
1759
+ const combined = (stdout || "") + (stderr || "");
1760
+ if (!err) {
1761
+ logger.info({ issueId: issue.id }, "[Validation] Gate passed");
1762
+ resolve3({
1763
+ passed: true,
1764
+ output: combined.slice(-2048),
1765
+ command,
1766
+ ranAt: (/* @__PURE__ */ new Date()).toISOString()
1767
+ });
1768
+ return;
1769
+ }
1770
+ logger.warn({ issueId: issue.id, exitCode: err.code }, "[Validation] Gate failed");
1771
+ resolve3({
1772
+ passed: false,
1773
+ output: combined.slice(-2048) || String(err).slice(0, 2048),
1774
+ command,
1775
+ ranAt: (/* @__PURE__ */ new Date()).toISOString()
1776
+ });
1777
+ });
1778
+ });
1779
+ }
1780
+
1781
+ // src/commands/merge-workspace.command.ts
1782
+ async function mergeWorkspaceCommand(input, deps) {
1783
+ const { issue, state, squashAlreadyApplied } = input;
1784
+ if (!["Approved", "Reviewing", "PendingDecision"].includes(issue.state)) {
1785
+ throw new Error(`Issue ${issue.identifier} is in state ${issue.state}. Merge is only allowed in Reviewing, PendingDecision, or Approved state.`);
1786
+ }
1787
+ ensureGitRepoReadyForWorktrees(TARGET_ROOT, "merge issues");
1788
+ if (issue.state === "Reviewing" || issue.state === "PendingDecision") {
1789
+ await transitionIssueCommand(
1790
+ { issue, target: "Approved", note: "Approved and merged by user." },
1791
+ deps
1792
+ );
1793
+ }
1794
+ const wp = issue.worktreePath ?? issue.workspacePath;
1795
+ if (!wp || !existsSync6(wp)) {
1796
+ throw new Error(`No mergeable workspace found for ${issue.identifier}. This issue likely ran before git was initialized for the project. Re-run the issue after git setup.`);
1797
+ }
1798
+ if (issue.branchName && issue.baseBranch) {
1799
+ try {
1800
+ const stat = execSync(
1801
+ `git diff --stat "${issue.baseBranch}"..."${issue.branchName}"`,
1802
+ { encoding: "utf8", cwd: TARGET_ROOT, stdio: "pipe", maxBuffer: 512e3, timeout: 1e4 }
1803
+ );
1804
+ parseDiffStats(issue, stat);
1805
+ logger.info({ issueId: issue.id, linesAdded: issue.linesAdded, linesRemoved: issue.linesRemoved, filesChanged: issue.filesChanged }, "[Merge] Diff stats computed");
1806
+ } catch (err) {
1807
+ logger.warn({ err: String(err), issueId: issue.id }, "[Merge] Failed to compute diff stats");
1808
+ }
1809
+ }
1810
+ const validation = await runValidationGate(issue, state.config);
1811
+ if (validation) {
1812
+ issue.validationResult = validation;
1813
+ if (!validation.passed) {
1814
+ throw new Error(`Validation gate failed (${validation.command}): ${validation.output.slice(0, 500)}`);
1815
+ }
1816
+ }
1817
+ let result;
1818
+ if (squashAlreadyApplied) {
1819
+ try {
1820
+ execSync("git add -A", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 1e4 });
1821
+ execSync(
1822
+ `git commit -m "fifony: merge ${issue.identifier}"`,
1823
+ { cwd: TARGET_ROOT, stdio: "pipe", timeout: 1e4 }
1824
+ );
1825
+ logger.info({ issueId: issue.id }, "[Merge] Committed existing test squash");
1826
+ } catch (err) {
1827
+ throw new Error(`Failed to commit test squash: ${err.stderr || err.stdout || String(err)}`);
1828
+ }
1829
+ issue.testApplied = false;
1830
+ result = { copied: [], deleted: [], skipped: [], conflicts: [] };
1831
+ } else {
1832
+ try {
1833
+ const indexStatus = execSync("git diff --cached --name-only", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
1834
+ const wtStatus = execSync("git diff --name-only", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
1835
+ if (indexStatus && !wtStatus) {
1836
+ execSync("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe" });
1837
+ logger.info({ issueId: issue.id }, "[Command] Cleared residual squash from index before merge");
1838
+ }
1839
+ } catch {
1840
+ }
1841
+ const mergeResult = mergeWorkspace(issue);
1842
+ result = mergeResult;
1843
+ }
1844
+ issue.mergeResult = {
1845
+ copied: result.copied.length,
1846
+ deleted: result.deleted.length,
1847
+ skipped: result.skipped.length,
1848
+ conflicts: result.conflicts.length,
1849
+ conflictFiles: result.conflicts.length > 0 ? result.conflicts : void 0
1850
+ };
1851
+ if (result.conflicts.length > 0) {
1852
+ deps.eventStore.addEvent(issue.id, "error", `Merge conflicts: ${result.conflicts.join(", ")}`);
1853
+ await deps.persistencePort.persistState(state);
1854
+ return result;
1855
+ }
1856
+ if (!issue.mergedReason) issue.mergedReason = squashAlreadyApplied ? "Approved and shipped after testing." : "Merged by user.";
1857
+ await transitionIssueCommand(
1858
+ { issue, target: "Merged", note: `Workspace merged for ${issue.identifier}.` },
1859
+ deps
1860
+ );
1861
+ if (issue.workspacePath) {
1862
+ try {
1863
+ await cleanWorkspace(issue.id, issue, state);
1864
+ issue.workspacePath = void 0;
1865
+ issue.worktreePath = void 0;
1866
+ } catch {
1867
+ }
1868
+ }
1869
+ await deps.persistencePort.persistState(state);
1870
+ return result;
1871
+ }
1872
+
1873
+ // src/commands/push-workspace.command.ts
1874
+ import { execFileSync, execSync as execSync2 } from "child_process";
1875
+ function isGhAvailable() {
1876
+ try {
1877
+ execFileSync("gh", ["--version"], { stdio: "pipe", timeout: 5e3 });
1878
+ return true;
1879
+ } catch {
1880
+ return false;
1881
+ }
1882
+ }
1883
+ function getCompareUrl(branchName, baseBranch) {
1884
+ try {
1885
+ const remote = execSync2("git remote get-url origin", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
1886
+ const cleanRemote = remote.replace(/\.git$/, "");
1887
+ return `${cleanRemote}/compare/${baseBranch}...${branchName}`;
1888
+ } catch {
1889
+ return `(branch pushed: ${branchName})`;
1890
+ }
1891
+ }
1892
+ function findExistingPr(branchName) {
1893
+ try {
1894
+ const result = execFileSync(
1895
+ "gh",
1896
+ ["pr", "view", branchName, "--json", "url,state", "--jq", 'select(.state == "OPEN") | .url'],
1897
+ { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe", timeout: 15e3 }
1898
+ ).trim();
1899
+ return result || null;
1900
+ } catch {
1901
+ return null;
1902
+ }
1903
+ }
1904
+ function createPr(branchName, baseBranch, title, body) {
1905
+ return execFileSync(
1906
+ "gh",
1907
+ ["pr", "create", "--head", branchName, "--base", baseBranch, "--title", title, "--body", body],
1908
+ { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe", timeout: 3e4 }
1909
+ ).trim();
1910
+ }
1911
+ async function pushWorkspaceCommand(input, deps) {
1912
+ const { issue, state } = input;
1913
+ if (!["Approved", "Reviewing", "PendingDecision"].includes(issue.state)) {
1914
+ throw new Error(`Issue ${issue.identifier} is in state ${issue.state}. Push is only allowed in Reviewing, PendingDecision, or Approved state.`);
1915
+ }
1916
+ ensureGitRepoReadyForWorktrees(TARGET_ROOT, "push issue branches");
1917
+ assertIssueHasGitWorktree(issue, "push");
1918
+ if (issue.testApplied) {
1919
+ try {
1920
+ execSync2("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
1921
+ execSync2("git clean -fd", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
1922
+ issue.testApplied = false;
1923
+ deps.eventStore.addEvent(issue.id, "info", "Auto-reverted test squash before push.");
1924
+ logger.info({ issueId: issue.id }, "[Push] Auto-reverted test squash before push");
1925
+ } catch (err) {
1926
+ const msg = err.stderr || err.stdout || String(err);
1927
+ throw new Error(`Failed to revert test squash before push: ${msg}`);
1928
+ }
1929
+ }
1930
+ if (issue.state === "Reviewing" || issue.state === "PendingDecision") {
1931
+ await transitionIssueCommand(
1932
+ { issue, target: "Approved", note: "Approved and pushed by user." },
1933
+ deps
1934
+ );
1935
+ }
1936
+ ensureWorktreeCommitted(issue);
1937
+ const validation = await runValidationGate(issue, state.config);
1938
+ if (validation) {
1939
+ issue.validationResult = validation;
1940
+ if (!validation.passed) {
1941
+ throw new Error(`Validation gate failed (${validation.command}): ${validation.output.slice(0, 500)}`);
1942
+ }
1943
+ }
1944
+ computeDiffStats(issue);
1945
+ const planSummary = issue.plan?.summary ?? issue.title;
1946
+ let diffStat = "";
1947
+ try {
1948
+ diffStat = execSync2(
1949
+ `git diff --stat "${issue.baseBranch}"..."${issue.branchName}"`,
1950
+ { cwd: TARGET_ROOT, encoding: "utf8", maxBuffer: 512e3, timeout: 1e4, stdio: "pipe" }
1951
+ ).trim();
1952
+ } catch {
1953
+ }
1954
+ const body = `## Summary
1955
+ ${planSummary}
1956
+
1957
+ ## Diff Stats
1958
+ \`\`\`
1959
+ ${diffStat || "No diff stats available"}
1960
+ \`\`\`
1961
+
1962
+ *Automated by fifony*`;
1963
+ execSync2(`git push -u origin "${issue.branchName}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
1964
+ const prBase = state.config.prBaseBranch || issue.baseBranch;
1965
+ const ghAvailable = isGhAvailable();
1966
+ let prUrl;
1967
+ if (!ghAvailable) {
1968
+ prUrl = getCompareUrl(issue.branchName, prBase);
1969
+ logger.info({ issueId: issue.id, prUrl }, "[Push] gh CLI not available \u2014 using compare URL");
1970
+ } else {
1971
+ const existingUrl = findExistingPr(issue.branchName);
1972
+ if (existingUrl) {
1973
+ prUrl = existingUrl;
1974
+ logger.info({ issueId: issue.id, prUrl }, "[Push] Existing open PR found");
1975
+ } else {
1976
+ try {
1977
+ prUrl = createPr(issue.branchName, prBase, issue.title, body);
1978
+ logger.info({ issueId: issue.id, prUrl }, "[Push] PR created");
1979
+ } catch (err) {
1980
+ const ghError = (err.stderr || err.stdout || String(err)).toString().slice(0, 500);
1981
+ logger.error({ issueId: issue.id, ghError }, "[Push] gh pr create failed");
1982
+ prUrl = getCompareUrl(issue.branchName, prBase);
1983
+ deps.eventStore.addEvent(issue.id, "error", `gh pr create failed: ${ghError}. Branch was pushed \u2014 use the compare URL to create the PR manually.`);
1984
+ }
1985
+ }
1986
+ }
1987
+ issue.prUrl = prUrl;
1988
+ if (!issue.mergedReason) issue.mergedReason = "Pushed to origin and PR created.";
1989
+ await transitionIssueCommand(
1990
+ { issue, target: "Merged", note: `Branch ${issue.branchName} pushed. PR: ${prUrl}` },
1991
+ deps
1992
+ );
1993
+ deps.eventStore.addEvent(issue.id, "merge", `PR created: ${prUrl}`);
1994
+ await deps.persistencePort.persistState(state);
1995
+ return { prUrl, ghAvailable };
1996
+ }
1997
+
1732
1998
  // src/persistence/resources/issues.resource.ts
1733
1999
  function getIssueId(c) {
1734
2000
  if (!c || typeof c !== "object" || !("req" in c) || !c.req || typeof c.req !== "object") {
@@ -1781,7 +2047,18 @@ async function patchIssueState(c) {
1781
2047
  }
1782
2048
  try {
1783
2049
  const payload = await c.req.json();
1784
- await handleStatePatch(context2.state, issue, payload);
2050
+ const nextState = parseIssueState(payload.state);
2051
+ if (!nextState) {
2052
+ throw new Error(`Unsupported state: ${String(payload.state)}`);
2053
+ }
2054
+ const container = getContainer();
2055
+ const reason = payload.reason ? toStringValue(payload.reason) : void 0;
2056
+ logger.info({ issueId, identifier: issue.identifier, targetState: nextState }, "[API] POST /api/issues/:id/state");
2057
+ await transitionIssueCommand({
2058
+ issue,
2059
+ target: nextState,
2060
+ note: reason || `Manual state update: ${nextState}`
2061
+ }, container);
1785
2062
  await persistState(context2.state);
1786
2063
  return { body: { ok: true, issue } };
1787
2064
  } catch (error) {
@@ -1798,17 +2075,41 @@ async function retryIssue(c) {
1798
2075
  if (!issue) {
1799
2076
  return { status: 404, body: { ok: false, error: "Issue not found" } };
1800
2077
  }
2078
+ let feedback;
2079
+ try {
2080
+ const body = await c.req.json();
2081
+ if (body?.feedback) feedback = toStringValue(body.feedback);
2082
+ } catch {
2083
+ }
1801
2084
  const container = getContainer();
2085
+ logger.info({ issueId, state: issue.state, lastFailedPhase: issue.lastFailedPhase, attempts: issue.attempts }, "[API] Retry \u2014 dispatching");
2086
+ const note = feedback ? `Rework requested for ${issue.identifier}: ${feedback.slice(0, 200)}` : `Manual retry for ${issue.identifier}.`;
1802
2087
  if (TERMINAL_STATES.has(issue.state)) {
2088
+ await transitionIssueCommand({ issue, target: "Planning", note }, container);
2089
+ if (issue.plan?.steps?.length) {
2090
+ await transitionIssueCommand({ issue, target: "PendingApproval", note: "Existing plan found." }, container);
2091
+ await transitionIssueCommand({ issue, target: "Queued", note: "Auto-queued after plan approval." }, container);
2092
+ }
2093
+ } else if (issue.state === "Blocked" && issue.lastFailedPhase === "review") {
1803
2094
  issue.lastError = void 0;
1804
- issue.nextRetryAt = void 0;
1805
- await transitionIssueCommand(
1806
- { issue, target: "Queued", note: "Manual retry requested." },
1807
- container
1808
- );
2095
+ issue.lastFailedPhase = void 0;
2096
+ await transitionIssueCommand({ issue, target: "Reviewing", note }, container);
2097
+ } else if (issue.state === "Blocked") {
2098
+ await transitionIssueCommand({ issue, target: "Queued", note }, container);
2099
+ } else if (issue.state === "Approved") {
2100
+ await transitionIssueCommand({ issue, target: "Planning", note }, container);
2101
+ if (issue.plan?.steps?.length) {
2102
+ await transitionIssueCommand({ issue, target: "PendingApproval", note: "Existing plan found." }, container);
2103
+ await transitionIssueCommand({ issue, target: "Queued", note: "Auto-queued for rework." }, container);
2104
+ }
2105
+ } else if (issue.state === "Reviewing" || issue.state === "PendingDecision") {
2106
+ const reworkNote = feedback || issue.lastError || "Manual rework request.";
2107
+ await transitionIssueCommand({ issue, target: "Queued", note: reworkNote }, container);
2108
+ } else if (issue.state === "PendingApproval") {
2109
+ await transitionIssueCommand({ issue, target: "Queued", note }, container);
1809
2110
  } else {
1810
- issue.nextRetryAt = void 0;
1811
2111
  issue.lastError = void 0;
2112
+ issue.nextRetryAt = void 0;
1812
2113
  issue.updatedAt = now();
1813
2114
  }
1814
2115
  addEvent(context2.state, issue.id, "manual", `Manual retry requested for ${issue.id}.`);
@@ -1841,19 +2142,57 @@ async function cancelIssue(c) {
1841
2142
  }
1842
2143
  await cancelIssueCommand(
1843
2144
  { issue },
1844
- getContainer()
2145
+ { ...getContainer(), state: context2.state }
1845
2146
  );
1846
2147
  addEvent(context2.state, issue.id, "manual", `Manual cancel requested for ${issue.id}.`);
1847
2148
  await persistState(context2.state);
1848
2149
  return { body: { ok: true, issue } };
1849
2150
  }
1850
- var issues_resource_default = {
1851
- name: S3DB_ISSUE_RESOURCE,
1852
- attributes: {
1853
- id: "string|required",
1854
- identifier: "string|required",
1855
- title: "string|required",
1856
- description: "string|optional",
2151
+ async function approveAndMerge(c) {
2152
+ const context2 = getApiRuntimeContextOrThrow();
2153
+ const issueId = getIssueId(c);
2154
+ if (!issueId) {
2155
+ return { status: 400, body: { ok: false, error: "Issue id is required." } };
2156
+ }
2157
+ const issue = findIssue(context2.state, issueId);
2158
+ if (!issue) {
2159
+ return { status: 404, body: { ok: false, error: "Issue not found" } };
2160
+ }
2161
+ if (issue.state !== "PendingDecision" && issue.state !== "Reviewing" && issue.state !== "Approved") {
2162
+ return { status: 400, body: { ok: false, error: `Cannot approve-and-merge from state ${issue.state}. Expected PendingDecision, Reviewing, or Approved.` } };
2163
+ }
2164
+ try {
2165
+ const container = getContainer();
2166
+ const mergeMode = context2.state.config.mergeMode;
2167
+ logger.info({ issueId, state: issue.state, testApplied: issue.testApplied, mergeMode }, "[API] POST /api/issues/:id/approve-and-merge");
2168
+ if (mergeMode === "push-pr") {
2169
+ if (issue.state !== "Approved") {
2170
+ await transitionIssueCommand(
2171
+ { issue, target: "Approved", note: "Approved for push-pr." },
2172
+ container
2173
+ );
2174
+ }
2175
+ await pushWorkspaceCommand({ issue }, { ...container, state: context2.state });
2176
+ } else {
2177
+ await mergeWorkspaceCommand(
2178
+ { issue, squashAlreadyApplied: issue.testApplied ?? false },
2179
+ { ...container, state: context2.state }
2180
+ );
2181
+ }
2182
+ addEvent(context2.state, issue.id, "manual", `Approved and ${mergeMode === "push-pr" ? "pushed PR for" : "merged"} ${issue.identifier}.`);
2183
+ await persistState(context2.state);
2184
+ return { body: { ok: true, issue } };
2185
+ } catch (error) {
2186
+ return { status: 409, body: { ok: false, error: error instanceof Error ? error.message : String(error) } };
2187
+ }
2188
+ }
2189
+ var issues_resource_default = {
2190
+ name: S3DB_ISSUE_RESOURCE,
2191
+ attributes: {
2192
+ id: "string|required",
2193
+ identifier: "string|required",
2194
+ title: "string|required",
2195
+ description: "string|optional",
1857
2196
  state: "string|required",
1858
2197
  branchName: "string|optional",
1859
2198
  url: "string|optional",
@@ -1886,6 +2225,7 @@ var issues_resource_default = {
1886
2225
  commandOutputTail: "string|optional",
1887
2226
  terminalWeek: "string|optional",
1888
2227
  usage: "json|optional",
2228
+ testApplied: "boolean|optional",
1889
2229
  tokenUsage: "json|optional",
1890
2230
  tokensByPhase: "json|optional",
1891
2231
  tokensByModel: "json|optional",
@@ -1965,6 +2305,13 @@ var issues_resource_default = {
1965
2305
  return c.json(result.body, result.status);
1966
2306
  }
1967
2307
  return result.body;
2308
+ },
2309
+ "POST /:id/approve-and-merge": async (c) => {
2310
+ const result = await approveAndMerge(c);
2311
+ if (result.status) {
2312
+ return c.json(result.body, result.status);
2313
+ }
2314
+ return result.body;
1968
2315
  }
1969
2316
  }
1970
2317
  };
@@ -2338,6 +2685,7 @@ async function ensureNotStale(state, staleTimeoutMs) {
2338
2685
  const staleMinutes = Math.round((Date.now() - Date.parse(issue.updatedAt)) / 6e4);
2339
2686
  const reason = `Stale execution \u2014 no updates for over ${staleMinutes} minute(s) in ${issue.state} state.`;
2340
2687
  logger.info({ issueId: issue.id, identifier: issue.identifier, state: issue.state, updatedAt: issue.updatedAt }, "[Scheduler] Recovering stale issue \u2192 Blocked");
2688
+ issue.lastFailedPhase = issue.state === "Reviewing" ? "review" : "execute";
2341
2689
  issue.attempts += 1;
2342
2690
  issue.nextRetryAt = getNextRetryAt(issue, state.config.retryDelayMs);
2343
2691
  issue.startedAt = void 0;
@@ -2352,13 +2700,13 @@ function hasTerminalQueue(state) {
2352
2700
  }
2353
2701
 
2354
2702
  // src/agents/providers-usage.ts
2355
- import { execFile } from "child_process";
2703
+ import { execFile as execFile2 } from "child_process";
2356
2704
  import { promisify } from "util";
2357
- import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync } from "fs";
2705
+ import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync } from "fs";
2358
2706
  import { join as join8, dirname } from "path";
2359
2707
  import { homedir as homedir2 } from "os";
2360
2708
  import { env } from "process";
2361
- var execFileAsync = promisify(execFile);
2709
+ var execFileAsync = promisify(execFile2);
2362
2710
  async function whichExists(cmd) {
2363
2711
  try {
2364
2712
  await execFileAsync("which", [cmd], { encoding: "utf8", timeout: 3e3 });
@@ -2393,7 +2741,7 @@ function resolveCodexHomeCandidates() {
2393
2741
  }
2394
2742
  function resolveCodexDir() {
2395
2743
  for (const candidate of resolveCodexHomeCandidates()) {
2396
- if (existsSync6(candidate)) {
2744
+ if (existsSync7(candidate)) {
2397
2745
  return candidate;
2398
2746
  }
2399
2747
  }
@@ -2401,7 +2749,7 @@ function resolveCodexDir() {
2401
2749
  }
2402
2750
  function findLatestCodexDb(codexDir) {
2403
2751
  const explicit = join8(codexDir, "state_5.sqlite");
2404
- if (existsSync6(explicit)) return explicit;
2752
+ if (existsSync7(explicit)) return explicit;
2405
2753
  const candidates = readdirSync2(codexDir).filter((name) => name.startsWith("state_") && name.endsWith(".sqlite")).sort().reverse();
2406
2754
  if (candidates.length === 0) return null;
2407
2755
  return join8(codexDir, candidates[0]);
@@ -2466,7 +2814,7 @@ function resolveClaudePlanKey(displayName) {
2466
2814
  async function collectClaudeUsage() {
2467
2815
  const home = homedir2();
2468
2816
  const claudeDir = join8(home, ".claude");
2469
- if (!existsSync6(claudeDir)) return null;
2817
+ if (!existsSync7(claudeDir)) return null;
2470
2818
  const available = await whichExists("claude");
2471
2819
  const projectsDir = join8(claudeDir, "projects");
2472
2820
  let totalInputTokens = 0;
@@ -2487,7 +2835,7 @@ async function collectClaudeUsage() {
2487
2835
  const weekMs = weekStart.getTime();
2488
2836
  const last5hStart = computeLastHoursStart(5);
2489
2837
  const last5hMs = last5hStart.getTime();
2490
- if (existsSync6(projectsDir)) {
2838
+ if (existsSync7(projectsDir)) {
2491
2839
  try {
2492
2840
  const projectDirs = readdirSync2(projectsDir, { withFileTypes: true });
2493
2841
  for (const dir of projectDirs) {
@@ -2569,7 +2917,7 @@ async function collectClaudeUsage() {
2569
2917
  let resetInfo = "Weekly reset (every Monday 00:00 UTC)";
2570
2918
  let currentModel = "";
2571
2919
  const settingsPath = join8(claudeDir, "settings.json");
2572
- if (existsSync6(settingsPath)) {
2920
+ if (existsSync7(settingsPath)) {
2573
2921
  try {
2574
2922
  const settings = JSON.parse(readFileSync5(settingsPath, "utf8"));
2575
2923
  if (settings.plan === "max" || settings.plan === "max5x") {
@@ -2693,7 +3041,7 @@ function aggregateCodexSessionUsageFromJsonl(lines) {
2693
3041
  }
2694
3042
  function collectCodexSessionUsagesFromJsonl(codexDir) {
2695
3043
  const sessionsDir = join8(codexDir, "sessions");
2696
- if (!existsSync6(sessionsDir)) return [];
3044
+ if (!existsSync7(sessionsDir)) return [];
2697
3045
  const stack = [sessionsDir];
2698
3046
  const usageByFile = [];
2699
3047
  const seen = /* @__PURE__ */ new Set();
@@ -2733,7 +3081,7 @@ async function collectCodexUsage() {
2733
3081
  const models = [];
2734
3082
  const modelsCachePath = join8(codexDir, "models_cache.json");
2735
3083
  let currentModel = "";
2736
- if (existsSync6(modelsCachePath)) {
3084
+ if (existsSync7(modelsCachePath)) {
2737
3085
  try {
2738
3086
  const cache = JSON.parse(readFileSync5(modelsCachePath, "utf8"));
2739
3087
  for (const m of cache.models || []) {
@@ -2747,7 +3095,7 @@ async function collectCodexUsage() {
2747
3095
  }
2748
3096
  }
2749
3097
  const configPath = join8(codexDir, "config.toml");
2750
- if (existsSync6(configPath)) {
3098
+ if (existsSync7(configPath)) {
2751
3099
  try {
2752
3100
  const configContent = readFileSync5(configPath, "utf8");
2753
3101
  const modelMatch = configContent.match(/^model\s*=\s*"([^"]+)"/m);
@@ -3010,7 +3358,7 @@ function aggregateGeminiSessionUsageFromJson(content) {
3010
3358
  }
3011
3359
  function collectGeminiSessionUsages() {
3012
3360
  const geminiTmp = join8(homedir2(), ".gemini", "tmp");
3013
- if (!existsSync6(geminiTmp)) return [];
3361
+ if (!existsSync7(geminiTmp)) return [];
3014
3362
  const usages = [];
3015
3363
  let entries = [];
3016
3364
  try {
@@ -3021,7 +3369,7 @@ function collectGeminiSessionUsages() {
3021
3369
  for (const profile of entries) {
3022
3370
  if (!profile.isDirectory()) continue;
3023
3371
  const chatsDir = join8(geminiTmp, profile.name, "chats");
3024
- if (!existsSync6(chatsDir)) continue;
3372
+ if (!existsSync7(chatsDir)) continue;
3025
3373
  let sessions = [];
3026
3374
  try {
3027
3375
  sessions = readdirSync2(chatsDir).filter((name) => name.startsWith("session-") && (name.endsWith(".json") || name.endsWith(".jsonl")));
@@ -3051,7 +3399,7 @@ async function collectGeminiUsage() {
3051
3399
  }
3052
3400
  let account = null;
3053
3401
  const accountsPath = join8(homedir2(), ".gemini", "google_accounts.json");
3054
- if (existsSync6(accountsPath)) {
3402
+ if (existsSync7(accountsPath)) {
3055
3403
  try {
3056
3404
  const data = JSON.parse(readFileSync5(accountsPath, "utf8"));
3057
3405
  if (typeof data.active === "string" && data.active.includes("@")) {
@@ -3069,7 +3417,7 @@ async function collectGeminiUsage() {
3069
3417
  const { stdout: binPath } = await execFileAsync("which", ["gemini"], { encoding: "utf8", timeout: 3e3 });
3070
3418
  const realBin = realpathSync(binPath.trim());
3071
3419
  const modelsPath = join8(dirname(dirname(realBin)), "node_modules", "@google", "gemini-cli-core", "dist", "src", "config", "models.js");
3072
- if (existsSync6(modelsPath)) {
3420
+ if (existsSync7(modelsPath)) {
3073
3421
  const content = readFileSync5(modelsPath, "utf8");
3074
3422
  const regex = /export const ([A-Z0-9_]+)\s*=\s*'(gemini-[^']+)';/g;
3075
3423
  const seen = /* @__PURE__ */ new Set();
@@ -3089,7 +3437,7 @@ async function collectGeminiUsage() {
3089
3437
  }
3090
3438
  let currentModel = "";
3091
3439
  const settingsPath = join8(homedir2(), ".gemini", "settings.json");
3092
- if (existsSync6(settingsPath)) {
3440
+ if (existsSync7(settingsPath)) {
3093
3441
  try {
3094
3442
  const settings = JSON.parse(readFileSync5(settingsPath, "utf8"));
3095
3443
  if (typeof settings.model === "string" && settings.model.trim()) {
@@ -3288,286 +3636,6 @@ async function replanIssueCommand(input, deps) {
3288
3636
  deps.eventStore.addEvent(issue.id, "manual", `Replan requested for ${issue.identifier} \u2014 now at plan v${issue.planVersion}.`);
3289
3637
  }
3290
3638
 
3291
- // src/commands/request-rework.command.ts
3292
- async function requestReworkCommand(input, deps) {
3293
- const { issue, reviewerFeedback, note } = input;
3294
- if (issue.state !== "Reviewing" && issue.state !== "PendingDecision") {
3295
- throw new Error(
3296
- `requestReworkCommand requires Reviewing or PendingDecision state, got ${issue.state}.`
3297
- );
3298
- }
3299
- issue.lastError = reviewerFeedback;
3300
- issue.lastFailedPhase = "review";
3301
- issue.attempts += 1;
3302
- if (issue.state === "Reviewing") {
3303
- await transitionIssueCommand(
3304
- { issue, target: "PendingDecision", note: `Reviewer completed for ${issue.identifier}.` },
3305
- deps
3306
- );
3307
- }
3308
- await transitionIssueCommand(
3309
- { issue, target: "Queued", note: note ?? `Reviewer requested rework for ${issue.identifier}.` },
3310
- deps
3311
- );
3312
- deps.eventStore.addEvent(
3313
- issue.id,
3314
- "runner",
3315
- `Issue ${issue.identifier} sent back for rework by reviewer.`
3316
- );
3317
- }
3318
-
3319
- // src/commands/merge-workspace.command.ts
3320
- import { existsSync as existsSync7 } from "fs";
3321
- import { execSync } from "child_process";
3322
-
3323
- // src/domains/validation.ts
3324
- import { execFile as execFile2 } from "child_process";
3325
- async function runValidationGate(issue, config) {
3326
- if (!config.testCommand) return null;
3327
- const cwd = issue.worktreePath ?? issue.workspacePath;
3328
- if (!cwd) {
3329
- logger.warn({ issueId: issue.id }, "[Validation] No workspace path \u2014 skipping gate");
3330
- return null;
3331
- }
3332
- const command = config.testCommand;
3333
- logger.info({ issueId: issue.id, command, cwd }, "[Validation] Running validation gate");
3334
- return new Promise((resolve3) => {
3335
- const child = execFile2("sh", ["-c", command], {
3336
- cwd,
3337
- encoding: "utf8",
3338
- timeout: 3e5,
3339
- maxBuffer: 2 * 1024 * 1024
3340
- }, (err, stdout, stderr) => {
3341
- const combined = (stdout || "") + (stderr || "");
3342
- if (!err) {
3343
- logger.info({ issueId: issue.id }, "[Validation] Gate passed");
3344
- resolve3({
3345
- passed: true,
3346
- output: combined.slice(-2048),
3347
- command,
3348
- ranAt: (/* @__PURE__ */ new Date()).toISOString()
3349
- });
3350
- return;
3351
- }
3352
- logger.warn({ issueId: issue.id, exitCode: err.code }, "[Validation] Gate failed");
3353
- resolve3({
3354
- passed: false,
3355
- output: combined.slice(-2048) || String(err).slice(0, 2048),
3356
- command,
3357
- ranAt: (/* @__PURE__ */ new Date()).toISOString()
3358
- });
3359
- });
3360
- });
3361
- }
3362
-
3363
- // src/commands/merge-workspace.command.ts
3364
- async function mergeWorkspaceCommand(input, deps) {
3365
- const { issue, state } = input;
3366
- if (!["Approved", "Reviewing", "PendingDecision"].includes(issue.state)) {
3367
- throw new Error(`Issue ${issue.identifier} is in state ${issue.state}. Merge is only allowed in Reviewing, PendingDecision, or Approved state.`);
3368
- }
3369
- ensureGitRepoReadyForWorktrees(TARGET_ROOT, "merge issues");
3370
- if (issue.state === "Reviewing" || issue.state === "PendingDecision") {
3371
- await transitionIssueCommand(
3372
- { issue, target: "Approved", note: "Approved and merged by user." },
3373
- deps
3374
- );
3375
- }
3376
- const wp = issue.worktreePath ?? issue.workspacePath;
3377
- if (!wp || !existsSync7(wp)) {
3378
- throw new Error(`No mergeable workspace found for ${issue.identifier}. This issue likely ran before git was initialized for the project. Re-run the issue after git setup.`);
3379
- }
3380
- if (issue.branchName && issue.baseBranch) {
3381
- try {
3382
- const stat = execSync(
3383
- `git diff --stat "${issue.baseBranch}"..."${issue.branchName}"`,
3384
- { encoding: "utf8", cwd: TARGET_ROOT, stdio: "pipe", maxBuffer: 512e3, timeout: 1e4 }
3385
- );
3386
- parseDiffStats(issue, stat);
3387
- logger.info({ issueId: issue.id, linesAdded: issue.linesAdded, linesRemoved: issue.linesRemoved, filesChanged: issue.filesChanged }, "[Merge] Diff stats computed");
3388
- } catch (err) {
3389
- logger.warn({ err: String(err), issueId: issue.id }, "[Merge] Failed to compute diff stats");
3390
- }
3391
- }
3392
- try {
3393
- const indexStatus = execSync("git diff --cached --name-only", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
3394
- const wtStatus = execSync("git diff --name-only", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
3395
- if (indexStatus && !wtStatus) {
3396
- execSync("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe" });
3397
- logger.info({ issueId: issue.id }, "[Command] Cleared residual squash from index before merge");
3398
- }
3399
- } catch {
3400
- }
3401
- const validation = await runValidationGate(issue, state.config);
3402
- if (validation) {
3403
- issue.validationResult = validation;
3404
- if (!validation.passed) {
3405
- throw new Error(`Validation gate failed (${validation.command}): ${validation.output.slice(0, 500)}`);
3406
- }
3407
- }
3408
- const result = mergeWorkspace(issue);
3409
- issue.mergeResult = {
3410
- copied: result.copied.length,
3411
- deleted: result.deleted.length,
3412
- skipped: result.skipped.length,
3413
- conflicts: result.conflicts.length,
3414
- conflictFiles: result.conflicts.length > 0 ? result.conflicts : void 0
3415
- };
3416
- if (result.conflicts.length > 0) {
3417
- deps.eventStore.addEvent(issue.id, "error", `Merge conflicts: ${result.conflicts.join(", ")}`);
3418
- await deps.persistencePort.persistState(state);
3419
- return result;
3420
- }
3421
- if (!issue.mergedReason) issue.mergedReason = "Merged by user via PreviewModal.";
3422
- await transitionIssueCommand(
3423
- { issue, target: "Merged", note: `Workspace merged: ${result.copied.length} file(s) copied, ${result.deleted.length} deleted.` },
3424
- deps
3425
- );
3426
- if (issue.workspacePath) {
3427
- try {
3428
- await cleanWorkspace(issue.id, issue, state);
3429
- issue.workspacePath = void 0;
3430
- issue.worktreePath = void 0;
3431
- } catch {
3432
- }
3433
- }
3434
- await deps.persistencePort.persistState(state);
3435
- return result;
3436
- }
3437
-
3438
- // src/commands/push-workspace.command.ts
3439
- import { execFileSync, execSync as execSync2 } from "child_process";
3440
- function isGhAvailable() {
3441
- try {
3442
- execFileSync("gh", ["--version"], { stdio: "pipe", timeout: 5e3 });
3443
- return true;
3444
- } catch {
3445
- return false;
3446
- }
3447
- }
3448
- function getCompareUrl(branchName, baseBranch) {
3449
- try {
3450
- const remote = execSync2("git remote get-url origin", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
3451
- const cleanRemote = remote.replace(/\.git$/, "");
3452
- return `${cleanRemote}/compare/${baseBranch}...${branchName}`;
3453
- } catch {
3454
- return `(branch pushed: ${branchName})`;
3455
- }
3456
- }
3457
- function findExistingPr(branchName) {
3458
- try {
3459
- const result = execFileSync(
3460
- "gh",
3461
- ["pr", "view", branchName, "--json", "url,state", "--jq", 'select(.state == "OPEN") | .url'],
3462
- { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe", timeout: 15e3 }
3463
- ).trim();
3464
- return result || null;
3465
- } catch {
3466
- return null;
3467
- }
3468
- }
3469
- function createPr(branchName, baseBranch, title, body) {
3470
- return execFileSync(
3471
- "gh",
3472
- ["pr", "create", "--head", branchName, "--base", baseBranch, "--title", title, "--body", body],
3473
- { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe", timeout: 3e4 }
3474
- ).trim();
3475
- }
3476
- async function pushWorkspaceCommand(input, deps) {
3477
- const { issue, state } = input;
3478
- if (!["Approved", "Reviewing", "PendingDecision"].includes(issue.state)) {
3479
- throw new Error(`Issue ${issue.identifier} is in state ${issue.state}. Push is only allowed in Reviewing, PendingDecision, or Approved state.`);
3480
- }
3481
- ensureGitRepoReadyForWorktrees(TARGET_ROOT, "push issue branches");
3482
- assertIssueHasGitWorktree(issue, "push");
3483
- if (issue.state === "Reviewing" || issue.state === "PendingDecision") {
3484
- await transitionIssueCommand(
3485
- { issue, target: "Approved", note: "Approved and pushed by user." },
3486
- deps
3487
- );
3488
- }
3489
- ensureWorktreeCommitted(issue);
3490
- const validation = await runValidationGate(issue, state.config);
3491
- if (validation) {
3492
- issue.validationResult = validation;
3493
- if (!validation.passed) {
3494
- throw new Error(`Validation gate failed (${validation.command}): ${validation.output.slice(0, 500)}`);
3495
- }
3496
- }
3497
- computeDiffStats(issue);
3498
- const planSummary = issue.plan?.summary ?? issue.title;
3499
- let diffStat = "";
3500
- try {
3501
- diffStat = execSync2(
3502
- `git diff --stat "${issue.baseBranch}"..."${issue.branchName}"`,
3503
- { cwd: TARGET_ROOT, encoding: "utf8", maxBuffer: 512e3, timeout: 1e4, stdio: "pipe" }
3504
- ).trim();
3505
- } catch {
3506
- }
3507
- const body = `## Summary
3508
- ${planSummary}
3509
-
3510
- ## Diff Stats
3511
- \`\`\`
3512
- ${diffStat || "No diff stats available"}
3513
- \`\`\`
3514
-
3515
- *Automated by fifony*`;
3516
- execSync2(`git push -u origin "${issue.branchName}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
3517
- const prBase = state.config.prBaseBranch || issue.baseBranch;
3518
- const ghAvailable = isGhAvailable();
3519
- let prUrl;
3520
- if (!ghAvailable) {
3521
- prUrl = getCompareUrl(issue.branchName, prBase);
3522
- logger.info({ issueId: issue.id, prUrl }, "[Push] gh CLI not available \u2014 using compare URL");
3523
- } else {
3524
- const existingUrl = findExistingPr(issue.branchName);
3525
- if (existingUrl) {
3526
- prUrl = existingUrl;
3527
- logger.info({ issueId: issue.id, prUrl }, "[Push] Existing open PR found");
3528
- } else {
3529
- try {
3530
- prUrl = createPr(issue.branchName, prBase, issue.title, body);
3531
- logger.info({ issueId: issue.id, prUrl }, "[Push] PR created");
3532
- } catch (err) {
3533
- const ghError = (err.stderr || err.stdout || String(err)).toString().slice(0, 500);
3534
- logger.error({ issueId: issue.id, ghError }, "[Push] gh pr create failed");
3535
- prUrl = getCompareUrl(issue.branchName, prBase);
3536
- deps.eventStore.addEvent(issue.id, "error", `gh pr create failed: ${ghError}. Branch was pushed \u2014 use the compare URL to create the PR manually.`);
3537
- }
3538
- }
3539
- }
3540
- issue.prUrl = prUrl;
3541
- if (!issue.mergedReason) issue.mergedReason = "Pushed to origin and PR created.";
3542
- await transitionIssueCommand(
3543
- { issue, target: "Merged", note: `Branch ${issue.branchName} pushed. PR: ${prUrl}` },
3544
- deps
3545
- );
3546
- deps.eventStore.addEvent(issue.id, "merge", `PR created: ${prUrl}`);
3547
- await deps.persistencePort.persistState(state);
3548
- return { prUrl, ghAvailable };
3549
- }
3550
-
3551
- // src/commands/retry-execution.command.ts
3552
- async function retryExecutionCommand(input, deps) {
3553
- const { issue, note } = input;
3554
- if (issue.state !== "Blocked") {
3555
- throw new Error(
3556
- `retryExecutionCommand requires Blocked state, got ${issue.state}. Use replanIssueCommand for re-planning or the generic /retry endpoint for other states.`
3557
- );
3558
- }
3559
- issue.attempts += 1;
3560
- await transitionIssueCommand(
3561
- { issue, target: "Queued", note: note ?? `Retry execution for ${issue.identifier} (attempt ${issue.attempts}).` },
3562
- deps
3563
- );
3564
- deps.eventStore.addEvent(
3565
- issue.id,
3566
- "manual",
3567
- `Execution retry requested for ${issue.identifier} \u2014 re-queued from Blocked.`
3568
- );
3569
- }
3570
-
3571
3639
  // src/routes/state.ts
3572
3640
  function getStateQuery(state, showAll = false) {
3573
3641
  let issues = state.issues;
@@ -3628,131 +3696,18 @@ function registerStateRoutes(app, state) {
3628
3696
  });
3629
3697
  } catch (error) {
3630
3698
  logger.error({ err: error, provider }, "Failed to collect provider usage");
3631
- return c.json({ providers: [] }, 500);
3632
- }
3633
- });
3634
- app.get("/api/providers/usage", async (c) => {
3635
- try {
3636
- const usage = await collectProvidersUsage();
3637
- return c.json(usage);
3638
- } catch (error) {
3639
- logger.error({ err: error }, "Failed to collect providers usage");
3640
- return c.json({ providers: [] }, 500);
3641
- }
3642
- });
3643
- app.post("/api/issues/create", async (c) => {
3644
- try {
3645
- const payload = await c.req.json();
3646
- logger.info({ title: (payload.title ?? "").toString().slice(0, 80) }, "[API] POST /api/issues/create");
3647
- const container = getContainer();
3648
- const result = await createIssueCommand({ payload, state }, container);
3649
- return c.json({ ok: true, issue: result.issue }, 201);
3650
- } catch (error) {
3651
- return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
3699
+ return c.json({ providers: [] }, 500);
3652
3700
  }
3653
3701
  });
3654
- app.post("/api/issues/:id/state", async (c) => {
3655
- const issueId = parseIssue(c);
3656
- if (!issueId) {
3657
- return c.json({ ok: false, error: "Issue id is required." }, 400);
3658
- }
3659
- const issue = findIssue(state, issueId);
3660
- if (!issue) {
3661
- return c.json({ ok: false, error: "Issue not found" }, 404);
3662
- }
3702
+ app.get("/api/providers/usage", async (c) => {
3663
3703
  try {
3664
- const payload = await c.req.json();
3665
- logger.info({ issueId, identifier: issue.identifier, targetState: payload.state }, "[API] POST /api/issues/:id/state");
3666
- const nextState = parseIssueState(payload.state);
3667
- if (!nextState) {
3668
- throw new Error(`Unsupported state: ${String(payload.state)}`);
3669
- }
3670
- if (nextState === "Running" && issue.state !== "Queued") {
3671
- return c.json({ ok: false, error: "Manual transition to Running is only supported from Queued." }, 400);
3672
- }
3673
- const container = getContainer();
3674
- await transitionIssueCommand({ issue, target: nextState, note: `Manual state update: ${nextState}` }, container);
3675
- if (nextState === "Running") {
3676
- await enqueue(issue, "execute");
3677
- }
3678
- if (nextState === "Cancelled" && payload.reason) {
3679
- issue.lastError = toStringValue(payload.reason);
3680
- }
3681
- await persistState(state);
3682
- return c.json({ ok: true, issue });
3704
+ const usage = await collectProvidersUsage();
3705
+ return c.json(usage);
3683
3706
  } catch (error) {
3684
- return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
3707
+ logger.error({ err: error }, "Failed to collect providers usage");
3708
+ return c.json({ providers: [] }, 500);
3685
3709
  }
3686
3710
  });
3687
- app.post("/api/issues/:id/retry", async (c) => {
3688
- logger.info({ issueId: parseIssue(c) }, "[API] POST /api/issues/:id/retry");
3689
- return mutateIssueState(state, c, async (issue) => {
3690
- const container = getContainer();
3691
- if (TERMINAL_STATES.has(issue.state)) {
3692
- await transitionIssueCommand(
3693
- { issue, target: "Planning", note: "Manual retry \u2014 reopened." },
3694
- container
3695
- );
3696
- if (issue.plan?.steps?.length) {
3697
- await transitionIssueCommand(
3698
- { issue, target: "PendingApproval", note: "Existing plan found." },
3699
- container
3700
- );
3701
- await transitionIssueCommand(
3702
- { issue, target: "Queued", note: "Auto-queued after plan approval." },
3703
- container
3704
- );
3705
- }
3706
- } else if (issue.state === "Blocked") {
3707
- await retryExecutionCommand(
3708
- { issue, note: "Manual retry from Blocked." },
3709
- container
3710
- );
3711
- } else if (issue.state === "Approved") {
3712
- issue.attempts += 1;
3713
- await transitionIssueCommand(
3714
- { issue, target: "Planning", note: "Requeued for rework after merge conflicts." },
3715
- container
3716
- );
3717
- if (issue.plan?.steps?.length) {
3718
- await transitionIssueCommand(
3719
- { issue, target: "PendingApproval", note: "Existing plan found." },
3720
- container
3721
- );
3722
- await transitionIssueCommand(
3723
- { issue, target: "Queued", note: "Auto-queued for rework." },
3724
- container
3725
- );
3726
- }
3727
- } else if (issue.state === "Reviewing" || issue.state === "PendingDecision") {
3728
- await requestReworkCommand(
3729
- {
3730
- issue,
3731
- reviewerFeedback: issue.lastError || "Manual rework request.",
3732
- note: `Manual rework requested for ${issue.identifier}.`
3733
- },
3734
- container
3735
- );
3736
- } else if (issue.state === "PendingApproval") {
3737
- await transitionIssueCommand(
3738
- { issue, target: "Queued", note: "Manual retry \u2014 queued for execution." },
3739
- container
3740
- );
3741
- } else {
3742
- issue.lastError = void 0;
3743
- issue.nextRetryAt = void 0;
3744
- issue.updatedAt = now();
3745
- }
3746
- addEvent(state, issue.id, "manual", `Manual retry requested for ${issue.id}.`);
3747
- });
3748
- });
3749
- app.post("/api/issues/:id/cancel", async (c) => {
3750
- logger.info({ issueId: parseIssue(c) }, "[API] POST /api/issues/:id/cancel");
3751
- return mutateIssueState(state, c, async (issue) => {
3752
- const container = getContainer();
3753
- await cancelIssueCommand({ issue }, container);
3754
- });
3755
- });
3756
3711
  app.post("/api/issues/:id/approve", async (c) => {
3757
3712
  logger.info({ issueId: parseIssue(c) }, "[API] POST /api/issues/:id/approve");
3758
3713
  return mutateIssueState(state, c, async (issue) => {
@@ -3786,7 +3741,7 @@ function registerStateRoutes(app, state) {
3786
3741
  const result2 = await pushWorkspaceCommand({ issue, state }, container);
3787
3742
  return c.json({ ok: true, prUrl: result2.prUrl, ghAvailable: result2.ghAvailable });
3788
3743
  }
3789
- const result = await mergeWorkspaceCommand({ issue, state }, container);
3744
+ const result = await mergeWorkspaceCommand({ issue, state, squashAlreadyApplied: issue.testApplied ?? false }, container);
3790
3745
  return c.json({ ok: true, ...result });
3791
3746
  } catch (error) {
3792
3747
  const issueId = parseIssue(c);
@@ -3801,7 +3756,7 @@ function registerStateRoutes(app, state) {
3801
3756
  if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
3802
3757
  const issue = findIssue(state, issueId);
3803
3758
  if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
3804
- const { dryMerge } = await import("./workspace-S5F5JFM6.js");
3759
+ const { dryMerge } = await import("./workspace-HJEJZMTO.js");
3805
3760
  const result = dryMerge(issue);
3806
3761
  return c.json({ ok: true, ...result });
3807
3762
  } catch (error) {
@@ -3816,7 +3771,7 @@ function registerStateRoutes(app, state) {
3816
3771
  if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
3817
3772
  const issue = findIssue(state, issueId);
3818
3773
  if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
3819
- const { rebaseWorktree } = await import("./workspace-S5F5JFM6.js");
3774
+ const { rebaseWorktree } = await import("./workspace-HJEJZMTO.js");
3820
3775
  const result = rebaseWorktree(issue);
3821
3776
  if (result.success) {
3822
3777
  addEvent(state, issue.id, "info", `Branch ${issue.branchName} rebased onto ${issue.baseBranch}.`);
@@ -3846,6 +3801,8 @@ function registerStateRoutes(app, state) {
3846
3801
  const msg = err.stderr || err.stdout || String(err);
3847
3802
  throw new Error(`git merge --squash failed: ${msg}`);
3848
3803
  }
3804
+ issue.testApplied = true;
3805
+ markIssueDirty(issue.id);
3849
3806
  addEvent(state, issue.id, "manual", `Test squash applied to workspace: git merge --squash ${issue.branchName}`);
3850
3807
  });
3851
3808
  });
@@ -3859,6 +3816,8 @@ function registerStateRoutes(app, state) {
3859
3816
  const msg = err.stderr || err.stdout || String(err);
3860
3817
  throw new Error(`git reset/clean failed: ${msg}`);
3861
3818
  }
3819
+ issue.testApplied = false;
3820
+ markIssueDirty(issue.id);
3862
3821
  addEvent(state, issue.id, "manual", `Test reverted: git reset --hard HEAD && git clean -fd`);
3863
3822
  });
3864
3823
  });
@@ -3948,7 +3907,7 @@ function registerStateRoutes(app, state) {
3948
3907
  const issue = findIssue(state, issueId);
3949
3908
  if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
3950
3909
  try {
3951
- const { getIssueTransitionHistory } = await import("./issue-state-machine-6Y2OTUGI.js");
3910
+ const { getIssueTransitionHistory } = await import("./issue-state-machine-NCD3ZFOI.js");
3952
3911
  const limit = parseInt(c.req.query("limit") ?? "50", 10);
3953
3912
  const offset = parseInt(c.req.query("offset") ?? "0", 10);
3954
3913
  const transitions = await getIssueTransitionHistory(issue.id, { limit, offset });
@@ -3959,7 +3918,7 @@ function registerStateRoutes(app, state) {
3959
3918
  });
3960
3919
  app.get("/api/state-machine/transitions", async (c) => {
3961
3920
  try {
3962
- const { getStateMachineTransitions } = await import("./issue-state-machine-6Y2OTUGI.js");
3921
+ const { getStateMachineTransitions } = await import("./issue-state-machine-NCD3ZFOI.js");
3963
3922
  return c.json({ ok: true, transitions: getStateMachineTransitions() });
3964
3923
  } catch (error) {
3965
3924
  return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 500);
@@ -3967,7 +3926,7 @@ function registerStateRoutes(app, state) {
3967
3926
  });
3968
3927
  app.get("/api/state-machine/visualize", async (c) => {
3969
3928
  try {
3970
- const { visualizeStateMachine } = await import("./issue-state-machine-6Y2OTUGI.js");
3929
+ const { visualizeStateMachine } = await import("./issue-state-machine-NCD3ZFOI.js");
3971
3930
  const dot = visualizeStateMachine();
3972
3931
  if (!dot) return c.json({ ok: false, error: "Visualization not available." }, 404);
3973
3932
  return c.json({ ok: true, dot });
@@ -4940,6 +4899,7 @@ function generatePlanInBackground(issue, config, _workflowDefinition, callbacks,
4940
4899
  addEvent2(issue.id, "info", `${fast ? "Fast plan" : "Plan"} generation starting for ${issue.identifier} (provider detection in progress).`);
4941
4900
  generatePlan(issue.title, issue.description, config, null, { fast }).then(async ({ plan, usage }) => {
4942
4901
  issue.plan = plan;
4902
+ issue.planVersion = Math.max(issue.planVersion ?? 0, 1);
4943
4903
  markIssuePlanDirty(issue.id);
4944
4904
  issue.planningStatus = "idle";
4945
4905
  issue.planningStartedAt = void 0;
@@ -4947,6 +4907,19 @@ function generatePlanInBackground(issue, config, _workflowDefinition, callbacks,
4947
4907
  issue.updatedAt = now();
4948
4908
  applyUsage(issue, usage);
4949
4909
  applySuggestions(issue, plan);
4910
+ try {
4911
+ const { getIssuePlanResource: getIssuePlanResource2 } = await import("./store-GZVWNJ6V.js");
4912
+ const planRes = getIssuePlanResource2();
4913
+ if (planRes) {
4914
+ await planRes.replace(issue.id, {
4915
+ id: issue.id,
4916
+ plan: issue.plan,
4917
+ planHistory: issue.planHistory,
4918
+ planVersion: issue.planVersion
4919
+ });
4920
+ }
4921
+ } catch {
4922
+ }
4950
4923
  addEvent2(issue.id, "progress", `${fast ? "Fast plan" : "Plan"} generated for ${issue.identifier}: ${plan.steps.length} steps, complexity: ${plan.estimatedComplexity}.`);
4951
4924
  if (usage.totalTokens > 0) {
4952
4925
  addEvent2(issue.id, "info", `Plan tokens (${issue.identifier}): ${usage.totalTokens.toLocaleString()} (in: ${usage.inputTokens.toLocaleString()}, out: ${usage.outputTokens.toLocaleString()}) [${usage.model}]`);
@@ -4972,6 +4945,7 @@ function refinePlanInBackground(issue, feedback, config, _workflowDefinition, ca
4972
4945
  addEvent2(issue.id, "info", `Plan refinement starting for ${issue.identifier}: "${feedbackSnippet}".`);
4973
4946
  refinePlan(issue, feedback, config, null).then(async ({ plan, usage }) => {
4974
4947
  issue.plan = plan;
4948
+ issue.planVersion = Math.max(issue.planVersion ?? 0, 1);
4975
4949
  markIssuePlanDirty(issue.id);
4976
4950
  issue.planningStatus = "idle";
4977
4951
  issue.planningStartedAt = void 0;
@@ -4979,6 +4953,19 @@ function refinePlanInBackground(issue, feedback, config, _workflowDefinition, ca
4979
4953
  issue.updatedAt = now();
4980
4954
  applyUsage(issue, usage);
4981
4955
  applySuggestions(issue, plan);
4956
+ try {
4957
+ const { getIssuePlanResource: getIssuePlanResource2 } = await import("./store-GZVWNJ6V.js");
4958
+ const planRes = getIssuePlanResource2();
4959
+ if (planRes) {
4960
+ await planRes.replace(issue.id, {
4961
+ id: issue.id,
4962
+ plan: issue.plan,
4963
+ planHistory: issue.planHistory,
4964
+ planVersion: issue.planVersion
4965
+ });
4966
+ }
4967
+ } catch {
4968
+ }
4982
4969
  const feedbackPreview = feedback.length > 80 ? `${feedback.slice(0, 77)}...` : feedback;
4983
4970
  addEvent2(issue.id, "progress", `Plan refined for ${issue.identifier}: "${feedbackPreview}" \u2192 ${plan.steps.length} steps, complexity: ${plan.estimatedComplexity}.`);
4984
4971
  if (usage.totalTokens > 0) {
@@ -5002,10 +4989,6 @@ import { existsSync as existsSync9, mkdtempSync as mkdtempSync3, readFileSync as
5002
4989
  import { spawn as spawn2 } from "child_process";
5003
4990
  import { tmpdir as tmpdir3 } from "os";
5004
4991
  import { join as join13 } from "path";
5005
- function getProviderCommand(provider, config) {
5006
- const explicit = provider === config.agentProvider ? config.agentCommand || "" : "";
5007
- return resolveAgentCommand(provider, explicit, "", "");
5008
- }
5009
4992
  async function buildPrompt2(field, title, description, issueType, images) {
5010
4993
  const context2 = {
5011
4994
  title: title || "(empty)",
@@ -5056,8 +5039,9 @@ function parseCandidate(raw, expectedField) {
5056
5039
  if (value && !isPlaceholder && (!field || field === expectedField)) {
5057
5040
  return value;
5058
5041
  }
5059
- if (typeof parsed.result === "string") {
5060
- const nested = parsed.result.trim();
5042
+ const nestedSource = typeof parsed.result === "string" ? parsed.result : typeof parsed.response === "string" ? parsed.response : void 0;
5043
+ if (nestedSource) {
5044
+ const nested = nestedSource.trim();
5061
5045
  if (nested) {
5062
5046
  const nestedClean = nested.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
5063
5047
  for (const nestedCandidate of extractJsonObjects(nestedClean)) {
@@ -5153,65 +5137,51 @@ async function enhanceIssueField(payload, config, _workflowDefinition) {
5153
5137
  const title = typeof payload.title === "string" ? payload.title.trim() : "";
5154
5138
  const description = typeof payload.description === "string" ? payload.description.trim() : "";
5155
5139
  const issueType = typeof payload.issueType === "string" ? payload.issueType.trim() : void 0;
5156
- const requestedProvider = normalizeAgentProvider(
5157
- typeof payload.preferredProvider === "string" ? payload.preferredProvider : payload.provider ?? config.agentProvider
5158
- );
5140
+ const images = Array.isArray(payload.images) ? payload.images.filter((p) => typeof p === "string") : void 0;
5141
+ const { provider: selectedProvider, model: planModel } = await resolvePlanStageConfig(config);
5159
5142
  const providers = detectAvailableProviders();
5160
- const availableSet = new Set(providers.filter((entry) => entry.available).map((entry) => entry.name));
5161
- const orderedProviders = [];
5162
- const addProvider = (candidate) => {
5163
- if (availableSet.has(candidate) && !orderedProviders.includes(candidate)) {
5164
- orderedProviders.push(candidate);
5165
- }
5166
- };
5167
- addProvider(requestedProvider);
5168
- for (const entry of providers) {
5169
- if (entry.available) addProvider(entry.name);
5170
- }
5171
- if (!orderedProviders.length) {
5143
+ const isAvailable = providers.some((p) => p.name === selectedProvider && p.available);
5144
+ if (!isAvailable) {
5172
5145
  const known = providers.map((entry) => `${entry.name}:${entry.available ? "available" : "missing"}`).join(", ");
5173
- throw new Error(`No AI provider available (codex/claude). Detected: ${known}`);
5146
+ throw new Error(`Configured plan provider "${selectedProvider}" is not available. Detected: ${known}`);
5174
5147
  }
5175
- const images = Array.isArray(payload.images) ? payload.images.filter((p) => typeof p === "string") : void 0;
5176
- const prompt = await buildPrompt2(field, title, description, issueType, images);
5177
- const errors = [];
5178
- const enhanceSchema = {
5148
+ const adapter = ADAPTERS[selectedProvider];
5149
+ if (!adapter) {
5150
+ throw new Error(`No adapter configured for plan provider "${selectedProvider}".`);
5151
+ }
5152
+ const ENHANCE_JSON_SCHEMA = JSON.stringify({
5179
5153
  type: "object",
5180
5154
  properties: {
5181
- field: { type: "string" },
5155
+ field: { type: "string", enum: ["title", "description"] },
5182
5156
  value: { type: "string" }
5183
5157
  },
5184
5158
  required: ["field", "value"],
5185
5159
  additionalProperties: false
5186
- };
5187
- for (const selectedProvider of orderedProviders) {
5188
- const command = getProviderCommand(selectedProvider, config);
5189
- if (!command) {
5190
- errors.push(`Provider "${selectedProvider}" has no command.`);
5191
- continue;
5192
- }
5193
- try {
5194
- const output = await runProviderCommand(
5195
- command,
5196
- selectedProvider,
5197
- prompt,
5198
- title,
5199
- description,
5200
- field,
5201
- config.commandTimeoutMs,
5202
- images
5203
- );
5204
- logger.info({ provider: selectedProvider, field, rawOutput: output.slice(0, 2e3) }, "Enhance raw output");
5205
- const value = parseEnhancerOutput(output, field);
5206
- logger.info({ provider: selectedProvider, field, parsedValue: value }, "Enhance parsed value");
5207
- return { field, value, provider: selectedProvider };
5208
- } catch (error) {
5209
- errors.push(
5210
- `Provider "${selectedProvider}" failed: ${error instanceof Error ? error.message : String(error)}`
5211
- );
5212
- }
5160
+ });
5161
+ const command = adapter.buildCommand({
5162
+ model: planModel,
5163
+ imagePaths: images,
5164
+ jsonSchema: selectedProvider === "claude" ? ENHANCE_JSON_SCHEMA : void 0,
5165
+ noToolAccess: selectedProvider === "claude"
5166
+ });
5167
+ if (!command) {
5168
+ throw new Error(`Adapter returned empty command for provider "${selectedProvider}".`);
5213
5169
  }
5214
- throw new Error(`Could not enhance issue field. ${errors.join(" | ")}`);
5170
+ const prompt = await buildPrompt2(field, title, description, issueType, images);
5171
+ const output = await runProviderCommand(
5172
+ command,
5173
+ selectedProvider,
5174
+ prompt,
5175
+ title,
5176
+ description,
5177
+ field,
5178
+ config.commandTimeoutMs,
5179
+ images
5180
+ );
5181
+ logger.info({ provider: selectedProvider, model: planModel, field, rawOutput: output.slice(0, 2e3) }, "Enhance raw output");
5182
+ const value = parseEnhancerOutput(output, field);
5183
+ logger.info({ provider: selectedProvider, field, parsedValue: value.slice(0, 500) }, "Enhance parsed value");
5184
+ return { field, value, provider: selectedProvider };
5215
5185
  }
5216
5186
 
5217
5187
  // src/routes/plan.ts
@@ -5524,17 +5494,6 @@ function registerScanningRoutes(app, state) {
5524
5494
  return c.json({ ok: false, error: "Failed to scan project." }, 500);
5525
5495
  }
5526
5496
  });
5527
- app.post("/api/scan/analyze", async (c) => {
5528
- try {
5529
- const payload = await c.req.json();
5530
- const provider = typeof payload.provider === "string" ? payload.provider : state.config.agentProvider;
5531
- const result = await analyzeProjectWithCli(provider, TARGET_ROOT);
5532
- return c.json(result);
5533
- } catch (error) {
5534
- logger.error({ err: error }, "Failed to analyze project with CLI");
5535
- return c.json({ ok: false, error: "Failed to analyze project." }, 500);
5536
- }
5537
- });
5538
5497
  app.post("/api/boot/skip-scan", async (c) => {
5539
5498
  broadcastToWebSocketClients({ type: "boot:scan:skipped" });
5540
5499
  return c.json({ ok: true, message: "Scan skipped." });
@@ -6236,7 +6195,7 @@ async function startApiServer(state, port, options) {
6236
6195
  docs: { enabled: true, title: "Fifony API", version: "1.0.0", description: "Local orchestration API for Fifony" },
6237
6196
  cors: { enabled: true, origin: "*" },
6238
6197
  security: { enabled: false },
6239
- logging: { enabled: true, excludePaths: ["/health", "/status", "/**/*.js", "/**/*.css", "/**/*.svg"] },
6198
+ logging: { enabled: !QUIET_MODE, excludePaths: ["/health", "/status", "/**/*.js", "/**/*.css", "/**/*.svg"] },
6240
6199
  compression: { enabled: true, threshold: 1024 },
6241
6200
  health: { enabled: true },
6242
6201
  resources: {
@@ -6474,9 +6433,13 @@ async function recoverStateFromIssueResource() {
6474
6433
  for (const issue of issues) {
6475
6434
  try {
6476
6435
  const planRecord = await issuePlanResource.get(issue.id);
6477
- if (planRecord?.plan) issue.plan = planRecord.plan;
6436
+ if (planRecord?.plan) {
6437
+ issue.plan = planRecord.plan;
6438
+ logger.debug({ issueId: issue.id, hasSteps: !!issue.plan?.steps?.length }, "[Recovery] Hydrated plan from issue_plans resource");
6439
+ }
6478
6440
  if (planRecord?.planHistory) issue.planHistory = planRecord.planHistory;
6479
- } catch {
6441
+ } catch (err) {
6442
+ logger.warn({ issueId: issue.id, err: String(err) }, "[Recovery] Failed to load plan from issue_plans resource");
6480
6443
  }
6481
6444
  }
6482
6445
  }
@@ -6813,314 +6776,7 @@ function scanProjectFiles(targetRoot) {
6813
6776
  packageDescription
6814
6777
  };
6815
6778
  }
6816
- var BUILD_FILE_SIGNALS = {
6817
- "package.json": { language: "javascript", stack: ["node"] },
6818
- "Cargo.toml": { language: "rust", stack: ["cargo"] },
6819
- "pyproject.toml": { language: "python", stack: ["python"] },
6820
- "setup.py": { language: "python", stack: ["python"] },
6821
- "requirements.txt": { language: "python", stack: ["pip"] },
6822
- "Pipfile": { language: "python", stack: ["pipenv"] },
6823
- "go.mod": { language: "go", stack: ["go"] },
6824
- "build.gradle": { language: "java", stack: ["gradle"] },
6825
- "build.gradle.kts": { language: "kotlin", stack: ["gradle"] },
6826
- "pom.xml": { language: "java", stack: ["maven"] },
6827
- "Gemfile": { language: "ruby", stack: ["bundler"] },
6828
- "mix.exs": { language: "elixir", stack: ["mix"] },
6829
- "pubspec.yaml": { language: "dart", stack: ["flutter"] },
6830
- "CMakeLists.txt": { language: "c++", stack: ["cmake"] },
6831
- "Makefile": { language: "unknown", stack: ["make"] },
6832
- "Dockerfile": { language: "unknown", stack: ["docker"] },
6833
- "composer.json": { language: "php", stack: ["composer"] },
6834
- "Package.swift": { language: "swift", stack: ["spm"] },
6835
- "deno.json": { language: "typescript", stack: ["deno"] },
6836
- "bun.lockb": { language: "typescript", stack: ["bun"] }
6837
- };
6838
- function buildFallbackAnalysis(targetRoot) {
6839
- let description = "";
6840
- let readmeExcerpt = "";
6841
- for (const readmeFile of ["README.md", "README.rst", "README.txt", "README"]) {
6842
- const p = join16(targetRoot, readmeFile);
6843
- if (existsSync13(p)) {
6844
- try {
6845
- readmeExcerpt = readFileSync10(p, "utf8").slice(0, 300).trim();
6846
- break;
6847
- } catch {
6848
- }
6849
- }
6850
- }
6851
- const pkgPath = join16(targetRoot, "package.json");
6852
- if (existsSync13(pkgPath)) {
6853
- try {
6854
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
6855
- const name = typeof pkg.name === "string" ? pkg.name : "";
6856
- const desc = typeof pkg.description === "string" ? pkg.description : "";
6857
- if (desc) description = name ? `${name}: ${desc}` : desc;
6858
- } catch {
6859
- }
6860
- }
6861
- const cargoPath = join16(targetRoot, "Cargo.toml");
6862
- if (!description && existsSync13(cargoPath)) {
6863
- try {
6864
- const content = readFileSync10(cargoPath, "utf8");
6865
- const descMatch = content.match(/^description\s*=\s*"([^"]+)"/m);
6866
- const nameMatch = content.match(/^name\s*=\s*"([^"]+)"/m);
6867
- if (descMatch) description = nameMatch ? `${nameMatch[1]}: ${descMatch[1]}` : descMatch[1];
6868
- } catch {
6869
- }
6870
- }
6871
- const pyprojectPath = join16(targetRoot, "pyproject.toml");
6872
- if (!description && existsSync13(pyprojectPath)) {
6873
- try {
6874
- const content = readFileSync10(pyprojectPath, "utf8");
6875
- const descMatch = content.match(/^description\s*=\s*"([^"]+)"/m);
6876
- const nameMatch = content.match(/^name\s*=\s*"([^"]+)"/m);
6877
- if (descMatch) description = nameMatch ? `${nameMatch[1]}: ${descMatch[1]}` : descMatch[1];
6878
- } catch {
6879
- }
6880
- }
6881
- if (!description) {
6882
- description = readmeExcerpt ? readmeExcerpt.split("\n").filter(Boolean).slice(0, 2).join(". ") : "A software project.";
6883
- }
6884
- let language = "unknown";
6885
- const stack = [];
6886
- for (const [file, signal] of Object.entries(BUILD_FILE_SIGNALS)) {
6887
- if (existsSync13(join16(targetRoot, file))) {
6888
- if (language === "unknown" && signal.language !== "unknown") {
6889
- language = signal.language;
6890
- }
6891
- for (const s of signal.stack) {
6892
- if (!stack.includes(s)) stack.push(s);
6893
- }
6894
- }
6895
- }
6896
- return {
6897
- description,
6898
- language,
6899
- domains: [],
6900
- stack: stack.length ? stack : [language],
6901
- suggestedAgents: ["code-reviewer", "software-architect"],
6902
- source: "fallback"
6903
- };
6904
- }
6905
- function parseAnalysisOutput(raw) {
6906
- const text = raw.trim();
6907
- if (!text) return null;
6908
- let jsonText = text;
6909
- try {
6910
- const envelope = JSON.parse(text);
6911
- if (typeof envelope.result === "string") {
6912
- jsonText = envelope.result.trim();
6913
- } else if (envelope.description || envelope.domains) {
6914
- return validateAnalysis(envelope);
6915
- }
6916
- } catch {
6917
- }
6918
- const fenced = jsonText.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
6919
- if (fenced) {
6920
- jsonText = fenced[1].trim();
6921
- }
6922
- try {
6923
- const parsed = JSON.parse(jsonText);
6924
- return validateAnalysis(parsed);
6925
- } catch {
6926
- const match = jsonText.match(/\{[\s\S]*\}/);
6927
- if (match) {
6928
- try {
6929
- const parsed = JSON.parse(match[0]);
6930
- return validateAnalysis(parsed);
6931
- } catch {
6932
- return null;
6933
- }
6934
- }
6935
- return null;
6936
- }
6937
- }
6938
- function validateAnalysis(parsed) {
6939
- if (!parsed || typeof parsed !== "object") return null;
6940
- const description = typeof parsed.description === "string" ? parsed.description.trim() : "";
6941
- const language = typeof parsed.language === "string" ? parsed.language.trim().toLowerCase() : "";
6942
- const domains = Array.isArray(parsed.domains) ? parsed.domains.filter((d) => typeof d === "string") : [];
6943
- const stack = Array.isArray(parsed.stack) ? parsed.stack.filter((s) => typeof s === "string") : [];
6944
- const suggestedAgents = Array.isArray(parsed.suggestedAgents) ? parsed.suggestedAgents.filter((a) => typeof a === "string") : [];
6945
- if (!description && domains.length === 0 && stack.length === 0) return null;
6946
- return {
6947
- description: description || "A software project.",
6948
- language,
6949
- domains,
6950
- stack,
6951
- suggestedAgents,
6952
- source: "cli"
6953
- };
6954
- }
6955
- function isBlockedProjectAnalysisResponse(analysis) {
6956
- const normalized = `${analysis.description || ""}`.toLowerCase();
6957
- const indicators = [
6958
- "could not inspect the repository files",
6959
- "local command execution is blocked",
6960
- "please provide access",
6961
- "paste the key files",
6962
- "failed to inspect",
6963
- "unable to access the repository"
6964
- ];
6965
- return indicators.some((indicator) => normalized.includes(indicator));
6966
- }
6967
6779
  var ANALYSIS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6968
- function computeProjectHash(targetRoot) {
6969
- const buildFiles = Object.keys(BUILD_FILE_SIGNALS);
6970
- const found = buildFiles.filter((f) => existsSync13(join16(targetRoot, f))).sort();
6971
- return createHash("sha256").update(found.join(",")).digest("hex").slice(0, 16);
6972
- }
6973
- async function loadCachedAnalysis(targetRoot) {
6974
- const resource = getSettingStateResource();
6975
- if (!resource) return null;
6976
- const hash = computeProjectHash(targetRoot);
6977
- const key = `project-analysis:${hash}`;
6978
- try {
6979
- const record2 = await resource.get(key);
6980
- if (!record2?.value) return null;
6981
- const cached = record2.value;
6982
- if (!cached.analysis || !cached.updatedAt) return null;
6983
- if (Date.now() - Date.parse(cached.updatedAt) > ANALYSIS_CACHE_TTL_MS) return null;
6984
- return cached.analysis;
6985
- } catch {
6986
- return null;
6987
- }
6988
- }
6989
- async function saveCachedAnalysis(targetRoot, analysis) {
6990
- const resource = getSettingStateResource();
6991
- if (!resource) return;
6992
- const hash = computeProjectHash(targetRoot);
6993
- const key = `project-analysis:${hash}`;
6994
- try {
6995
- await resource.replace(key, {
6996
- id: key,
6997
- scope: "system",
6998
- source: "detected",
6999
- value: { analysis, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
7000
- });
7001
- } catch {
7002
- }
7003
- }
7004
- async function analyzeProjectWithCli(provider, targetRoot, options) {
7005
- if (!options?.forceRefresh) {
7006
- const cached = await loadCachedAnalysis(targetRoot);
7007
- if (cached) {
7008
- logger.info("Using cached project analysis.");
7009
- return cached;
7010
- }
7011
- }
7012
- const normalizedProvider = provider.trim().toLowerCase();
7013
- const providers = detectAvailableProviders();
7014
- const providerInfo = providers.find((p) => p.name === normalizedProvider && p.available);
7015
- if (!providerInfo) {
7016
- logger.warn(
7017
- { provider: normalizedProvider },
7018
- "Requested CLI provider not available, using fallback analysis"
7019
- );
7020
- return buildFallbackAnalysis(targetRoot);
7021
- }
7022
- const tempDir = mkdtempSync4(join16(tmpdir4(), "fifony-scan-"));
7023
- const promptFile = join16(tempDir, "fifony-scan-prompt.txt");
7024
- const analysisPrompt = await renderPrompt("project-analysis");
7025
- writeFileSync11(promptFile, analysisPrompt, "utf8");
7026
- const processEnv = {};
7027
- for (const [key, value] of Object.entries(env3)) {
7028
- if (typeof value === "string") processEnv[key] = value;
7029
- }
7030
- processEnv.FIFONY_PROMPT_FILE = promptFile;
7031
- try {
7032
- const output = await new Promise((resolve3, reject) => {
7033
- let stdout = "";
7034
- let stderr = "";
7035
- let timedOut = false;
7036
- let args;
7037
- let command;
7038
- if (normalizedProvider === "claude") {
7039
- command = "claude";
7040
- args = [
7041
- "--print",
7042
- "--no-session-persistence",
7043
- "--output-format",
7044
- "json",
7045
- "-p",
7046
- analysisPrompt
7047
- ];
7048
- } else if (normalizedProvider === "codex") {
7049
- command = "sh";
7050
- args = ["-c", `codex exec --skip-git-repo-check < "${promptFile}"`];
7051
- } else {
7052
- reject(new Error(`Unsupported provider: ${normalizedProvider}`));
7053
- return;
7054
- }
7055
- const child = spawn3(command, args, {
7056
- cwd: targetRoot,
7057
- env: processEnv,
7058
- stdio: ["pipe", "pipe", "pipe"]
7059
- });
7060
- if (child.stdin) child.stdin.end();
7061
- child.stdout?.on("data", (chunk) => {
7062
- stdout = appendFileTail(stdout, chunk.toString("utf8"), 64e3);
7063
- });
7064
- child.stderr?.on("data", (chunk) => {
7065
- stderr = appendFileTail(stderr, chunk.toString("utf8"), 16e3);
7066
- });
7067
- const timer = setTimeout(() => {
7068
- timedOut = true;
7069
- child.kill("SIGTERM");
7070
- }, 12e4);
7071
- child.on("error", (err) => {
7072
- clearTimeout(timer);
7073
- reject(new Error(`Failed to spawn ${normalizedProvider}: ${err.message}`));
7074
- });
7075
- child.on("close", (code) => {
7076
- clearTimeout(timer);
7077
- if (timedOut) {
7078
- reject(new Error(`CLI analysis timed out after 120s`));
7079
- return;
7080
- }
7081
- if (code !== 0) {
7082
- logger.debug(
7083
- { provider: normalizedProvider, code, stderr: stderr.slice(0, 500) },
7084
- "CLI analysis command exited with non-zero code"
7085
- );
7086
- }
7087
- resolve3(stdout);
7088
- });
7089
- });
7090
- const analysis = parseAnalysisOutput(output);
7091
- if (analysis && !isBlockedProjectAnalysisResponse(analysis)) {
7092
- logger.info(
7093
- { provider: normalizedProvider, domains: analysis.domains, stack: analysis.stack },
7094
- "CLI project analysis completed"
7095
- );
7096
- await saveCachedAnalysis(targetRoot, analysis);
7097
- return analysis;
7098
- }
7099
- if (!analysis) {
7100
- logger.warn(
7101
- { provider: normalizedProvider, rawOutput: output.slice(0, 500) },
7102
- "CLI returned unparseable output, using fallback"
7103
- );
7104
- } else {
7105
- logger.warn(
7106
- { provider: normalizedProvider, blockedAnalysis: analysis.description },
7107
- "CLI analysis returned blocked/insufficient context response, using fallback"
7108
- );
7109
- }
7110
- return buildFallbackAnalysis(targetRoot);
7111
- } catch (error) {
7112
- logger.warn(
7113
- { err: error, provider: normalizedProvider },
7114
- "CLI analysis failed, using fallback"
7115
- );
7116
- return buildFallbackAnalysis(targetRoot);
7117
- } finally {
7118
- try {
7119
- rmSync5(tempDir, { recursive: true, force: true });
7120
- } catch {
7121
- }
7122
- }
7123
- }
7124
6780
  var DEFAULT_REFERENCE_REPOSITORIES = [
7125
6781
  {
7126
6782
  id: "ring",
@@ -7559,7 +7215,7 @@ function importReferenceArtifacts(repositoryId, workspaceRoot, options) {
7559
7215
  }
7560
7216
 
7561
7217
  // src/domains/config.ts
7562
- import { env as env4 } from "process";
7218
+ import { env as env3 } from "process";
7563
7219
  var VALID_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "extra-high"]);
7564
7220
  function parseEffortValue(value) {
7565
7221
  const str = typeof value === "string" ? value.trim().toLowerCase() : "";
@@ -7621,21 +7277,21 @@ function deriveConfig(args) {
7621
7277
  staleInProgressTimeoutMs: parseEnvNumber("FIFONY_STALE_IN_PROGRESS_MS", 24e5),
7622
7278
  logLinesTail: parseEnvNumber("FIFONY_LOG_TAIL_CHARS", 12e3),
7623
7279
  maxPreviousOutputChars: parseEnvNumber("FIFONY_PREVIOUS_OUTPUT_CHARS", 2e4),
7624
- agentProvider: normalizeAgentProvider(env4.FIFONY_AGENT_PROVIDER ?? "codex"),
7625
- agentCommand: toStringValue(env4.FIFONY_AGENT_COMMAND, ""),
7280
+ agentProvider: normalizeAgentProvider(env3.FIFONY_AGENT_PROVIDER ?? "codex"),
7281
+ agentCommand: toStringValue(env3.FIFONY_AGENT_COMMAND, ""),
7626
7282
  defaultEffort: {
7627
- default: parseEffortValue(env4.FIFONY_REASONING_EFFORT),
7628
- planner: parseEffortValue(env4.FIFONY_PLANNER_EFFORT),
7629
- executor: parseEffortValue(env4.FIFONY_EXECUTOR_EFFORT),
7630
- reviewer: parseEffortValue(env4.FIFONY_REVIEWER_EFFORT)
7283
+ default: parseEffortValue(env3.FIFONY_REASONING_EFFORT),
7284
+ planner: parseEffortValue(env3.FIFONY_PLANNER_EFFORT),
7285
+ executor: parseEffortValue(env3.FIFONY_EXECUTOR_EFFORT),
7286
+ reviewer: parseEffortValue(env3.FIFONY_REVIEWER_EFFORT)
7631
7287
  },
7632
7288
  maxConcurrentByState: {},
7633
7289
  runMode: "filesystem",
7634
7290
  autoReviewApproval: true,
7635
- afterCreateHook: env4.FIFONY_AFTER_CREATE_HOOK ?? "",
7636
- beforeRunHook: env4.FIFONY_BEFORE_RUN_HOOK ?? "",
7637
- afterRunHook: env4.FIFONY_AFTER_RUN_HOOK ?? "",
7638
- beforeRemoveHook: env4.FIFONY_BEFORE_REMOVE_HOOK ?? ""
7291
+ afterCreateHook: env3.FIFONY_AFTER_CREATE_HOOK ?? "",
7292
+ beforeRunHook: env3.FIFONY_BEFORE_RUN_HOOK ?? "",
7293
+ afterRunHook: env3.FIFONY_AFTER_RUN_HOOK ?? "",
7294
+ beforeRemoveHook: env3.FIFONY_BEFORE_REMOVE_HOOK ?? ""
7639
7295
  };
7640
7296
  }
7641
7297
  function applyWorkflowConfig(config, port) {
@@ -7857,35 +7513,33 @@ function getNextRetryAt(issue, baseMs) {
7857
7513
  const nextDelay = withRetryBackoff(nextAttempt, baseMs);
7858
7514
  return new Date(Date.now() + nextDelay).toISOString();
7859
7515
  }
7860
- async function handleStatePatch(state, issue, payload) {
7861
- const nextState = parseIssueState(payload.state);
7862
- if (!nextState || !ALLOWED_STATES.includes(nextState)) {
7863
- throw new Error(`Unsupported state: ${String(payload.state)}`);
7864
- }
7865
- const sourceState = issue.state;
7866
- const path = findIssueStateMachineTransitionPath(null, issue.state, nextState);
7867
- if (!path || path.length === 0) {
7868
- throw new Error(`No valid transition from '${issue.state}' to '${nextState}' for issue ${issue.id}.`);
7869
- }
7870
- for (const event of path) {
7871
- await transitionIssue(issue, event, { note: `Manual state update: ${nextState}`, reason: toStringValue(payload.reason) });
7872
- }
7873
- if (nextState === "Running" && sourceState === "Queued") {
7874
- try {
7875
- const { enqueue: enqueue2 } = await import("./queue-workers-TDJWHJMJ.js");
7876
- await enqueue2(issue, "execute");
7877
- } catch (error) {
7878
- logger.warn({ issueId: issue.id, err: error }, "[Issues] Failed to enqueue after manual Running transition");
7879
- }
7880
- }
7881
- if (nextState === "PendingApproval") {
7882
- issue.nextRetryAt = void 0;
7883
- issue.lastError = void 0;
7516
+
7517
+ // src/commands/request-rework.command.ts
7518
+ async function requestReworkCommand(input, deps) {
7519
+ const { issue, reviewerFeedback, note } = input;
7520
+ if (issue.state !== "Reviewing" && issue.state !== "PendingDecision") {
7521
+ throw new Error(
7522
+ `requestReworkCommand requires Reviewing or PendingDecision state, got ${issue.state}.`
7523
+ );
7884
7524
  }
7885
- if (nextState === "Cancelled") {
7886
- issue.lastError = toStringValue(payload.reason);
7525
+ issue.lastError = reviewerFeedback;
7526
+ issue.lastFailedPhase = "review";
7527
+ issue.attempts += 1;
7528
+ if (issue.state === "Reviewing") {
7529
+ await transitionIssueCommand(
7530
+ { issue, target: "PendingDecision", note: `Reviewer completed for ${issue.identifier}.` },
7531
+ deps
7532
+ );
7887
7533
  }
7888
- addEvent(state, issue.id, "manual", `Manual state transition to ${nextState}`);
7534
+ await transitionIssueCommand(
7535
+ { issue, target: "Queued", note: note ?? `Reviewer requested rework for ${issue.identifier}.` },
7536
+ deps
7537
+ );
7538
+ deps.eventStore.addEvent(
7539
+ issue.id,
7540
+ "runner",
7541
+ `Issue ${issue.identifier} sent back for rework by reviewer.`
7542
+ );
7889
7543
  }
7890
7544
 
7891
7545
  // src/agents/issue-runner.ts
@@ -7908,8 +7562,23 @@ async function runPlanningJob(state, issue) {
7908
7562
  { persistSession: false }
7909
7563
  );
7910
7564
  issue.plan = plan;
7911
- markIssuePlanDirty(issue.id);
7912
7565
  issue.planVersion = Math.max(issue.planVersion ?? 0, 1);
7566
+ markIssuePlanDirty(issue.id);
7567
+ try {
7568
+ const { getIssuePlanResource: getIssuePlanResource2 } = await import("./store-GZVWNJ6V.js");
7569
+ const planRes = getIssuePlanResource2();
7570
+ if (planRes) {
7571
+ await planRes.replace(issue.id, {
7572
+ id: issue.id,
7573
+ plan: issue.plan,
7574
+ planHistory: issue.planHistory,
7575
+ planVersion: issue.planVersion
7576
+ });
7577
+ logger.debug({ issueId: issue.id, planVersion: issue.planVersion }, "[Agent] Plan flushed to issue_plans resource");
7578
+ }
7579
+ } catch (flushErr) {
7580
+ logger.warn({ err: String(flushErr), issueId: issue.id }, "[Agent] Failed to flush plan to issue_plans resource");
7581
+ }
7913
7582
  if (plan.suggestedPaths?.length && !issue.paths?.length) issue.paths = plan.suggestedPaths;
7914
7583
  if (plan.suggestedEffort && !issue.effort) issue.effort = plan.suggestedEffort;
7915
7584
  if (usage.totalTokens > 0) {
@@ -8140,7 +7809,7 @@ async function runIssueOnce(state, issue, running) {
8140
7809
  const { workspacePath, promptText, promptFile } = await prepareWorkspace(issue, state, state.config.defaultBranch);
8141
7810
  container.issueRepository.markDirty(issue.id);
8142
7811
  try {
8143
- const { getIssueStateResource: getIssueStateResource2 } = await import("./store-DSMN6IKR.js");
7812
+ const { getIssueStateResource: getIssueStateResource2 } = await import("./store-GZVWNJ6V.js");
8144
7813
  const res = getIssueStateResource2();
8145
7814
  if (res) {
8146
7815
  await res.patch(issue.id, {
@@ -8179,6 +7848,13 @@ async function runIssueOnce(state, issue, running) {
8179
7848
  state.metrics = computeMetrics(state.issues);
8180
7849
  state.updatedAt = now();
8181
7850
  await container.persistencePort.persistState(state);
7851
+ if (issue.state === "Reviewing") {
7852
+ try {
7853
+ const { enqueue: enqueue2 } = await import("./queue-workers-2NJZE6L2.js");
7854
+ await enqueue2(issue, "review");
7855
+ } catch {
7856
+ }
7857
+ }
8182
7858
  }
8183
7859
  }
8184
7860
 
@@ -8220,7 +7896,6 @@ export {
8220
7896
  transitionIssue,
8221
7897
  issueDependenciesResolved,
8222
7898
  getNextRetryAt,
8223
- handleStatePatch,
8224
7899
  runAgentSession,
8225
7900
  runAgentPipeline,
8226
7901
  issueHasResumableSession,
@@ -8252,4 +7927,4 @@ export {
8252
7927
  syncReferenceRepositories,
8253
7928
  importReferenceArtifacts
8254
7929
  };
8255
- //# sourceMappingURL=chunk-TSSVMTCS.js.map
7930
+ //# sourceMappingURL=chunk-YJHD4BE4.js.map