fifony 0.1.39 → 0.1.40
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/app/dist/assets/{CommandPalette--xFIYira.js → CommandPalette-dMSFpGLm.js} +1 -1
- package/app/dist/assets/{KeyboardShortcutsHelp-BtrclGbX.js → KeyboardShortcutsHelp-CH5aYlDe.js} +1 -1
- package/app/dist/assets/{OnboardingWizard-AdxAh1n0.js → OnboardingWizard-CdJHsRny.js} +1 -1
- package/app/dist/assets/{analytics.lazy-DaMIOiTI.js → analytics.lazy-CKu4136R.js} +1 -1
- package/app/dist/assets/{index-BWB0OQnx.css → index-BatA8x-K.css} +1 -1
- package/app/dist/assets/index-C13WwYFD.js +54 -0
- package/app/dist/index.html +2 -2
- package/app/dist/service-worker.js +1 -1
- package/dist/agent/run-local.js +5 -5
- package/dist/{agent-AAT7OHAL.js → agent-KDPOZCI5.js} +6 -6
- package/dist/{chunk-AVSRIV47.js → chunk-3QL4QAQ5.js} +22 -14
- package/dist/{chunk-T2YJOZ6N.js → chunk-EPY5TTQK.js} +6 -29
- package/dist/{chunk-DWMY2HBG.js → chunk-LYAI5RPK.js} +428 -145
- package/dist/{chunk-UNYIR5AK.js → chunk-O3FGX4J6.js} +29 -13
- package/dist/{chunk-WBOBY75G.js → chunk-UXXUTDGV.js} +3 -3
- package/dist/cli.js +5 -5
- package/dist/issue-runner-YZM6WQMY.js +15 -0
- package/dist/{issue-state-machine-TEIICCAA.js → issue-state-machine-KOZE5JWX.js} +4 -4
- package/dist/{issues-JPMKO2EE.js → issues-7HQC7OIN.js} +6 -6
- package/dist/mcp/server.js +1 -1
- package/dist/{queue-workers-5JOCROD6.js → queue-workers-ZEZHDX7M.js} +2 -2
- package/dist/{scheduler-GAO2MXGZ.js → scheduler-4R4ZAF25.js} +6 -6
- package/dist/{store-3YSID6N2.js → store-FNUWCFOX.js} +6 -6
- package/dist/{workspace-ZD5H6YOL.js → workspace-AOHHNWL5.js} +3 -3
- package/package.json +1 -1
- package/app/dist/assets/index-D5rsr1We.js +0 -54
- package/dist/issue-runner-JSHZGTKQ.js +0 -15
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
enqueue
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3QL4QAQ5.js";
|
|
4
4
|
import {
|
|
5
5
|
ISSUE_STATE_MACHINE_ID,
|
|
6
6
|
computeMetrics,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
findIssueStateMachineTransitionPath,
|
|
9
9
|
getDirtyEventIds,
|
|
10
10
|
getDirtyIssueIds,
|
|
11
|
+
getIssueResourceStateApi,
|
|
11
12
|
getIssueStateMachinePlugin,
|
|
12
13
|
getMetrics,
|
|
13
14
|
hasDirtyState,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
snapshotAndClearDirtyEventIds,
|
|
25
26
|
snapshotAndClearDirtyIssueIds,
|
|
26
27
|
snapshotAndClearDirtyIssuePlanIds
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-UXXUTDGV.js";
|
|
28
29
|
import {
|
|
29
30
|
ADAPTERS,
|
|
30
31
|
assertIssueHasGitWorktree,
|
|
@@ -52,9 +53,10 @@ import {
|
|
|
52
53
|
parseDiffStats,
|
|
53
54
|
prepareWorkspace,
|
|
54
55
|
readCodexConfig,
|
|
56
|
+
rebaseWorktree,
|
|
55
57
|
runCommandWithTimeout,
|
|
56
58
|
runHook
|
|
57
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-O3FGX4J6.js";
|
|
58
60
|
import {
|
|
59
61
|
logger
|
|
60
62
|
} from "./chunk-DVU3CXWA.js";
|
|
@@ -79,7 +81,7 @@ import {
|
|
|
79
81
|
toStringArray,
|
|
80
82
|
toStringValue,
|
|
81
83
|
withRetryBackoff
|
|
82
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-EPY5TTQK.js";
|
|
83
85
|
import {
|
|
84
86
|
ATTACHMENTS_ROOT,
|
|
85
87
|
COMPLETED_STATES,
|
|
@@ -289,10 +291,10 @@ import {
|
|
|
289
291
|
mkdirSync as mkdirSync8,
|
|
290
292
|
readdirSync as readdirSync4,
|
|
291
293
|
readFileSync as readFileSync11,
|
|
292
|
-
writeFileSync as
|
|
294
|
+
writeFileSync as writeFileSync13
|
|
293
295
|
} from "fs";
|
|
294
296
|
import { homedir as homedir3 } from "os";
|
|
295
|
-
import { basename as basename4, dirname as dirname2, join as
|
|
297
|
+
import { basename as basename4, dirname as dirname2, join as join18, relative as relativePath, resolve as resolve2 } from "path";
|
|
296
298
|
|
|
297
299
|
// src/persistence/plugins/api-runtime-context.ts
|
|
298
300
|
var context = null;
|
|
@@ -494,6 +496,19 @@ function tryParseJsonOutput(output) {
|
|
|
494
496
|
}
|
|
495
497
|
return null;
|
|
496
498
|
}
|
|
499
|
+
function extractUsageArrays(obj) {
|
|
500
|
+
if (!obj) return {};
|
|
501
|
+
const toArr = (v) => {
|
|
502
|
+
if (!Array.isArray(v) || v.length === 0) return void 0;
|
|
503
|
+
return v.filter((s) => typeof s === "string" && s.length > 0);
|
|
504
|
+
};
|
|
505
|
+
return {
|
|
506
|
+
toolsUsed: toArr(obj.tools_used ?? obj.toolsUsed),
|
|
507
|
+
skillsUsed: toArr(obj.skills_used ?? obj.skillsUsed),
|
|
508
|
+
agentsUsed: toArr(obj.agents_used ?? obj.agentsUsed),
|
|
509
|
+
commandsRun: toArr(obj.commands_run ?? obj.commandsRun)
|
|
510
|
+
};
|
|
511
|
+
}
|
|
497
512
|
function readAgentDirective(workspacePath, output, success) {
|
|
498
513
|
const fallbackStatus = success ? "done" : "failed";
|
|
499
514
|
const resultFile = join(workspacePath, "result.json");
|
|
@@ -512,9 +527,26 @@ function readAgentDirective(workspacePath, output, success) {
|
|
|
512
527
|
status: normalizeAgentDirectiveStatus(jsonOutput.status, fallbackStatus),
|
|
513
528
|
summary: toStringValue(jsonOutput.summary) || toStringValue(jsonOutput.message) || "",
|
|
514
529
|
nextPrompt: toStringValue(jsonOutput.nextPrompt) || toStringValue(jsonOutput.next_prompt) || "",
|
|
515
|
-
tokenUsage
|
|
530
|
+
tokenUsage,
|
|
531
|
+
...extractUsageArrays(jsonOutput)
|
|
516
532
|
};
|
|
517
533
|
}
|
|
534
|
+
const codeBlocks = [...output.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)].map((m) => m[1].trim()).reverse();
|
|
535
|
+
for (const block of codeBlocks) {
|
|
536
|
+
try {
|
|
537
|
+
const parsed = JSON.parse(block);
|
|
538
|
+
if (parsed?.status) {
|
|
539
|
+
return {
|
|
540
|
+
status: normalizeAgentDirectiveStatus(parsed.status, fallbackStatus),
|
|
541
|
+
summary: toStringValue(parsed.summary) || toStringValue(parsed.message) || "",
|
|
542
|
+
nextPrompt: toStringValue(parsed.nextPrompt) || toStringValue(parsed.next_prompt) || "",
|
|
543
|
+
tokenUsage,
|
|
544
|
+
...extractUsageArrays(parsed)
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
}
|
|
518
550
|
if (existsSync(resultFile)) {
|
|
519
551
|
try {
|
|
520
552
|
const parsed = JSON.parse(readFileSync(resultFile, "utf8"));
|
|
@@ -531,7 +563,7 @@ function readAgentDirective(workspacePath, output, success) {
|
|
|
531
563
|
);
|
|
532
564
|
const summary = toStringValue(resultPayload.summary) || toStringValue(resultPayload.message) || extractOutputMarker(output, "FIFONY_SUMMARY");
|
|
533
565
|
const nextPrompt = toStringValue(resultPayload.nextPrompt) || toStringValue(resultPayload.next_prompt) || "";
|
|
534
|
-
return { status, summary, nextPrompt, tokenUsage };
|
|
566
|
+
return { status, summary, nextPrompt, tokenUsage, ...extractUsageArrays(resultPayload) };
|
|
535
567
|
}
|
|
536
568
|
|
|
537
569
|
// src/agents/pid-manager.ts
|
|
@@ -1348,6 +1380,10 @@ ${previousOutput.slice(-maxOutputChars)}` : previousOutput;
|
|
|
1348
1380
|
}
|
|
1349
1381
|
addEvent(state, issue.id, "info", parts.join(" "));
|
|
1350
1382
|
}
|
|
1383
|
+
if (directive.toolsUsed?.length) issue.toolsUsed = [.../* @__PURE__ */ new Set([...issue.toolsUsed ?? [], ...directive.toolsUsed])];
|
|
1384
|
+
if (directive.skillsUsed?.length) issue.skillsUsed = [.../* @__PURE__ */ new Set([...issue.skillsUsed ?? [], ...directive.skillsUsed])];
|
|
1385
|
+
if (directive.agentsUsed?.length) issue.agentsUsed = [.../* @__PURE__ */ new Set([...issue.agentsUsed ?? [], ...directive.agentsUsed])];
|
|
1386
|
+
if (directive.commandsRun?.length) issue.commandsRun = [.../* @__PURE__ */ new Set([...issue.commandsRun ?? [], ...directive.commandsRun])];
|
|
1351
1387
|
session.turns.push({
|
|
1352
1388
|
turn: turnIndex,
|
|
1353
1389
|
role: provider.role,
|
|
@@ -1362,7 +1398,11 @@ ${previousOutput.slice(-maxOutputChars)}` : previousOutput;
|
|
|
1362
1398
|
directiveStatus: directive.status,
|
|
1363
1399
|
directiveSummary: directive.summary,
|
|
1364
1400
|
nextPrompt: directive.nextPrompt,
|
|
1365
|
-
tokenUsage: directive.tokenUsage
|
|
1401
|
+
tokenUsage: directive.tokenUsage,
|
|
1402
|
+
toolsUsed: directive.toolsUsed,
|
|
1403
|
+
skillsUsed: directive.skillsUsed,
|
|
1404
|
+
agentsUsed: directive.agentsUsed,
|
|
1405
|
+
commandsRun: directive.commandsRun
|
|
1366
1406
|
});
|
|
1367
1407
|
session.lastCode = lastCode;
|
|
1368
1408
|
session.lastOutput = lastOutput;
|
|
@@ -2453,7 +2493,7 @@ function generatePlanInBackground(issue, config, _workflowDefinition, callbacks,
|
|
|
2453
2493
|
applyUsage(issue, usage);
|
|
2454
2494
|
applySuggestions(issue, plan);
|
|
2455
2495
|
try {
|
|
2456
|
-
const { savePlanForIssue: savePlanForIssue2 } = await import("./store-
|
|
2496
|
+
const { savePlanForIssue: savePlanForIssue2 } = await import("./store-FNUWCFOX.js");
|
|
2457
2497
|
await savePlanForIssue2(issue.id, plan, issue.planVersion);
|
|
2458
2498
|
} catch {
|
|
2459
2499
|
}
|
|
@@ -2490,7 +2530,7 @@ function refinePlanInBackground(issue, feedback, config, _workflowDefinition, ca
|
|
|
2490
2530
|
applyUsage(issue, usage);
|
|
2491
2531
|
applySuggestions(issue, plan);
|
|
2492
2532
|
try {
|
|
2493
|
-
const { savePlanForIssue: savePlanForIssue2 } = await import("./store-
|
|
2533
|
+
const { savePlanForIssue: savePlanForIssue2 } = await import("./store-FNUWCFOX.js");
|
|
2494
2534
|
await savePlanForIssue2(issue.id, plan, issue.planVersion);
|
|
2495
2535
|
} catch {
|
|
2496
2536
|
}
|
|
@@ -2768,7 +2808,7 @@ async function runPlanningJob(state, issue) {
|
|
|
2768
2808
|
issue.plan = plan;
|
|
2769
2809
|
issue.planVersion = Math.max(issue.planVersion ?? 0, 1);
|
|
2770
2810
|
try {
|
|
2771
|
-
const { savePlanForIssue: savePlanForIssue2 } = await import("./store-
|
|
2811
|
+
const { savePlanForIssue: savePlanForIssue2 } = await import("./store-FNUWCFOX.js");
|
|
2772
2812
|
await savePlanForIssue2(issue.id, plan, issue.planVersion);
|
|
2773
2813
|
logger.debug({ issueId: issue.id, planVersion: issue.planVersion }, "[Agent] Plan saved to issue_plans resource");
|
|
2774
2814
|
} catch (err) {
|
|
@@ -3004,7 +3044,7 @@ async function runIssueOnce(state, issue, running) {
|
|
|
3004
3044
|
const { workspacePath, promptText, promptFile } = await prepareWorkspace(issue, state, state.config.defaultBranch);
|
|
3005
3045
|
container.issueRepository.markDirty(issue.id);
|
|
3006
3046
|
try {
|
|
3007
|
-
const { getIssueStateResource: getIssueStateResource2 } = await import("./store-
|
|
3047
|
+
const { getIssueStateResource: getIssueStateResource2 } = await import("./store-FNUWCFOX.js");
|
|
3008
3048
|
const res = getIssueStateResource2();
|
|
3009
3049
|
if (res) {
|
|
3010
3050
|
await res.patch(issue.id, {
|
|
@@ -3043,13 +3083,6 @@ async function runIssueOnce(state, issue, running) {
|
|
|
3043
3083
|
state.metrics = computeMetrics(state.issues);
|
|
3044
3084
|
state.updatedAt = now();
|
|
3045
3085
|
await container.persistencePort.persistState(state);
|
|
3046
|
-
if (issue.state === "Reviewing") {
|
|
3047
|
-
try {
|
|
3048
|
-
const { enqueue: enqueue2 } = await import("./queue-workers-5JOCROD6.js");
|
|
3049
|
-
await enqueue2(issue, "review");
|
|
3050
|
-
} catch {
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
3086
|
}
|
|
3054
3087
|
}
|
|
3055
3088
|
|
|
@@ -3134,9 +3167,122 @@ async function cancelIssueCommand(input, deps) {
|
|
|
3134
3167
|
deps.eventStore.addEvent(issue.id, "manual", `Manual cancel requested for ${issue.id}.`);
|
|
3135
3168
|
}
|
|
3136
3169
|
|
|
3170
|
+
// src/commands/delete-issue.command.ts
|
|
3171
|
+
async function deleteIssueCommand(input) {
|
|
3172
|
+
const { issue, state } = input;
|
|
3173
|
+
const pidInfo = issue.workspacePath ? readAgentPid(issue.workspacePath) : null;
|
|
3174
|
+
if (pidInfo) {
|
|
3175
|
+
try {
|
|
3176
|
+
process.kill(-pidInfo.pid, "SIGTERM");
|
|
3177
|
+
logger.info({ pid: pidInfo.pid, issueId: issue.id }, "[Delete] Sent SIGTERM to agent process group");
|
|
3178
|
+
} catch {
|
|
3179
|
+
try {
|
|
3180
|
+
process.kill(pidInfo.pid, "SIGTERM");
|
|
3181
|
+
} catch {
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
try {
|
|
3186
|
+
await cleanWorkspace(issue.id, issue, state);
|
|
3187
|
+
} catch (error) {
|
|
3188
|
+
logger.warn({ issueId: issue.id, err: String(error) }, "[Delete] Workspace cleanup failed (continuing)");
|
|
3189
|
+
}
|
|
3190
|
+
const idx = state.issues.findIndex((i) => i.id === issue.id);
|
|
3191
|
+
if (idx >= 0) state.issues.splice(idx, 1);
|
|
3192
|
+
state.events = state.events.filter((e) => e.issueId !== issue.id);
|
|
3193
|
+
try {
|
|
3194
|
+
const fsmApi = getIssueResourceStateApi();
|
|
3195
|
+
if (fsmApi?.delete) await fsmApi.delete(issue.id);
|
|
3196
|
+
} catch (error) {
|
|
3197
|
+
logger.debug({ issueId: issue.id, err: String(error) }, "[Delete] FSM state cleanup (non-critical)");
|
|
3198
|
+
}
|
|
3199
|
+
try {
|
|
3200
|
+
const resource = getIssueStateResource();
|
|
3201
|
+
if (resource && typeof resource.delete === "function") {
|
|
3202
|
+
await resource.delete(issue.id);
|
|
3203
|
+
}
|
|
3204
|
+
} catch (error) {
|
|
3205
|
+
logger.debug({ issueId: issue.id, err: String(error) }, "[Delete] s3db record cleanup (non-critical)");
|
|
3206
|
+
}
|
|
3207
|
+
logger.info({ issueId: issue.id, identifier: issue.identifier }, "[Delete] Issue deleted");
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3137
3210
|
// src/commands/merge-workspace.command.ts
|
|
3138
3211
|
import { existsSync as existsSync7 } from "fs";
|
|
3212
|
+
import { execSync as execSync3 } from "child_process";
|
|
3213
|
+
|
|
3214
|
+
// src/domains/merge-conflict-resolver.ts
|
|
3139
3215
|
import { execSync as execSync2 } from "child_process";
|
|
3216
|
+
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync8, rmSync as rmSync4 } from "fs";
|
|
3217
|
+
import { join as join12 } from "path";
|
|
3218
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
3219
|
+
async function resolveConflictsWithAgent(options) {
|
|
3220
|
+
const { issue, conflictFiles, provider, model, targetRoot } = options;
|
|
3221
|
+
const startMs = Date.now();
|
|
3222
|
+
const adapter = ADAPTERS[provider];
|
|
3223
|
+
if (!adapter) {
|
|
3224
|
+
return { resolved: false, resolvedFiles: [], output: `No adapter for provider "${provider}"`, provider, durationMs: 0, resolvedAt: now() };
|
|
3225
|
+
}
|
|
3226
|
+
const command = adapter.buildCommand({ model });
|
|
3227
|
+
if (!command) {
|
|
3228
|
+
return { resolved: false, resolvedFiles: [], output: `Adapter returned empty command for "${provider}"`, provider, durationMs: 0, resolvedAt: now() };
|
|
3229
|
+
}
|
|
3230
|
+
const prompt = await renderPrompt("merge-conflict-resolver", {
|
|
3231
|
+
issueIdentifier: issue.identifier,
|
|
3232
|
+
title: issue.title,
|
|
3233
|
+
description: issue.description || "",
|
|
3234
|
+
baseBranch: issue.baseBranch || "main",
|
|
3235
|
+
featureBranch: issue.branchName || "unknown",
|
|
3236
|
+
conflictFiles
|
|
3237
|
+
});
|
|
3238
|
+
const tempDir = mkdtempSync3(join12(tmpdir3(), "fifony-conflict-"));
|
|
3239
|
+
const promptFile = join12(tempDir, "fifony-conflict-prompt.md");
|
|
3240
|
+
writeFileSync8(promptFile, `${prompt}
|
|
3241
|
+
`, "utf8");
|
|
3242
|
+
let output = "";
|
|
3243
|
+
try {
|
|
3244
|
+
output = await runPlanningProcess({
|
|
3245
|
+
command,
|
|
3246
|
+
tempDir: targetRoot,
|
|
3247
|
+
// agent runs in TARGET_ROOT where conflict markers exist
|
|
3248
|
+
promptFile,
|
|
3249
|
+
provider
|
|
3250
|
+
});
|
|
3251
|
+
} catch (err) {
|
|
3252
|
+
output = String(err);
|
|
3253
|
+
logger.error({ err, issueId: issue.id }, "[ConflictResolver] Agent process failed");
|
|
3254
|
+
} finally {
|
|
3255
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
3256
|
+
}
|
|
3257
|
+
let remainingConflicts = [];
|
|
3258
|
+
try {
|
|
3259
|
+
const unmerged = execSync2("git diff --name-only --diff-filter=U", { cwd: targetRoot, encoding: "utf8" }).trim();
|
|
3260
|
+
remainingConflicts = unmerged ? unmerged.split("\n").filter(Boolean) : [];
|
|
3261
|
+
} catch {
|
|
3262
|
+
}
|
|
3263
|
+
const resolved = remainingConflicts.length === 0;
|
|
3264
|
+
const resolvedFiles = resolved ? conflictFiles : conflictFiles.filter((f) => !remainingConflicts.includes(f));
|
|
3265
|
+
const durationMs = Date.now() - startMs;
|
|
3266
|
+
logger.info({
|
|
3267
|
+
issueId: issue.id,
|
|
3268
|
+
resolved,
|
|
3269
|
+
resolvedFiles,
|
|
3270
|
+
remainingConflicts,
|
|
3271
|
+
durationMs,
|
|
3272
|
+
provider
|
|
3273
|
+
}, "[ConflictResolver] Resolution attempt complete");
|
|
3274
|
+
return {
|
|
3275
|
+
resolved,
|
|
3276
|
+
resolvedFiles,
|
|
3277
|
+
output: output.slice(-2e3),
|
|
3278
|
+
// tail for debugging
|
|
3279
|
+
provider,
|
|
3280
|
+
durationMs,
|
|
3281
|
+
resolvedAt: now()
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
// src/commands/merge-workspace.command.ts
|
|
3140
3286
|
async function mergeWorkspaceCommand(input, deps) {
|
|
3141
3287
|
const { issue, state, squashAlreadyApplied } = input;
|
|
3142
3288
|
if (!["Approved", "Reviewing", "PendingDecision"].includes(issue.state)) {
|
|
@@ -3155,7 +3301,7 @@ async function mergeWorkspaceCommand(input, deps) {
|
|
|
3155
3301
|
}
|
|
3156
3302
|
if (issue.branchName && issue.baseBranch) {
|
|
3157
3303
|
try {
|
|
3158
|
-
const stat =
|
|
3304
|
+
const stat = execSync3(
|
|
3159
3305
|
`git diff --stat "${issue.baseBranch}"..."${issue.branchName}"`,
|
|
3160
3306
|
{ encoding: "utf8", cwd: TARGET_ROOT, stdio: "pipe", maxBuffer: 512e3, timeout: 1e4 }
|
|
3161
3307
|
);
|
|
@@ -3172,11 +3318,28 @@ async function mergeWorkspaceCommand(input, deps) {
|
|
|
3172
3318
|
throw new Error(`Validation gate failed (${validation.command}): ${validation.output.slice(0, 500)}`);
|
|
3173
3319
|
}
|
|
3174
3320
|
}
|
|
3321
|
+
if (!squashAlreadyApplied && issue.worktreePath && issue.baseBranch) {
|
|
3322
|
+
try {
|
|
3323
|
+
const rebase = rebaseWorktree(issue);
|
|
3324
|
+
issue.rebaseResult = { success: rebase.success, conflictFiles: rebase.conflictFiles, rebasedAt: now() };
|
|
3325
|
+
deps.issueRepository.markDirty(issue.id);
|
|
3326
|
+
if (rebase.success) {
|
|
3327
|
+
deps.eventStore.addEvent(issue.id, "info", `Auto-rebase onto ${issue.baseBranch} succeeded \u2014 branch is up to date.`);
|
|
3328
|
+
logger.info({ issueId: issue.id, baseBranch: issue.baseBranch }, "[Merge] Auto-rebase succeeded");
|
|
3329
|
+
} else {
|
|
3330
|
+
const files = rebase.conflictFiles.join(", ");
|
|
3331
|
+
deps.eventStore.addEvent(issue.id, "error", `Auto-rebase onto ${issue.baseBranch} failed \u2014 ${rebase.conflictFiles.length} conflict(s): ${files}. Proceeding with direct merge attempt.`);
|
|
3332
|
+
logger.warn({ issueId: issue.id, conflictFiles: rebase.conflictFiles }, "[Merge] Auto-rebase failed, will attempt direct merge");
|
|
3333
|
+
}
|
|
3334
|
+
} catch (err) {
|
|
3335
|
+
logger.warn({ issueId: issue.id, err: String(err) }, "[Merge] Auto-rebase threw unexpectedly, skipping");
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3175
3338
|
let result;
|
|
3176
3339
|
if (squashAlreadyApplied) {
|
|
3177
3340
|
try {
|
|
3178
|
-
|
|
3179
|
-
|
|
3341
|
+
execSync3("git add -A", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 1e4 });
|
|
3342
|
+
execSync3(
|
|
3180
3343
|
`git commit -m "fifony: merge ${issue.identifier}"`,
|
|
3181
3344
|
{ cwd: TARGET_ROOT, stdio: "pipe", timeout: 1e4 }
|
|
3182
3345
|
);
|
|
@@ -3188,26 +3351,89 @@ async function mergeWorkspaceCommand(input, deps) {
|
|
|
3188
3351
|
result = { copied: [], deleted: [], skipped: [], conflicts: [] };
|
|
3189
3352
|
} else {
|
|
3190
3353
|
try {
|
|
3191
|
-
const indexStatus =
|
|
3192
|
-
const wtStatus =
|
|
3354
|
+
const indexStatus = execSync3("git diff --cached --name-only", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
|
|
3355
|
+
const wtStatus = execSync3("git diff --name-only", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
|
|
3193
3356
|
if (indexStatus && !wtStatus) {
|
|
3194
|
-
|
|
3357
|
+
execSync3("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
3195
3358
|
logger.info({ issueId: issue.id }, "[Command] Cleared residual squash from index before merge");
|
|
3196
3359
|
}
|
|
3197
3360
|
} catch {
|
|
3198
3361
|
}
|
|
3199
|
-
const mergeResult = mergeWorkspace(
|
|
3362
|
+
const mergeResult = mergeWorkspace(
|
|
3363
|
+
issue,
|
|
3364
|
+
/* abortOnConflict */
|
|
3365
|
+
false
|
|
3366
|
+
);
|
|
3200
3367
|
result = mergeResult;
|
|
3368
|
+
if (result.conflicts.length > 0) {
|
|
3369
|
+
deps.eventStore.addEvent(issue.id, "info", `Merge conflicts in ${result.conflicts.length} file(s): ${result.conflicts.join(", ")}. Attempting agent-based resolution...`);
|
|
3370
|
+
logger.info({ issueId: issue.id, conflicts: result.conflicts }, "[Merge] Conflicts detected \u2014 spawning agent to resolve");
|
|
3371
|
+
try {
|
|
3372
|
+
const { provider, model } = await resolvePlanStageConfig(state.config);
|
|
3373
|
+
const resolution = await resolveConflictsWithAgent({
|
|
3374
|
+
issue,
|
|
3375
|
+
conflictFiles: result.conflicts,
|
|
3376
|
+
provider,
|
|
3377
|
+
model,
|
|
3378
|
+
targetRoot: TARGET_ROOT
|
|
3379
|
+
});
|
|
3380
|
+
if (resolution.resolved) {
|
|
3381
|
+
try {
|
|
3382
|
+
execSync3("git add -A", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 1e4 });
|
|
3383
|
+
execSync3(
|
|
3384
|
+
`git commit --no-edit`,
|
|
3385
|
+
{ cwd: TARGET_ROOT, stdio: "pipe", timeout: 1e4 }
|
|
3386
|
+
);
|
|
3387
|
+
result.conflicts = [];
|
|
3388
|
+
deps.eventStore.addEvent(issue.id, "info", `Agent (${resolution.provider}) resolved ${resolution.resolvedFiles.length} conflict(s) in ${Math.round(resolution.durationMs / 1e3)}s.`);
|
|
3389
|
+
logger.info({ issueId: issue.id, provider: resolution.provider, durationMs: resolution.durationMs }, "[Merge] Agent resolved all conflicts \u2014 merge committed");
|
|
3390
|
+
} catch (commitErr) {
|
|
3391
|
+
try {
|
|
3392
|
+
execSync3("git merge --abort", { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
3393
|
+
} catch {
|
|
3394
|
+
}
|
|
3395
|
+
deps.eventStore.addEvent(issue.id, "error", `Agent resolved conflicts but merge commit failed: ${String(commitErr)}`);
|
|
3396
|
+
logger.error({ issueId: issue.id, err: String(commitErr) }, "[Merge] Commit after conflict resolution failed");
|
|
3397
|
+
}
|
|
3398
|
+
} else {
|
|
3399
|
+
try {
|
|
3400
|
+
execSync3("git merge --abort", { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
3401
|
+
} catch {
|
|
3402
|
+
}
|
|
3403
|
+
deps.eventStore.addEvent(issue.id, "error", `Agent-based conflict resolution failed (${resolution.provider}, ${Math.round(resolution.durationMs / 1e3)}s). ${resolution.resolvedFiles.length}/${result.conflicts.length} files resolved.`);
|
|
3404
|
+
logger.warn({ issueId: issue.id, resolvedFiles: resolution.resolvedFiles, provider: resolution.provider }, "[Merge] Agent failed to resolve all conflicts");
|
|
3405
|
+
}
|
|
3406
|
+
issue.mergeResult = {
|
|
3407
|
+
...issue.mergeResult,
|
|
3408
|
+
conflictResolution: {
|
|
3409
|
+
resolved: resolution.resolved,
|
|
3410
|
+
provider: resolution.provider,
|
|
3411
|
+
resolvedFiles: resolution.resolvedFiles,
|
|
3412
|
+
durationMs: resolution.durationMs,
|
|
3413
|
+
output: resolution.output.slice(-500),
|
|
3414
|
+
resolvedAt: resolution.resolvedAt
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
} catch (err) {
|
|
3418
|
+
try {
|
|
3419
|
+
execSync3("git merge --abort", { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
3420
|
+
} catch {
|
|
3421
|
+
}
|
|
3422
|
+
deps.eventStore.addEvent(issue.id, "error", `Agent conflict resolution threw: ${String(err)}`);
|
|
3423
|
+
logger.error({ issueId: issue.id, err: String(err) }, "[Merge] Conflict resolution threw unexpectedly");
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3201
3426
|
}
|
|
3202
3427
|
issue.mergeResult = {
|
|
3203
3428
|
copied: result.copied.length,
|
|
3204
3429
|
deleted: result.deleted.length,
|
|
3205
3430
|
skipped: result.skipped.length,
|
|
3206
3431
|
conflicts: result.conflicts.length,
|
|
3207
|
-
conflictFiles: result.conflicts.length > 0 ? result.conflicts : void 0
|
|
3432
|
+
conflictFiles: result.conflicts.length > 0 ? result.conflicts : void 0,
|
|
3433
|
+
...issue.mergeResult?.conflictResolution ? { conflictResolution: issue.mergeResult.conflictResolution } : {}
|
|
3208
3434
|
};
|
|
3209
3435
|
if (result.conflicts.length > 0) {
|
|
3210
|
-
deps.eventStore.addEvent(issue.id, "error", `Merge conflicts: ${result.conflicts.join(", ")}`);
|
|
3436
|
+
deps.eventStore.addEvent(issue.id, "error", `Merge aborted \u2014 ${result.conflicts.length} conflict(s) remain: ${result.conflicts.join(", ")}`);
|
|
3211
3437
|
await deps.persistencePort.persistState(state);
|
|
3212
3438
|
return result;
|
|
3213
3439
|
}
|
|
@@ -3229,7 +3455,7 @@ async function mergeWorkspaceCommand(input, deps) {
|
|
|
3229
3455
|
}
|
|
3230
3456
|
|
|
3231
3457
|
// src/commands/push-workspace.command.ts
|
|
3232
|
-
import { execFileSync, execSync as
|
|
3458
|
+
import { execFileSync, execSync as execSync4 } from "child_process";
|
|
3233
3459
|
function isGhAvailable() {
|
|
3234
3460
|
try {
|
|
3235
3461
|
execFileSync("gh", ["--version"], { stdio: "pipe", timeout: 5e3 });
|
|
@@ -3240,7 +3466,7 @@ function isGhAvailable() {
|
|
|
3240
3466
|
}
|
|
3241
3467
|
function getCompareUrl(branchName, baseBranch) {
|
|
3242
3468
|
try {
|
|
3243
|
-
const remote =
|
|
3469
|
+
const remote = execSync4("git remote get-url origin", { cwd: TARGET_ROOT, encoding: "utf8", stdio: "pipe" }).trim();
|
|
3244
3470
|
const cleanRemote = remote.replace(/\.git$/, "");
|
|
3245
3471
|
return `${cleanRemote}/compare/${baseBranch}...${branchName}`;
|
|
3246
3472
|
} catch {
|
|
@@ -3275,8 +3501,8 @@ async function pushWorkspaceCommand(input, deps) {
|
|
|
3275
3501
|
assertIssueHasGitWorktree(issue, "push");
|
|
3276
3502
|
if (issue.testApplied) {
|
|
3277
3503
|
try {
|
|
3278
|
-
|
|
3279
|
-
|
|
3504
|
+
execSync4("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
|
|
3505
|
+
execSync4("git clean -fd", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
|
|
3280
3506
|
issue.testApplied = false;
|
|
3281
3507
|
deps.eventStore.addEvent(issue.id, "info", "Auto-reverted test squash before push.");
|
|
3282
3508
|
logger.info({ issueId: issue.id }, "[Push] Auto-reverted test squash before push");
|
|
@@ -3303,7 +3529,7 @@ async function pushWorkspaceCommand(input, deps) {
|
|
|
3303
3529
|
const planSummary = issue.plan?.summary ?? issue.title;
|
|
3304
3530
|
let diffStat = "";
|
|
3305
3531
|
try {
|
|
3306
|
-
diffStat =
|
|
3532
|
+
diffStat = execSync4(
|
|
3307
3533
|
`git diff --stat "${issue.baseBranch}"..."${issue.branchName}"`,
|
|
3308
3534
|
{ cwd: TARGET_ROOT, encoding: "utf8", maxBuffer: 512e3, timeout: 1e4, stdio: "pipe" }
|
|
3309
3535
|
).trim();
|
|
@@ -3318,7 +3544,7 @@ ${diffStat || "No diff stats available"}
|
|
|
3318
3544
|
\`\`\`
|
|
3319
3545
|
|
|
3320
3546
|
*Automated by fifony*`;
|
|
3321
|
-
|
|
3547
|
+
execSync4(`git push -u origin "${issue.branchName}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
3322
3548
|
const prBase = state.config.prBaseBranch || issue.baseBranch;
|
|
3323
3549
|
const ghAvailable = isGhAvailable();
|
|
3324
3550
|
let prUrl;
|
|
@@ -3506,6 +3732,27 @@ async function cancelIssue(c) {
|
|
|
3506
3732
|
await persistState(context2.state);
|
|
3507
3733
|
return { body: { ok: true, issue } };
|
|
3508
3734
|
}
|
|
3735
|
+
async function deleteIssue(c) {
|
|
3736
|
+
const context2 = getApiRuntimeContextOrThrow();
|
|
3737
|
+
const issueId = getIssueId(c);
|
|
3738
|
+
if (!issueId) {
|
|
3739
|
+
return { status: 400, body: { ok: false, error: "Issue id is required." } };
|
|
3740
|
+
}
|
|
3741
|
+
const issue = findIssue(context2.state, issueId);
|
|
3742
|
+
if (!issue) {
|
|
3743
|
+
return { status: 404, body: { ok: false, error: "Issue not found" } };
|
|
3744
|
+
}
|
|
3745
|
+
if (issue.state === "Running" || issue.state === "Reviewing") {
|
|
3746
|
+
return { status: 409, body: { ok: false, error: `Cannot delete issue in state ${issue.state}. Cancel it first.` } };
|
|
3747
|
+
}
|
|
3748
|
+
try {
|
|
3749
|
+
await deleteIssueCommand({ issue, state: context2.state });
|
|
3750
|
+
await persistState(context2.state);
|
|
3751
|
+
return { body: { ok: true, id: issueId } };
|
|
3752
|
+
} catch (error) {
|
|
3753
|
+
return { status: 500, body: { ok: false, error: error instanceof Error ? error.message : String(error) } };
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3509
3756
|
async function approveAndMerge(c) {
|
|
3510
3757
|
const context2 = getApiRuntimeContextOrThrow();
|
|
3511
3758
|
const issueId = getIssueId(c);
|
|
@@ -3668,6 +3915,13 @@ var issues_resource_default = {
|
|
|
3668
3915
|
return c.json(result.body, result.status);
|
|
3669
3916
|
}
|
|
3670
3917
|
return result.body;
|
|
3918
|
+
},
|
|
3919
|
+
"POST /:id/delete": async (c) => {
|
|
3920
|
+
const result = await deleteIssue(c);
|
|
3921
|
+
if (result.status) {
|
|
3922
|
+
return c.json(result.body, result.status);
|
|
3923
|
+
}
|
|
3924
|
+
return result.body;
|
|
3671
3925
|
}
|
|
3672
3926
|
}
|
|
3673
3927
|
};
|
|
@@ -4064,7 +4318,7 @@ function hasTerminalQueue(state) {
|
|
|
4064
4318
|
import { execFile as execFile2 } from "child_process";
|
|
4065
4319
|
import { promisify } from "util";
|
|
4066
4320
|
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync2, realpathSync } from "fs";
|
|
4067
|
-
import { join as
|
|
4321
|
+
import { join as join13, dirname } from "path";
|
|
4068
4322
|
import { homedir as homedir2 } from "os";
|
|
4069
4323
|
import { env } from "process";
|
|
4070
4324
|
var execFileAsync = promisify(execFile2);
|
|
@@ -4096,7 +4350,7 @@ function resolveCodexHomeCandidates() {
|
|
|
4096
4350
|
]);
|
|
4097
4351
|
const candidates = [...homePaths, ...direct].filter(Boolean).flatMap((candidate) => {
|
|
4098
4352
|
if (candidate.endsWith("/.codex") || candidate.endsWith("/codex")) return [candidate];
|
|
4099
|
-
return [
|
|
4353
|
+
return [join13(candidate, ".codex"), join13(candidate, "codex")];
|
|
4100
4354
|
});
|
|
4101
4355
|
return [...new Set(candidates)];
|
|
4102
4356
|
}
|
|
@@ -4109,11 +4363,11 @@ function resolveCodexDir() {
|
|
|
4109
4363
|
return null;
|
|
4110
4364
|
}
|
|
4111
4365
|
function findLatestCodexDb(codexDir) {
|
|
4112
|
-
const explicit =
|
|
4366
|
+
const explicit = join13(codexDir, "state_5.sqlite");
|
|
4113
4367
|
if (existsSync8(explicit)) return explicit;
|
|
4114
4368
|
const candidates = readdirSync2(codexDir).filter((name) => name.startsWith("state_") && name.endsWith(".sqlite")).sort().reverse();
|
|
4115
4369
|
if (candidates.length === 0) return null;
|
|
4116
|
-
return
|
|
4370
|
+
return join13(codexDir, candidates[0]);
|
|
4117
4371
|
}
|
|
4118
4372
|
function computeNextMonday() {
|
|
4119
4373
|
const now2 = /* @__PURE__ */ new Date();
|
|
@@ -4174,10 +4428,10 @@ function resolveClaudePlanKey(displayName) {
|
|
|
4174
4428
|
}
|
|
4175
4429
|
async function collectClaudeUsage() {
|
|
4176
4430
|
const home = homedir2();
|
|
4177
|
-
const claudeDir =
|
|
4431
|
+
const claudeDir = join13(home, ".claude");
|
|
4178
4432
|
if (!existsSync8(claudeDir)) return null;
|
|
4179
4433
|
const available = await whichExists("claude");
|
|
4180
|
-
const projectsDir =
|
|
4434
|
+
const projectsDir = join13(claudeDir, "projects");
|
|
4181
4435
|
let totalInputTokens = 0;
|
|
4182
4436
|
let totalOutputTokens = 0;
|
|
4183
4437
|
let totalSessions = 0;
|
|
@@ -4201,7 +4455,7 @@ async function collectClaudeUsage() {
|
|
|
4201
4455
|
const projectDirs = readdirSync2(projectsDir, { withFileTypes: true });
|
|
4202
4456
|
for (const dir of projectDirs) {
|
|
4203
4457
|
if (!dir.isDirectory()) continue;
|
|
4204
|
-
const projectPath =
|
|
4458
|
+
const projectPath = join13(projectsDir, dir.name);
|
|
4205
4459
|
let sessionFiles;
|
|
4206
4460
|
try {
|
|
4207
4461
|
sessionFiles = readdirSync2(projectPath).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -4209,7 +4463,7 @@ async function collectClaudeUsage() {
|
|
|
4209
4463
|
continue;
|
|
4210
4464
|
}
|
|
4211
4465
|
for (const file of sessionFiles) {
|
|
4212
|
-
const filePath =
|
|
4466
|
+
const filePath = join13(projectPath, file);
|
|
4213
4467
|
let content;
|
|
4214
4468
|
try {
|
|
4215
4469
|
content = readFileSync6(filePath, "utf8");
|
|
@@ -4277,7 +4531,7 @@ async function collectClaudeUsage() {
|
|
|
4277
4531
|
let plan = "pro";
|
|
4278
4532
|
let resetInfo = "Weekly reset (every Monday 00:00 UTC)";
|
|
4279
4533
|
let currentModel = "";
|
|
4280
|
-
const settingsPath =
|
|
4534
|
+
const settingsPath = join13(claudeDir, "settings.json");
|
|
4281
4535
|
if (existsSync8(settingsPath)) {
|
|
4282
4536
|
try {
|
|
4283
4537
|
const settings = JSON.parse(readFileSync6(settingsPath, "utf8"));
|
|
@@ -4401,7 +4655,7 @@ function aggregateCodexSessionUsageFromJsonl(lines) {
|
|
|
4401
4655
|
};
|
|
4402
4656
|
}
|
|
4403
4657
|
function collectCodexSessionUsagesFromJsonl(codexDir) {
|
|
4404
|
-
const sessionsDir =
|
|
4658
|
+
const sessionsDir = join13(codexDir, "sessions");
|
|
4405
4659
|
if (!existsSync8(sessionsDir)) return [];
|
|
4406
4660
|
const stack = [sessionsDir];
|
|
4407
4661
|
const usageByFile = [];
|
|
@@ -4417,7 +4671,7 @@ function collectCodexSessionUsagesFromJsonl(codexDir) {
|
|
|
4417
4671
|
continue;
|
|
4418
4672
|
}
|
|
4419
4673
|
for (const entry of entries) {
|
|
4420
|
-
const next =
|
|
4674
|
+
const next = join13(current, entry.name);
|
|
4421
4675
|
if (entry.isDirectory()) {
|
|
4422
4676
|
stack.push(next);
|
|
4423
4677
|
continue;
|
|
@@ -4440,7 +4694,7 @@ async function collectCodexUsage() {
|
|
|
4440
4694
|
if (!codexDir) return null;
|
|
4441
4695
|
const available = await whichExists("codex");
|
|
4442
4696
|
const models = [];
|
|
4443
|
-
const modelsCachePath =
|
|
4697
|
+
const modelsCachePath = join13(codexDir, "models_cache.json");
|
|
4444
4698
|
let currentModel = "";
|
|
4445
4699
|
if (existsSync8(modelsCachePath)) {
|
|
4446
4700
|
try {
|
|
@@ -4455,7 +4709,7 @@ async function collectCodexUsage() {
|
|
|
4455
4709
|
} catch {
|
|
4456
4710
|
}
|
|
4457
4711
|
}
|
|
4458
|
-
const configPath =
|
|
4712
|
+
const configPath = join13(codexDir, "config.toml");
|
|
4459
4713
|
if (existsSync8(configPath)) {
|
|
4460
4714
|
try {
|
|
4461
4715
|
const configContent = readFileSync6(configPath, "utf8");
|
|
@@ -4718,7 +4972,7 @@ function aggregateGeminiSessionUsageFromJson(content) {
|
|
|
4718
4972
|
};
|
|
4719
4973
|
}
|
|
4720
4974
|
function collectGeminiSessionUsages() {
|
|
4721
|
-
const geminiTmp =
|
|
4975
|
+
const geminiTmp = join13(homedir2(), ".gemini", "tmp");
|
|
4722
4976
|
if (!existsSync8(geminiTmp)) return [];
|
|
4723
4977
|
const usages = [];
|
|
4724
4978
|
let entries = [];
|
|
@@ -4729,7 +4983,7 @@ function collectGeminiSessionUsages() {
|
|
|
4729
4983
|
}
|
|
4730
4984
|
for (const profile of entries) {
|
|
4731
4985
|
if (!profile.isDirectory()) continue;
|
|
4732
|
-
const chatsDir =
|
|
4986
|
+
const chatsDir = join13(geminiTmp, profile.name, "chats");
|
|
4733
4987
|
if (!existsSync8(chatsDir)) continue;
|
|
4734
4988
|
let sessions = [];
|
|
4735
4989
|
try {
|
|
@@ -4738,7 +4992,7 @@ function collectGeminiSessionUsages() {
|
|
|
4738
4992
|
continue;
|
|
4739
4993
|
}
|
|
4740
4994
|
for (const sessionFile of sessions) {
|
|
4741
|
-
const sessionPath =
|
|
4995
|
+
const sessionPath = join13(chatsDir, sessionFile);
|
|
4742
4996
|
try {
|
|
4743
4997
|
const usage = aggregateGeminiSessionUsageFromJson(readFileSync6(sessionPath, "utf8"));
|
|
4744
4998
|
if (usage.totalTokens > 0) usages.push(usage);
|
|
@@ -4759,7 +5013,7 @@ async function collectGeminiUsage() {
|
|
|
4759
5013
|
} catch {
|
|
4760
5014
|
}
|
|
4761
5015
|
let account = null;
|
|
4762
|
-
const accountsPath =
|
|
5016
|
+
const accountsPath = join13(homedir2(), ".gemini", "google_accounts.json");
|
|
4763
5017
|
if (existsSync8(accountsPath)) {
|
|
4764
5018
|
try {
|
|
4765
5019
|
const data = JSON.parse(readFileSync6(accountsPath, "utf8"));
|
|
@@ -4777,7 +5031,7 @@ async function collectGeminiUsage() {
|
|
|
4777
5031
|
try {
|
|
4778
5032
|
const { stdout: binPath } = await execFileAsync("which", ["gemini"], { encoding: "utf8", timeout: 3e3 });
|
|
4779
5033
|
const realBin = realpathSync(binPath.trim());
|
|
4780
|
-
const modelsPath =
|
|
5034
|
+
const modelsPath = join13(dirname(dirname(realBin)), "node_modules", "@google", "gemini-cli-core", "dist", "src", "config", "models.js");
|
|
4781
5035
|
if (existsSync8(modelsPath)) {
|
|
4782
5036
|
const content = readFileSync6(modelsPath, "utf8");
|
|
4783
5037
|
const regex = /export const ([A-Z0-9_]+)\s*=\s*'(gemini-[^']+)';/g;
|
|
@@ -4797,7 +5051,7 @@ async function collectGeminiUsage() {
|
|
|
4797
5051
|
} catch {
|
|
4798
5052
|
}
|
|
4799
5053
|
let currentModel = "";
|
|
4800
|
-
const settingsPath =
|
|
5054
|
+
const settingsPath = join13(homedir2(), ".gemini", "settings.json");
|
|
4801
5055
|
if (existsSync8(settingsPath)) {
|
|
4802
5056
|
try {
|
|
4803
5057
|
const settings = JSON.parse(readFileSync6(settingsPath, "utf8"));
|
|
@@ -4930,10 +5184,10 @@ async function collectProviderUsage(providerName) {
|
|
|
4930
5184
|
}
|
|
4931
5185
|
|
|
4932
5186
|
// src/routes/state.ts
|
|
4933
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as
|
|
5187
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
4934
5188
|
import { randomUUID } from "crypto";
|
|
4935
|
-
import { execSync as
|
|
4936
|
-
import { basename as basename2, extname, join as
|
|
5189
|
+
import { execSync as execSync5 } from "child_process";
|
|
5190
|
+
import { basename as basename2, extname, join as join14 } from "path";
|
|
4937
5191
|
|
|
4938
5192
|
// src/commands/approve-plan.command.ts
|
|
4939
5193
|
async function approvePlanCommand(input, deps) {
|
|
@@ -5112,7 +5366,7 @@ function registerStateRoutes(app, state) {
|
|
|
5112
5366
|
if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
|
|
5113
5367
|
const issue = findIssue(state, issueId);
|
|
5114
5368
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
5115
|
-
const { dryMerge } = await import("./workspace-
|
|
5369
|
+
const { dryMerge } = await import("./workspace-AOHHNWL5.js");
|
|
5116
5370
|
const result = dryMerge(issue);
|
|
5117
5371
|
return c.json({ ok: true, ...result });
|
|
5118
5372
|
} catch (error) {
|
|
@@ -5127,8 +5381,8 @@ function registerStateRoutes(app, state) {
|
|
|
5127
5381
|
if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
|
|
5128
5382
|
const issue = findIssue(state, issueId);
|
|
5129
5383
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
5130
|
-
const { rebaseWorktree } = await import("./workspace-
|
|
5131
|
-
const result =
|
|
5384
|
+
const { rebaseWorktree: rebaseWorktree2 } = await import("./workspace-AOHHNWL5.js");
|
|
5385
|
+
const result = rebaseWorktree2(issue);
|
|
5132
5386
|
if (result.success) {
|
|
5133
5387
|
addEvent(state, issue.id, "info", `Branch ${issue.branchName} rebased onto ${issue.baseBranch}.`);
|
|
5134
5388
|
}
|
|
@@ -5149,7 +5403,7 @@ function registerStateRoutes(app, state) {
|
|
|
5149
5403
|
throw new Error("No branch name found for this issue.");
|
|
5150
5404
|
}
|
|
5151
5405
|
try {
|
|
5152
|
-
|
|
5406
|
+
execSync5(
|
|
5153
5407
|
`git merge --squash "${issue.branchName}"`,
|
|
5154
5408
|
{ encoding: "utf8", cwd: TARGET_ROOT, stdio: "pipe", timeout: 3e4 }
|
|
5155
5409
|
);
|
|
@@ -5166,8 +5420,8 @@ function registerStateRoutes(app, state) {
|
|
|
5166
5420
|
logger.info({ issueId: parseIssue(c) }, "[API] POST /api/issues/:id/revert-try");
|
|
5167
5421
|
return mutateIssueState(state, c, async (issue) => {
|
|
5168
5422
|
try {
|
|
5169
|
-
|
|
5170
|
-
|
|
5423
|
+
execSync5("git reset --hard HEAD", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
|
|
5424
|
+
execSync5("git clean -fd", { cwd: TARGET_ROOT, stdio: "pipe", timeout: 15e3 });
|
|
5171
5425
|
} catch (err) {
|
|
5172
5426
|
const msg = err.stderr || err.stdout || String(err);
|
|
5173
5427
|
throw new Error(`git reset/clean failed: ${msg}`);
|
|
@@ -5210,15 +5464,15 @@ function registerStateRoutes(app, state) {
|
|
|
5210
5464
|
if (!Array.isArray(payload.files) || payload.files.length === 0) {
|
|
5211
5465
|
return c.json({ ok: false, error: "No files provided." }, 400);
|
|
5212
5466
|
}
|
|
5213
|
-
const issueAttachDir =
|
|
5467
|
+
const issueAttachDir = join14(ATTACHMENTS_ROOT, issue.id);
|
|
5214
5468
|
mkdirSync6(issueAttachDir, { recursive: true });
|
|
5215
5469
|
const newPaths = [];
|
|
5216
5470
|
for (const file of payload.files) {
|
|
5217
5471
|
if (typeof file.data !== "string" || !file.name) continue;
|
|
5218
5472
|
const safeExt = extname(file.name).replace(/[^a-z0-9.]/gi, "").slice(0, 10) || ".bin";
|
|
5219
5473
|
const safeName = `${randomUUID()}${safeExt}`;
|
|
5220
|
-
const dest =
|
|
5221
|
-
|
|
5474
|
+
const dest = join14(issueAttachDir, safeName);
|
|
5475
|
+
writeFileSync9(dest, Buffer.from(file.data, "base64"));
|
|
5222
5476
|
newPaths.push(dest);
|
|
5223
5477
|
}
|
|
5224
5478
|
issue.images = [...issue.images ?? [], ...newPaths];
|
|
@@ -5238,7 +5492,7 @@ function registerStateRoutes(app, state) {
|
|
|
5238
5492
|
const filename = c.req.param?.("filename") ?? c.req.params?.filename ?? "";
|
|
5239
5493
|
if (!filename) return c.json({ ok: false, error: "Filename is required." }, 400);
|
|
5240
5494
|
const safeName = basename2(filename);
|
|
5241
|
-
const filePath =
|
|
5495
|
+
const filePath = join14(ATTACHMENTS_ROOT, issueId, safeName);
|
|
5242
5496
|
if (!existsSync9(filePath)) return c.json({ ok: false, error: "Image not found." }, 404);
|
|
5243
5497
|
const ext = extname(safeName).toLowerCase();
|
|
5244
5498
|
const mimeMap = {
|
|
@@ -5263,7 +5517,7 @@ function registerStateRoutes(app, state) {
|
|
|
5263
5517
|
const issue = findIssue(state, issueId);
|
|
5264
5518
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
5265
5519
|
try {
|
|
5266
|
-
const { getIssueTransitionHistory } = await import("./issue-state-machine-
|
|
5520
|
+
const { getIssueTransitionHistory } = await import("./issue-state-machine-KOZE5JWX.js");
|
|
5267
5521
|
const limit = parseInt(c.req.query("limit") ?? "50", 10);
|
|
5268
5522
|
const offset = parseInt(c.req.query("offset") ?? "0", 10);
|
|
5269
5523
|
const transitions = await getIssueTransitionHistory(issue.id, { limit, offset });
|
|
@@ -5274,7 +5528,7 @@ function registerStateRoutes(app, state) {
|
|
|
5274
5528
|
});
|
|
5275
5529
|
app.get("/api/state-machine/transitions", async (c) => {
|
|
5276
5530
|
try {
|
|
5277
|
-
const { getStateMachineTransitions } = await import("./issue-state-machine-
|
|
5531
|
+
const { getStateMachineTransitions } = await import("./issue-state-machine-KOZE5JWX.js");
|
|
5278
5532
|
return c.json({ ok: true, transitions: getStateMachineTransitions() });
|
|
5279
5533
|
} catch (error) {
|
|
5280
5534
|
return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 500);
|
|
@@ -5282,7 +5536,7 @@ function registerStateRoutes(app, state) {
|
|
|
5282
5536
|
});
|
|
5283
5537
|
app.get("/api/state-machine/visualize", async (c) => {
|
|
5284
5538
|
try {
|
|
5285
|
-
const { visualizeStateMachine } = await import("./issue-state-machine-
|
|
5539
|
+
const { visualizeStateMachine } = await import("./issue-state-machine-KOZE5JWX.js");
|
|
5286
5540
|
const dot = visualizeStateMachine();
|
|
5287
5541
|
if (!dot) return c.json({ ok: false, error: "Visualization not available." }, 404);
|
|
5288
5542
|
return c.json({ ok: true, dot });
|
|
@@ -5299,10 +5553,10 @@ function registerStateRoutes(app, state) {
|
|
|
5299
5553
|
|
|
5300
5554
|
// src/agents/planning/issue-enhancer.ts
|
|
5301
5555
|
import { env as env2 } from "process";
|
|
5302
|
-
import { existsSync as existsSync10, mkdtempSync as
|
|
5556
|
+
import { existsSync as existsSync10, mkdtempSync as mkdtempSync4, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
5303
5557
|
import { spawn as spawn2 } from "child_process";
|
|
5304
|
-
import { tmpdir as
|
|
5305
|
-
import { join as
|
|
5558
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
5559
|
+
import { join as join15 } from "path";
|
|
5306
5560
|
async function buildPrompt2(field, title, description, issueType, images) {
|
|
5307
5561
|
const context2 = {
|
|
5308
5562
|
title: title || "(empty)",
|
|
@@ -5320,13 +5574,29 @@ function parseEnhancerOutput(raw, expectedField) {
|
|
|
5320
5574
|
if (!text) {
|
|
5321
5575
|
throw new Error("AI provider returned an empty response.");
|
|
5322
5576
|
}
|
|
5323
|
-
const
|
|
5324
|
-
|
|
5325
|
-
|
|
5577
|
+
const codeBlocks = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)].map((m) => m[1].trim()).reverse();
|
|
5578
|
+
for (const block of codeBlocks) {
|
|
5579
|
+
for (const candidate of extractJsonObjects(block)) {
|
|
5580
|
+
const value = parseCandidate(candidate, expectedField);
|
|
5581
|
+
if (value) return value;
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
const sourceText = codeBlocks[0] ?? text;
|
|
5585
|
+
const candidates = extractJsonObjects(sourceText);
|
|
5326
5586
|
for (const candidate of candidates) {
|
|
5327
5587
|
const value = parseCandidate(candidate, expectedField);
|
|
5328
5588
|
if (value) return value;
|
|
5329
5589
|
}
|
|
5590
|
+
const fieldRe = /\{\s*"field"/g;
|
|
5591
|
+
const starts = [];
|
|
5592
|
+
let fm;
|
|
5593
|
+
while ((fm = fieldRe.exec(sourceText)) !== null) starts.push(fm.index);
|
|
5594
|
+
for (let i = starts.length - 1; i >= 0; i--) {
|
|
5595
|
+
for (const candidate of extractJsonObjects(sourceText.slice(starts[i]))) {
|
|
5596
|
+
const value = parseCandidate(candidate, expectedField);
|
|
5597
|
+
if (value) return value;
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5330
5600
|
const cleanedRaw = text.trim();
|
|
5331
5601
|
const trimmed = cleanedRaw.replace(/^\"|\"$/g, "").trim();
|
|
5332
5602
|
if (trimmed) {
|
|
@@ -5349,7 +5619,7 @@ function parseCandidate(raw, expectedField) {
|
|
|
5349
5619
|
const parsed = JSON.parse(candidate);
|
|
5350
5620
|
const value = typeof parsed.value === "string" ? parsed.value.trim() : typeof parsed.text === "string" ? parsed.text.trim() : "";
|
|
5351
5621
|
const field = parsed.field;
|
|
5352
|
-
const isPlaceholder = /^\.{2,}$/.test(value);
|
|
5622
|
+
const isPlaceholder = /^\.{2,}$/.test(value) || /^<REPLACE_/.test(value) || /^your improved /i.test(value);
|
|
5353
5623
|
if (value && !isPlaceholder && (!field || field === expectedField)) {
|
|
5354
5624
|
return value;
|
|
5355
5625
|
}
|
|
@@ -5378,13 +5648,13 @@ function readProviderOutput(resultFile, fallback) {
|
|
|
5378
5648
|
return fallback;
|
|
5379
5649
|
}
|
|
5380
5650
|
async function runProviderCommand(command, provider, prompt, title, description, field, timeoutMs, images) {
|
|
5381
|
-
const tempDir =
|
|
5382
|
-
const promptFile =
|
|
5383
|
-
const issuePayloadFile =
|
|
5384
|
-
const resultFile =
|
|
5385
|
-
|
|
5651
|
+
const tempDir = mkdtempSync4(join15(tmpdir4(), "fifony-enhance-"));
|
|
5652
|
+
const promptFile = join15(tempDir, "fifony-enhance-prompt.md");
|
|
5653
|
+
const issuePayloadFile = join15(tempDir, "fifony-issue.json");
|
|
5654
|
+
const resultFile = join15(tempDir, "fifony-result.txt");
|
|
5655
|
+
writeFileSync10(promptFile, `${prompt}
|
|
5386
5656
|
`, "utf8");
|
|
5387
|
-
|
|
5657
|
+
writeFileSync10(issuePayloadFile, JSON.stringify({ title, description, field }, null, 2), "utf8");
|
|
5388
5658
|
let effectiveCommand = command;
|
|
5389
5659
|
if (provider === "codex" && images?.length) {
|
|
5390
5660
|
const imageFlags = images.map((p) => `--image "${p}"`).join(" ");
|
|
@@ -5408,7 +5678,7 @@ async function runProviderCommand(command, provider, prompt, title, description,
|
|
|
5408
5678
|
let timeout = false;
|
|
5409
5679
|
const child = spawn2(effectiveCommand, {
|
|
5410
5680
|
shell: true,
|
|
5411
|
-
cwd:
|
|
5681
|
+
cwd: TARGET_ROOT,
|
|
5412
5682
|
env: spawnEnv
|
|
5413
5683
|
});
|
|
5414
5684
|
if (child.stdin) child.stdin.end();
|
|
@@ -5424,22 +5694,25 @@ async function runProviderCommand(command, provider, prompt, title, description,
|
|
|
5424
5694
|
}, Math.max(timeoutMs, 1e3));
|
|
5425
5695
|
child.on("error", () => {
|
|
5426
5696
|
clearTimeout(timer);
|
|
5427
|
-
|
|
5697
|
+
rmSync5(tempDir, { recursive: true, force: true });
|
|
5428
5698
|
reject(new Error("Could not execute AI command."));
|
|
5429
5699
|
});
|
|
5430
5700
|
child.on("close", (code) => {
|
|
5431
5701
|
clearTimeout(timer);
|
|
5432
5702
|
if (timeout) {
|
|
5433
|
-
|
|
5703
|
+
rmSync5(tempDir, { recursive: true, force: true });
|
|
5434
5704
|
reject(new Error(`Enhance command timeout after ${Date.now() - startedAt}ms.`));
|
|
5435
5705
|
return;
|
|
5436
5706
|
}
|
|
5437
5707
|
const commandOutput = readProviderOutput(resultFile, output);
|
|
5438
|
-
|
|
5708
|
+
rmSync5(tempDir, { recursive: true, force: true });
|
|
5439
5709
|
if (code !== 0) {
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5710
|
+
if (commandOutput.trim()) {
|
|
5711
|
+
logger.warn({ exitCode: code, provider }, `[Enhance] Provider exited ${code} but produced output \u2014 attempting to use it`);
|
|
5712
|
+
resolve3(commandOutput);
|
|
5713
|
+
return;
|
|
5714
|
+
}
|
|
5715
|
+
reject(new Error(`Enhance command failed (exit ${code ?? "unknown"}) with no output.`));
|
|
5443
5716
|
return;
|
|
5444
5717
|
}
|
|
5445
5718
|
resolve3(commandOutput);
|
|
@@ -5452,7 +5725,7 @@ async function enhanceIssueField(payload, config, _workflowDefinition) {
|
|
|
5452
5725
|
const description = typeof payload.description === "string" ? payload.description.trim() : "";
|
|
5453
5726
|
const issueType = typeof payload.issueType === "string" ? payload.issueType.trim() : void 0;
|
|
5454
5727
|
const images = Array.isArray(payload.images) ? payload.images.filter((p) => typeof p === "string") : void 0;
|
|
5455
|
-
const { provider: selectedProvider, model:
|
|
5728
|
+
const { provider: selectedProvider, model: selectedModel } = await resolvePlanStageConfig(config);
|
|
5456
5729
|
const providers = detectAvailableProviders();
|
|
5457
5730
|
const isAvailable = providers.some((p) => p.name === selectedProvider && p.available);
|
|
5458
5731
|
if (!isAvailable) {
|
|
@@ -5473,7 +5746,7 @@ async function enhanceIssueField(payload, config, _workflowDefinition) {
|
|
|
5473
5746
|
additionalProperties: false
|
|
5474
5747
|
});
|
|
5475
5748
|
const command = adapter.buildCommand({
|
|
5476
|
-
model:
|
|
5749
|
+
model: selectedModel,
|
|
5477
5750
|
imagePaths: images,
|
|
5478
5751
|
jsonSchema: selectedProvider === "claude" ? ENHANCE_JSON_SCHEMA : void 0,
|
|
5479
5752
|
noToolAccess: selectedProvider === "claude"
|
|
@@ -5492,7 +5765,7 @@ async function enhanceIssueField(payload, config, _workflowDefinition) {
|
|
|
5492
5765
|
config.commandTimeoutMs,
|
|
5493
5766
|
images
|
|
5494
5767
|
);
|
|
5495
|
-
logger.info({ provider: selectedProvider, model:
|
|
5768
|
+
logger.info({ provider: selectedProvider, model: selectedModel, field, rawOutput: output.slice(0, 2e3) }, "Enhance raw output");
|
|
5496
5769
|
const value = parseEnhancerOutput(output, field);
|
|
5497
5770
|
logger.info({ provider: selectedProvider, field, parsedValue: value.slice(0, 500) }, "Enhance parsed value");
|
|
5498
5771
|
return { field, value, provider: selectedProvider };
|
|
@@ -5815,8 +6088,8 @@ function registerScanningRoutes(app, state) {
|
|
|
5815
6088
|
}
|
|
5816
6089
|
|
|
5817
6090
|
// src/agents/claude-md-manager.ts
|
|
5818
|
-
import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as
|
|
5819
|
-
import { join as
|
|
6091
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
6092
|
+
import { join as join16 } from "path";
|
|
5820
6093
|
var BLOCK_START = "<!-- FIFONY:START \u2014 managed by fifony, do not edit manually -->";
|
|
5821
6094
|
var BLOCK_END = "<!-- FIFONY:END -->";
|
|
5822
6095
|
var BLOCK_PATTERN = /<!-- FIFONY:START[^>]*-->[\s\S]*?<!-- FIFONY:END -->/;
|
|
@@ -5847,7 +6120,7 @@ function buildManagedBlock(skills, agents, commands) {
|
|
|
5847
6120
|
}
|
|
5848
6121
|
function updateClaudeMdManagedBlock(targetRoot, skills, agents, commands) {
|
|
5849
6122
|
if (skills.length === 0 && agents.length === 0 && commands.length === 0) return;
|
|
5850
|
-
const claudeMdPath =
|
|
6123
|
+
const claudeMdPath = join16(targetRoot, "CLAUDE.md");
|
|
5851
6124
|
const newBlock = buildManagedBlock(skills, agents, commands);
|
|
5852
6125
|
let existing = "";
|
|
5853
6126
|
if (existsSync11(claudeMdPath)) {
|
|
@@ -5866,7 +6139,7 @@ ${newBlock}
|
|
|
5866
6139
|
`;
|
|
5867
6140
|
}
|
|
5868
6141
|
if (updated === existing) return;
|
|
5869
|
-
|
|
6142
|
+
writeFileSync11(claudeMdPath, updated, "utf8");
|
|
5870
6143
|
}
|
|
5871
6144
|
|
|
5872
6145
|
// src/routes/catalog.ts
|
|
@@ -5995,7 +6268,7 @@ function registerReferenceRepositoryRoutes(app) {
|
|
|
5995
6268
|
}
|
|
5996
6269
|
|
|
5997
6270
|
// src/routes/misc.ts
|
|
5998
|
-
import { execSync as
|
|
6271
|
+
import { execSync as execSync6 } from "child_process";
|
|
5999
6272
|
import {
|
|
6000
6273
|
appendFileSync,
|
|
6001
6274
|
closeSync,
|
|
@@ -6006,13 +6279,13 @@ import {
|
|
|
6006
6279
|
readFileSync as readFileSync9,
|
|
6007
6280
|
readSync,
|
|
6008
6281
|
statSync,
|
|
6009
|
-
writeFileSync as
|
|
6282
|
+
writeFileSync as writeFileSync12
|
|
6010
6283
|
} from "fs";
|
|
6011
6284
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6012
|
-
import { basename as basename3, extname as extname2, join as
|
|
6285
|
+
import { basename as basename3, extname as extname2, join as join17 } from "path";
|
|
6013
6286
|
function registerMiscRoutes(app, state) {
|
|
6014
6287
|
app.get("/api/queue/stats", async (c) => {
|
|
6015
|
-
const { getQueueStats } = await import("./queue-workers-
|
|
6288
|
+
const { getQueueStats } = await import("./queue-workers-ZEZHDX7M.js");
|
|
6016
6289
|
return c.json(await getQueueStats());
|
|
6017
6290
|
});
|
|
6018
6291
|
app.post("/api/issues/:id/push", async (c) => {
|
|
@@ -6178,7 +6451,7 @@ function registerMiscRoutes(app, state) {
|
|
|
6178
6451
|
let raw = "";
|
|
6179
6452
|
if (issue.branchName && issue.baseBranch) {
|
|
6180
6453
|
try {
|
|
6181
|
-
raw =
|
|
6454
|
+
raw = execSync6(
|
|
6182
6455
|
`git diff --no-color "${issue.baseBranch}"..."${issue.branchName}"`,
|
|
6183
6456
|
{ encoding: "utf8", maxBuffer: 4 * 1024 * 1024, timeout: 15e3, cwd: TARGET_ROOT, stdio: "pipe" }
|
|
6184
6457
|
);
|
|
@@ -6190,7 +6463,7 @@ function registerMiscRoutes(app, state) {
|
|
|
6190
6463
|
return c.json({ ok: true, files: [], diff: "", message: "Source root not found." });
|
|
6191
6464
|
}
|
|
6192
6465
|
try {
|
|
6193
|
-
raw =
|
|
6466
|
+
raw = execSync6(
|
|
6194
6467
|
`git diff --no-index --no-color -- "${SOURCE_ROOT}" "${wp}"`,
|
|
6195
6468
|
{ encoding: "utf8", maxBuffer: 4 * 1024 * 1024, timeout: 15e3 }
|
|
6196
6469
|
);
|
|
@@ -6259,7 +6532,7 @@ function registerMiscRoutes(app, state) {
|
|
|
6259
6532
|
if (!branchName || !/^[a-zA-Z0-9/_.-]+$/.test(branchName)) {
|
|
6260
6533
|
return c.json({ ok: false, error: "Invalid branch name." }, 400);
|
|
6261
6534
|
}
|
|
6262
|
-
|
|
6535
|
+
execSync6(`git checkout -b "${branchName}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
6263
6536
|
state.config.defaultBranch = branchName;
|
|
6264
6537
|
await persistState(state);
|
|
6265
6538
|
return c.json({ ok: true, defaultBranch: branchName });
|
|
@@ -6275,9 +6548,9 @@ function registerMiscRoutes(app, state) {
|
|
|
6275
6548
|
}
|
|
6276
6549
|
let created = false;
|
|
6277
6550
|
try {
|
|
6278
|
-
|
|
6551
|
+
execSync6(`git checkout "${branchName}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
6279
6552
|
} catch {
|
|
6280
|
-
|
|
6553
|
+
execSync6(`git checkout -b "${branchName}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
|
|
6281
6554
|
created = true;
|
|
6282
6555
|
}
|
|
6283
6556
|
state.config.defaultBranch = branchName;
|
|
@@ -6300,7 +6573,7 @@ function registerMiscRoutes(app, state) {
|
|
|
6300
6573
|
});
|
|
6301
6574
|
app.get("/api/gitignore/status", async (c) => {
|
|
6302
6575
|
try {
|
|
6303
|
-
const gitignorePath =
|
|
6576
|
+
const gitignorePath = join17(TARGET_ROOT, ".gitignore");
|
|
6304
6577
|
if (!existsSync12(gitignorePath)) {
|
|
6305
6578
|
return c.json({ exists: false, hasFifony: false });
|
|
6306
6579
|
}
|
|
@@ -6315,9 +6588,9 @@ function registerMiscRoutes(app, state) {
|
|
|
6315
6588
|
});
|
|
6316
6589
|
app.post("/api/gitignore/add", async (c) => {
|
|
6317
6590
|
try {
|
|
6318
|
-
const gitignorePath =
|
|
6591
|
+
const gitignorePath = join17(TARGET_ROOT, ".gitignore");
|
|
6319
6592
|
if (!existsSync12(gitignorePath)) {
|
|
6320
|
-
|
|
6593
|
+
writeFileSync12(gitignorePath, "# Fifony state directory\n.fifony/\n", "utf-8");
|
|
6321
6594
|
return c.json({ ok: true, created: true });
|
|
6322
6595
|
}
|
|
6323
6596
|
const content = readFileSync9(gitignorePath, "utf-8");
|
|
@@ -6345,11 +6618,11 @@ function registerMiscRoutes(app, state) {
|
|
|
6345
6618
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
6346
6619
|
const wp = issue.workspacePath;
|
|
6347
6620
|
if (!wp) return c.json({ ok: true, files: [] });
|
|
6348
|
-
const outputsDir =
|
|
6621
|
+
const outputsDir = join17(wp, "outputs");
|
|
6349
6622
|
if (!existsSync12(outputsDir)) return c.json({ ok: true, files: [] });
|
|
6350
6623
|
const entries = readdirSync3(outputsDir).filter((f) => f.endsWith(".stdout.log")).map((f) => {
|
|
6351
6624
|
try {
|
|
6352
|
-
const s = statSync(
|
|
6625
|
+
const s = statSync(join17(outputsDir, f));
|
|
6353
6626
|
return { name: f, size: s.size };
|
|
6354
6627
|
} catch {
|
|
6355
6628
|
return { name: f, size: 0 };
|
|
@@ -6374,7 +6647,7 @@ function registerMiscRoutes(app, state) {
|
|
|
6374
6647
|
}
|
|
6375
6648
|
const wp = issue.workspacePath;
|
|
6376
6649
|
if (!wp) return c.json({ ok: false, error: "No workspace found." }, 404);
|
|
6377
|
-
const filePath =
|
|
6650
|
+
const filePath = join17(wp, "outputs", safeName);
|
|
6378
6651
|
if (!existsSync12(filePath)) return c.json({ ok: false, error: "Output file not found." }, 404);
|
|
6379
6652
|
const content = readFileSync9(filePath, "utf8");
|
|
6380
6653
|
return new Response(content, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
@@ -6389,15 +6662,15 @@ function registerMiscRoutes(app, state) {
|
|
|
6389
6662
|
return c.json({ ok: false, error: "No files provided." }, 400);
|
|
6390
6663
|
}
|
|
6391
6664
|
const uploadId = randomUUID2();
|
|
6392
|
-
const uploadDir =
|
|
6665
|
+
const uploadDir = join17(ATTACHMENTS_ROOT, "temp", uploadId);
|
|
6393
6666
|
mkdirSync7(uploadDir, { recursive: true });
|
|
6394
6667
|
const paths = [];
|
|
6395
6668
|
for (const file of payload.files) {
|
|
6396
6669
|
if (typeof file.data !== "string" || !file.name) continue;
|
|
6397
6670
|
const safeExt = extname2(file.name).replace(/[^a-z0-9.]/gi, "").slice(0, 10) || ".bin";
|
|
6398
6671
|
const safeName = `${randomUUID2()}${safeExt}`;
|
|
6399
|
-
const dest =
|
|
6400
|
-
|
|
6672
|
+
const dest = join17(uploadDir, safeName);
|
|
6673
|
+
writeFileSync12(dest, Buffer.from(file.data, "base64"));
|
|
6401
6674
|
paths.push(dest);
|
|
6402
6675
|
}
|
|
6403
6676
|
return c.json({ ok: true, paths, uploadId });
|
|
@@ -6781,6 +7054,16 @@ async function loadPersistedState() {
|
|
|
6781
7054
|
if (record2?.state && typeof record2.state === "object") {
|
|
6782
7055
|
const state = record2.state;
|
|
6783
7056
|
if (Array.isArray(state.issues) && state.issues.length > 0) {
|
|
7057
|
+
for (const issue of state.issues) {
|
|
7058
|
+
try {
|
|
7059
|
+
const current = await getCurrentPlanForIssue(issue.id);
|
|
7060
|
+
if (current) {
|
|
7061
|
+
issue.plan = current.plan;
|
|
7062
|
+
}
|
|
7063
|
+
} catch (err) {
|
|
7064
|
+
logger.warn({ issueId: issue.id, err: String(err) }, "[Store] Failed to hydrate plan on load");
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
6784
7067
|
return state;
|
|
6785
7068
|
}
|
|
6786
7069
|
logger.warn("Runtime state blob has no issues, attempting recovery from issue resource...");
|
|
@@ -7051,7 +7334,7 @@ function resolveProjectMetadata(settings, targetRoot) {
|
|
|
7051
7334
|
};
|
|
7052
7335
|
}
|
|
7053
7336
|
function scanProjectFiles(targetRoot) {
|
|
7054
|
-
const check = (rel) => existsSync14(
|
|
7337
|
+
const check = (rel) => existsSync14(join18(targetRoot, rel));
|
|
7055
7338
|
const files = {
|
|
7056
7339
|
claudeMd: check("CLAUDE.md"),
|
|
7057
7340
|
claudeDir: check(".claude"),
|
|
@@ -7072,7 +7355,7 @@ function scanProjectFiles(targetRoot) {
|
|
|
7072
7355
|
};
|
|
7073
7356
|
const existingAgents = [];
|
|
7074
7357
|
for (const agentDir of [".claude/agents", ".codex/agents"]) {
|
|
7075
|
-
const fullPath =
|
|
7358
|
+
const fullPath = join18(targetRoot, agentDir);
|
|
7076
7359
|
if (!existsSync14(fullPath)) continue;
|
|
7077
7360
|
try {
|
|
7078
7361
|
const entries = readdirSync4(fullPath);
|
|
@@ -7086,12 +7369,12 @@ function scanProjectFiles(targetRoot) {
|
|
|
7086
7369
|
}
|
|
7087
7370
|
const existingSkills = [];
|
|
7088
7371
|
for (const skillDir of [".claude/skills", ".codex/skills"]) {
|
|
7089
|
-
const fullPath =
|
|
7372
|
+
const fullPath = join18(targetRoot, skillDir);
|
|
7090
7373
|
if (!existsSync14(fullPath)) continue;
|
|
7091
7374
|
try {
|
|
7092
7375
|
const entries = readdirSync4(fullPath);
|
|
7093
7376
|
for (const entry of entries) {
|
|
7094
|
-
const skillFile =
|
|
7377
|
+
const skillFile = join18(fullPath, entry, "SKILL.md");
|
|
7095
7378
|
if (existsSync14(skillFile)) {
|
|
7096
7379
|
existingSkills.push(entry);
|
|
7097
7380
|
}
|
|
@@ -7100,7 +7383,7 @@ function scanProjectFiles(targetRoot) {
|
|
|
7100
7383
|
}
|
|
7101
7384
|
}
|
|
7102
7385
|
let readmeExcerpt = "";
|
|
7103
|
-
const readmePath =
|
|
7386
|
+
const readmePath = join18(targetRoot, "README.md");
|
|
7104
7387
|
if (existsSync14(readmePath)) {
|
|
7105
7388
|
try {
|
|
7106
7389
|
const content = readFileSync11(readmePath, "utf8");
|
|
@@ -7110,7 +7393,7 @@ function scanProjectFiles(targetRoot) {
|
|
|
7110
7393
|
}
|
|
7111
7394
|
let packageName = "";
|
|
7112
7395
|
let packageDescription = "";
|
|
7113
|
-
const pkgPath =
|
|
7396
|
+
const pkgPath = join18(targetRoot, "package.json");
|
|
7114
7397
|
if (existsSync14(pkgPath)) {
|
|
7115
7398
|
try {
|
|
7116
7399
|
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
@@ -7255,9 +7538,9 @@ function collectAgentArtifacts(agentsDir, usedNames, out) {
|
|
|
7255
7538
|
const parent = slugify(basename4(dirname2(agentsDir)));
|
|
7256
7539
|
const entries = collectDirectoryEntries(agentsDir);
|
|
7257
7540
|
for (const entry of entries) {
|
|
7258
|
-
const itemPath =
|
|
7541
|
+
const itemPath = join18(agentsDir, entry.name);
|
|
7259
7542
|
if (entry.isDirectory()) {
|
|
7260
|
-
const nestedAgentSpec =
|
|
7543
|
+
const nestedAgentSpec = join18(itemPath, "AGENT.md");
|
|
7261
7544
|
if (existsSync14(nestedAgentSpec)) {
|
|
7262
7545
|
const name2 = uniqueSuffix(`${parent}__${slugify(entry.name)}`, usedNames);
|
|
7263
7546
|
out.push({ kind: "agent", sourcePath: nestedAgentSpec, targetName: name2 });
|
|
@@ -7279,9 +7562,9 @@ function collectSkillArtifacts(skillsDir, usedNames, out) {
|
|
|
7279
7562
|
const parent = slugify(basename4(dirname2(skillsDir)));
|
|
7280
7563
|
const entries = collectDirectoryEntries(skillsDir);
|
|
7281
7564
|
for (const entry of entries) {
|
|
7282
|
-
const itemPath =
|
|
7565
|
+
const itemPath = join18(skillsDir, entry.name);
|
|
7283
7566
|
if (entry.isDirectory()) {
|
|
7284
|
-
const skillFile =
|
|
7567
|
+
const skillFile = join18(itemPath, "SKILL.md");
|
|
7285
7568
|
if (existsSync14(skillFile)) {
|
|
7286
7569
|
const name = uniqueSuffix(`${parent}__${slugify(entry.name)}`, usedNames);
|
|
7287
7570
|
out.push({ kind: "skill", sourcePath: skillFile, targetName: name });
|
|
@@ -7309,7 +7592,7 @@ function collectStandardArtifacts(repoPath) {
|
|
|
7309
7592
|
for (const entry of entries) {
|
|
7310
7593
|
if (!entry.isDirectory()) continue;
|
|
7311
7594
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
7312
|
-
const childPath =
|
|
7595
|
+
const childPath = join18(state.path, entry.name);
|
|
7313
7596
|
if (entry.name === "agents") {
|
|
7314
7597
|
collectAgentArtifacts(childPath, agentsUsed, artifacts);
|
|
7315
7598
|
}
|
|
@@ -7337,13 +7620,13 @@ function collectAgencyArtifacts(repoPath) {
|
|
|
7337
7620
|
if (SKIP_DIRS.has(entry.name) || AGENCY_AGENTS_EXCLUDED_DIRS.has(entry.name)) {
|
|
7338
7621
|
continue;
|
|
7339
7622
|
}
|
|
7340
|
-
queue.push({ path:
|
|
7623
|
+
queue.push({ path: join18(state.path, entry.name), depth: state.depth + 1 });
|
|
7341
7624
|
continue;
|
|
7342
7625
|
}
|
|
7343
|
-
if (!isMarkdownFile(entry.name, "readme.md") || !isReferenceFrontMatterFile(
|
|
7626
|
+
if (!isMarkdownFile(entry.name, "readme.md") || !isReferenceFrontMatterFile(join18(state.path, entry.name))) {
|
|
7344
7627
|
continue;
|
|
7345
7628
|
}
|
|
7346
|
-
const itemPath =
|
|
7629
|
+
const itemPath = join18(state.path, entry.name);
|
|
7347
7630
|
const targetName = uniqueSuffix(buildRelativeArtifactName(repoPath, itemPath), agentsUsed);
|
|
7348
7631
|
artifacts.push({
|
|
7349
7632
|
kind: "agent",
|
|
@@ -7357,12 +7640,12 @@ function collectAgencyArtifacts(repoPath) {
|
|
|
7357
7640
|
function collectImpeccableArtifacts(repoPath) {
|
|
7358
7641
|
const skillsUsed = /* @__PURE__ */ new Set();
|
|
7359
7642
|
const artifacts = [];
|
|
7360
|
-
const sourceSkills =
|
|
7643
|
+
const sourceSkills = join18(repoPath, "source", "skills");
|
|
7361
7644
|
if (existsSync14(sourceSkills)) {
|
|
7362
7645
|
collectSkillArtifacts(sourceSkills, skillsUsed, artifacts);
|
|
7363
7646
|
return artifacts;
|
|
7364
7647
|
}
|
|
7365
|
-
const claudeSkills =
|
|
7648
|
+
const claudeSkills = join18(repoPath, ".claude", "skills");
|
|
7366
7649
|
if (existsSync14(claudeSkills)) {
|
|
7367
7650
|
collectSkillArtifacts(claudeSkills, skillsUsed, artifacts);
|
|
7368
7651
|
}
|
|
@@ -7389,7 +7672,7 @@ function getReferenceRepositoriesRoot() {
|
|
|
7389
7672
|
}
|
|
7390
7673
|
function listReferenceRepositories() {
|
|
7391
7674
|
return DEFAULT_REFERENCE_REPOSITORIES.map((repo) => {
|
|
7392
|
-
const path =
|
|
7675
|
+
const path = join18(REPOSITORY_ROOT, repo.id);
|
|
7393
7676
|
const status = {
|
|
7394
7677
|
id: repo.id,
|
|
7395
7678
|
name: repo.name,
|
|
@@ -7401,7 +7684,7 @@ function listReferenceRepositories() {
|
|
|
7401
7684
|
if (!status.present) {
|
|
7402
7685
|
return status;
|
|
7403
7686
|
}
|
|
7404
|
-
if (!existsSync14(
|
|
7687
|
+
if (!existsSync14(join18(path, ".git"))) {
|
|
7405
7688
|
status.error = "Path exists but is not a git repo";
|
|
7406
7689
|
return status;
|
|
7407
7690
|
}
|
|
@@ -7433,7 +7716,7 @@ function syncReferenceRepositories(repositoryId) {
|
|
|
7433
7716
|
}
|
|
7434
7717
|
const results = [];
|
|
7435
7718
|
for (const repo of selected) {
|
|
7436
|
-
const target =
|
|
7719
|
+
const target = join18(root, repo.id);
|
|
7437
7720
|
const candidates = [repo.url, ...repo.fallbackUrls ?? []];
|
|
7438
7721
|
if (!existsSync14(target)) {
|
|
7439
7722
|
let cloneError;
|
|
@@ -7462,7 +7745,7 @@ function syncReferenceRepositories(repositoryId) {
|
|
|
7462
7745
|
}
|
|
7463
7746
|
continue;
|
|
7464
7747
|
}
|
|
7465
|
-
if (!existsSync14(
|
|
7748
|
+
if (!existsSync14(join18(target, ".git"))) {
|
|
7466
7749
|
results.push({
|
|
7467
7750
|
id: repo.id,
|
|
7468
7751
|
path: target,
|
|
@@ -7497,14 +7780,14 @@ function importReferenceArtifacts(repositoryId, workspaceRoot, options) {
|
|
|
7497
7780
|
if (!repository) {
|
|
7498
7781
|
throw new Error(`Unknown reference repository: ${repositoryId}`);
|
|
7499
7782
|
}
|
|
7500
|
-
const localPath =
|
|
7783
|
+
const localPath = join18(REPOSITORY_ROOT, repository.id);
|
|
7501
7784
|
if (!existsSync14(localPath)) {
|
|
7502
7785
|
throw new Error(`Repository not synced yet: ${repository.id}. Run 'fifony onboarding sync --repository ${repository.id}' first.`);
|
|
7503
7786
|
}
|
|
7504
7787
|
const basePath = resolve2(workspaceRoot);
|
|
7505
|
-
const targetBase = options.importToGlobal ?
|
|
7506
|
-
const agentsDir =
|
|
7507
|
-
const skillsDir =
|
|
7788
|
+
const targetBase = options.importToGlobal ? join18(homedir3(), ".codex") : join18(basePath, ".codex");
|
|
7789
|
+
const agentsDir = join18(targetBase, "agents");
|
|
7790
|
+
const skillsDir = join18(targetBase, "skills");
|
|
7508
7791
|
const artifacts = collectArtifacts(localPath, repository.id);
|
|
7509
7792
|
const filtered = options.kind === "all" ? artifacts : artifacts.filter((artifact) => artifact.kind === options.kind.slice(0, -1));
|
|
7510
7793
|
const summary = {
|
|
@@ -7530,7 +7813,7 @@ function importReferenceArtifacts(repositoryId, workspaceRoot, options) {
|
|
|
7530
7813
|
try {
|
|
7531
7814
|
const source = readFileSync11(artifact.sourcePath, "utf8");
|
|
7532
7815
|
if (artifact.kind === "agent") {
|
|
7533
|
-
const target =
|
|
7816
|
+
const target = join18(agentsDir, `${artifact.targetName}.md`);
|
|
7534
7817
|
if (!options.overwrite && existsSync14(target)) {
|
|
7535
7818
|
summary.skippedAgents.push(artifact.targetName);
|
|
7536
7819
|
continue;
|
|
@@ -7539,11 +7822,11 @@ function importReferenceArtifacts(repositoryId, workspaceRoot, options) {
|
|
|
7539
7822
|
summary.importedAgents.push(artifact.targetName);
|
|
7540
7823
|
continue;
|
|
7541
7824
|
}
|
|
7542
|
-
|
|
7825
|
+
writeFileSync13(target, source, "utf8");
|
|
7543
7826
|
summary.importedAgents.push(artifact.targetName);
|
|
7544
7827
|
} else {
|
|
7545
|
-
const targetDir =
|
|
7546
|
-
const target =
|
|
7828
|
+
const targetDir = join18(skillsDir, artifact.targetName);
|
|
7829
|
+
const target = join18(targetDir, "SKILL.md");
|
|
7547
7830
|
if (!options.overwrite && existsSync14(target)) {
|
|
7548
7831
|
summary.skippedSkills.push(artifact.targetName);
|
|
7549
7832
|
continue;
|
|
@@ -7553,7 +7836,7 @@ function importReferenceArtifacts(repositoryId, workspaceRoot, options) {
|
|
|
7553
7836
|
continue;
|
|
7554
7837
|
}
|
|
7555
7838
|
mkdirSync8(targetDir, { recursive: true });
|
|
7556
|
-
|
|
7839
|
+
writeFileSync13(target, source, "utf8");
|
|
7557
7840
|
summary.importedSkills.push(artifact.targetName);
|
|
7558
7841
|
}
|
|
7559
7842
|
} catch (error) {
|
|
@@ -7939,4 +8222,4 @@ export {
|
|
|
7939
8222
|
syncReferenceRepositories,
|
|
7940
8223
|
importReferenceArtifacts
|
|
7941
8224
|
};
|
|
7942
|
-
//# sourceMappingURL=chunk-
|
|
8225
|
+
//# sourceMappingURL=chunk-LYAI5RPK.js.map
|