acdev 1.0.13 → 1.0.15
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/README.md +5 -3
- package/package.json +1 -1
- package/public/app.js +273 -18
- package/public/index.html +14 -7
- package/public/styles.css +37 -0
- package/src/afterPrRules.js +19 -3
- package/src/config.js +1 -1
- package/src/github.js +439 -1
- package/src/jira.js +178 -0
- package/src/server.js +104 -7
package/src/server.js
CHANGED
|
@@ -3,13 +3,14 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { Store } from './store.js';
|
|
6
|
-
import { parseIssueUrl, createPr, fetchIssueDetails } from './github.js';
|
|
6
|
+
import { parseIssueUrl, createPr, fetchGithubPrStatus, fetchIssueDetails, listGithubIssueStatuses } from './github.js';
|
|
7
7
|
import {
|
|
8
8
|
parseJiraIssueRef,
|
|
9
9
|
resolveJiraCredentials,
|
|
10
10
|
testJiraConnection,
|
|
11
11
|
fetchJiraIssue,
|
|
12
12
|
mapJiraIssueType,
|
|
13
|
+
listJiraBoardStatuses,
|
|
13
14
|
} from './jira.js';
|
|
14
15
|
import { applyAfterPrOpenedRules } from './afterPrRules.js';
|
|
15
16
|
import {
|
|
@@ -43,7 +44,7 @@ import { upsertEnvVars } from './env.js';
|
|
|
43
44
|
import { listModels } from './models.js';
|
|
44
45
|
import { splitIssueUrls } from './urls.js';
|
|
45
46
|
import { usageFromLogs, withJobUsage, snapshotJobLlm, tagUsageProvider } from './usage.js';
|
|
46
|
-
import { checkGhAuth } from './gh-auth.js';
|
|
47
|
+
import { checkGhAuth, originRemoteInfo } from './gh-auth.js';
|
|
47
48
|
import { checkClaudeAuth } from './claude-auth.js';
|
|
48
49
|
import { checkOpenRouterAuth } from './openrouter-auth.js';
|
|
49
50
|
import { isValidModelId, isNoModel, NO_MODEL, isModelIdForProvider } from './models.js';
|
|
@@ -171,6 +172,8 @@ export function normalizeReviewComments(body) {
|
|
|
171
172
|
* deps?: {
|
|
172
173
|
* pushBranch?: typeof pushBranch,
|
|
173
174
|
* createPr?: typeof createPr,
|
|
175
|
+
* fetchGithubPrStatus?: typeof fetchGithubPrStatus,
|
|
176
|
+
* removeWorktree?: typeof removeWorktree,
|
|
174
177
|
* getDiff?: typeof getDiff,
|
|
175
178
|
* listChangedFiles?: typeof listChangedFiles,
|
|
176
179
|
* applyFileExclusions?: typeof applyFileExclusions,
|
|
@@ -182,6 +185,8 @@ export function normalizeReviewComments(body) {
|
|
|
182
185
|
* resolveJiraCredentials?: Function,
|
|
183
186
|
* checkGhAuth?: typeof checkGhAuth,
|
|
184
187
|
* checkClaudeAuth?: typeof checkClaudeAuth,
|
|
188
|
+
* listJiraBoardStatuses?: typeof listJiraBoardStatuses,
|
|
189
|
+
* listGithubIssueStatuses?: typeof listGithubIssueStatuses,
|
|
185
190
|
* },
|
|
186
191
|
* }} options
|
|
187
192
|
*/
|
|
@@ -189,14 +194,36 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
189
194
|
const app = express();
|
|
190
195
|
const publicDir = path.join(__dirname, '..', 'public');
|
|
191
196
|
app.use(express.json());
|
|
192
|
-
|
|
193
197
|
const doPushBranch = deps.pushBranch || pushBranch;
|
|
194
198
|
const doCreatePr = deps.createPr || createPr;
|
|
199
|
+
const doRemoveWorktree = deps.removeWorktree || removeWorktree;
|
|
195
200
|
const doGetDiff = deps.getDiff || getDiff;
|
|
196
201
|
const doListChangedFiles = deps.listChangedFiles || listChangedFiles;
|
|
197
202
|
const doApplyFileExclusions = deps.applyFileExclusions || applyFileExclusions;
|
|
203
|
+
const doFetchGithubPrStatus = deps.fetchGithubPrStatus || fetchGithubPrStatus;
|
|
198
204
|
const doCheckGhAuth = deps.checkGhAuth || checkGhAuth;
|
|
199
205
|
const doCheckClaudeAuth = deps.checkClaudeAuth || checkClaudeAuth;
|
|
206
|
+
const doListJiraBoardStatuses = deps.listJiraBoardStatuses || listJiraBoardStatuses;
|
|
207
|
+
const doListGithubIssueStatuses = deps.listGithubIssueStatuses || listGithubIssueStatuses;
|
|
208
|
+
|
|
209
|
+
/** @type {Map<string, { at: number, value: object }>} */
|
|
210
|
+
const prStatusCache = new Map();
|
|
211
|
+
|
|
212
|
+
async function getPrStatusForJob(job) {
|
|
213
|
+
const prUrl = String(job?.prUrl || '').trim();
|
|
214
|
+
if (!prUrl) return null;
|
|
215
|
+
|
|
216
|
+
const cached = prStatusCache.get(prUrl);
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
if (cached && now - cached.at < 15_000) {
|
|
219
|
+
return cached.value;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = await doFetchGithubPrStatus({ prUrl, cwd: repoRoot });
|
|
223
|
+
const value = result.ok ? { ok: true, ...result } : result;
|
|
224
|
+
prStatusCache.set(prUrl, { at: now, value });
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
200
227
|
|
|
201
228
|
function formatAgentJobError(err) {
|
|
202
229
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -608,10 +635,16 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
608
635
|
}
|
|
609
636
|
}
|
|
610
637
|
|
|
611
|
-
app.get('/api/jobs', (_req, res) => {
|
|
638
|
+
app.get('/api/jobs', async (_req, res) => {
|
|
612
639
|
try {
|
|
613
|
-
|
|
614
|
-
|
|
640
|
+
const jobs = await Promise.all(
|
|
641
|
+
store.getJobs().map(async (job) => {
|
|
642
|
+
const withUsage = withJobUsage(job);
|
|
643
|
+
const prStatus = await getPrStatusForJob(withUsage);
|
|
644
|
+
return prStatus ? { ...withUsage, prStatus } : withUsage;
|
|
645
|
+
})
|
|
646
|
+
);
|
|
647
|
+
res.json(jobs);
|
|
615
648
|
} catch (err) {
|
|
616
649
|
res.status(500).json({ error: err.message });
|
|
617
650
|
}
|
|
@@ -731,6 +764,54 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
731
764
|
}
|
|
732
765
|
});
|
|
733
766
|
|
|
767
|
+
app.get('/api/jira/statuses', async (_req, res) => {
|
|
768
|
+
try {
|
|
769
|
+
const creds = resolveJiraCredentials({ configBaseUrl: config.jiraBaseUrl });
|
|
770
|
+
if ('error' in creds) {
|
|
771
|
+
return res.status(400).json({ ok: false, error: creds.error, statuses: [] });
|
|
772
|
+
}
|
|
773
|
+
const result = await doListJiraBoardStatuses(creds);
|
|
774
|
+
if (!result.ok) {
|
|
775
|
+
return res.status(400).json(result);
|
|
776
|
+
}
|
|
777
|
+
res.json(result);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
res.status(500).json({
|
|
780
|
+
ok: false,
|
|
781
|
+
error: err instanceof Error ? err.message : String(err),
|
|
782
|
+
statuses: [],
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
app.get('/api/github/statuses', async (_req, res) => {
|
|
788
|
+
try {
|
|
789
|
+
const gh = doCheckGhAuth();
|
|
790
|
+
if (!gh.ok) {
|
|
791
|
+
const error =
|
|
792
|
+
gh.reason === 'not-found'
|
|
793
|
+
? 'GitHub CLI (gh) is not installed.'
|
|
794
|
+
: 'GitHub is not authenticated. Configure a PAT in Settings → Authentication, or run gh auth login.';
|
|
795
|
+
return res.status(400).json({ ok: false, error, statuses: [] });
|
|
796
|
+
}
|
|
797
|
+
const origin = originRemoteInfo(repoRoot);
|
|
798
|
+
const result = await doListGithubIssueStatuses({
|
|
799
|
+
cwd: repoRoot,
|
|
800
|
+
originUrl: origin.url,
|
|
801
|
+
});
|
|
802
|
+
if (!result.ok) {
|
|
803
|
+
return res.status(400).json(result);
|
|
804
|
+
}
|
|
805
|
+
res.json(result);
|
|
806
|
+
} catch (err) {
|
|
807
|
+
res.status(500).json({
|
|
808
|
+
ok: false,
|
|
809
|
+
error: err instanceof Error ? err.message : String(err),
|
|
810
|
+
statuses: [],
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
|
|
734
815
|
app.post('/api/issues', (req, res) => {
|
|
735
816
|
try {
|
|
736
817
|
const gate = authGate({ needGh: true, needLlm: true });
|
|
@@ -1076,7 +1157,23 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
1076
1157
|
);
|
|
1077
1158
|
}
|
|
1078
1159
|
|
|
1079
|
-
|
|
1160
|
+
const refreshed = store.getJob(updated.id) || updated;
|
|
1161
|
+
const wtId = worktreeIdForJob(refreshed);
|
|
1162
|
+
if (wtId != null && refreshed.worktreePath) {
|
|
1163
|
+
try {
|
|
1164
|
+
await doRemoveWorktree(repoRoot, wtId, refreshed.branchName);
|
|
1165
|
+
} catch (cleanupErr) {
|
|
1166
|
+
console.warn(
|
|
1167
|
+
`[acdev] removeWorktree after PR open failed for job ${refreshed.id}:`,
|
|
1168
|
+
cleanupErr instanceof Error ? cleanupErr.message : cleanupErr
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
store.updateJob(refreshed.id, { worktreePath: undefined });
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const finalJob = withJobUsage(store.getJob(updated.id) || updated);
|
|
1175
|
+
const finalPrStatus = await getPrStatusForJob(finalJob);
|
|
1176
|
+
res.json(finalPrStatus ? { ...finalJob, prStatus: finalPrStatus } : finalJob);
|
|
1080
1177
|
} catch (err) {
|
|
1081
1178
|
const message = err instanceof Error ? err.message : String(err);
|
|
1082
1179
|
store.updateJob(job.id, { status: 'failed', error: message });
|