acdev 1.0.9 → 1.0.10
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/package.json +1 -1
- package/public/app.js +26 -3
- package/public/index.html +18 -0
- package/public/styles.css +32 -0
- package/src/git.js +62 -0
- package/src/server.js +61 -2
- package/src/store.js +2 -0
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -228,6 +228,7 @@ const els = {
|
|
|
228
228
|
enqueueJiraSettingsLink: document.getElementById('enqueue-jira-settings-link'),
|
|
229
229
|
overviewTicketSource: document.getElementById('overview-ticket-source'),
|
|
230
230
|
enqueueFeedback: document.getElementById('enqueue-feedback'),
|
|
231
|
+
preferredBranch: document.getElementById('preferred-branch'),
|
|
231
232
|
addBtn: document.getElementById('add-btn'),
|
|
232
233
|
statsGrid: document.getElementById('stats-grid'),
|
|
233
234
|
overviewRunning: document.getElementById('overview-running'),
|
|
@@ -900,16 +901,22 @@ function jobTitle(job) {
|
|
|
900
901
|
return job.issueTitle || truncate(job.issueUrl, 48);
|
|
901
902
|
}
|
|
902
903
|
|
|
904
|
+
function jobDisplayBranch(job) {
|
|
905
|
+
return job.branchName || job.preferredBranchName || '';
|
|
906
|
+
}
|
|
907
|
+
|
|
903
908
|
function jobRepoLine(job) {
|
|
904
909
|
if (isJiraJob(job)) {
|
|
905
910
|
const key = parseJiraKeyFromJob(job) || '—';
|
|
906
|
-
const
|
|
911
|
+
const name = jobDisplayBranch(job);
|
|
912
|
+
const branch = name ? ` · ${name}` : '';
|
|
907
913
|
return { repo: 'Jira', number: key, branch, text: `Jira ${key}${branch}`, isJira: true };
|
|
908
914
|
}
|
|
909
915
|
const ref = parseIssueRef(job.issueUrl);
|
|
910
916
|
const repo = ref?.full || '—';
|
|
911
917
|
const num = ref?.number || job.issueNumber || '—';
|
|
912
|
-
const
|
|
918
|
+
const name = jobDisplayBranch(job);
|
|
919
|
+
const branch = name ? ` · ${name}` : '';
|
|
913
920
|
return { repo, number: num, branch, text: `${repo} #${num}${branch}`, isJira: false };
|
|
914
921
|
}
|
|
915
922
|
|
|
@@ -2696,6 +2703,7 @@ function jobMatchesReviewSearch(job, q) {
|
|
|
2696
2703
|
job.issueTitle,
|
|
2697
2704
|
job.issueUrl,
|
|
2698
2705
|
job.branchName,
|
|
2706
|
+
job.preferredBranchName,
|
|
2699
2707
|
job.prTitle,
|
|
2700
2708
|
job.prUrl,
|
|
2701
2709
|
ref?.number,
|
|
@@ -4445,18 +4453,32 @@ els.issueUrls?.addEventListener('keydown', (ev) => {
|
|
|
4445
4453
|
els.addBtn?.click();
|
|
4446
4454
|
});
|
|
4447
4455
|
|
|
4456
|
+
els.preferredBranch?.addEventListener('keydown', (ev) => {
|
|
4457
|
+
if (ev.key !== 'Enter' || ev.shiftKey || ev.metaKey || ev.ctrlKey || ev.altKey) return;
|
|
4458
|
+
ev.preventDefault();
|
|
4459
|
+
els.addBtn?.click();
|
|
4460
|
+
});
|
|
4461
|
+
|
|
4448
4462
|
els.addBtn.addEventListener('click', async () => {
|
|
4449
4463
|
els.enqueueFeedback.classList.add('hidden');
|
|
4450
4464
|
const text = els.issueUrls.value.trim();
|
|
4451
4465
|
if (!text) return;
|
|
4452
4466
|
|
|
4453
4467
|
const urls = splitIssueUrls(text);
|
|
4468
|
+
const branchName = els.preferredBranch?.value?.trim() || '';
|
|
4469
|
+
// #region agent log
|
|
4470
|
+
fetch('http://127.0.0.1:7258/ingest/377aa5e2-15ea-4447-a68b-7ce215882bc3',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'473a78'},body:JSON.stringify({sessionId:'473a78',runId:'pre-fix',hypothesisId:'B',location:'public/app.js:addBtn',message:'enqueue submit',data:{hasCustom:Boolean(branchName),branchName:branchName||null,urlCount:urls.length},timestamp:Date.now()})}).catch(()=>{});
|
|
4471
|
+
// #endregion
|
|
4454
4472
|
|
|
4455
4473
|
try {
|
|
4456
4474
|
const res = await fetch('/api/issues', {
|
|
4457
4475
|
method: 'POST',
|
|
4458
4476
|
headers: { 'Content-Type': 'application/json' },
|
|
4459
|
-
body: JSON.stringify({
|
|
4477
|
+
body: JSON.stringify({
|
|
4478
|
+
urls,
|
|
4479
|
+
ticketSource,
|
|
4480
|
+
...(branchName ? { branchName } : {}),
|
|
4481
|
+
}),
|
|
4460
4482
|
});
|
|
4461
4483
|
const data = await res.json();
|
|
4462
4484
|
if (!res.ok) {
|
|
@@ -4467,6 +4489,7 @@ els.addBtn.addEventListener('click', async () => {
|
|
|
4467
4489
|
return;
|
|
4468
4490
|
}
|
|
4469
4491
|
els.issueUrls.value = '';
|
|
4492
|
+
if (els.preferredBranch) els.preferredBranch.value = '';
|
|
4470
4493
|
const parts = [];
|
|
4471
4494
|
if (data.jobs?.length) parts.push(`Added ${data.jobs.length}`);
|
|
4472
4495
|
if (data.skipped?.length) {
|
package/public/index.html
CHANGED
|
@@ -138,6 +138,24 @@
|
|
|
138
138
|
or a browse link. Press <kbd class="enqueue-kbd">Enter</kbd> to add.
|
|
139
139
|
</p>
|
|
140
140
|
|
|
141
|
+
<div class="enqueue-branch-field">
|
|
142
|
+
<div class="enqueue-branch-label-row">
|
|
143
|
+
<label class="field-label" for="preferred-branch">Branch name</label>
|
|
144
|
+
<span class="enqueue-branch-optional">Optional</span>
|
|
145
|
+
</div>
|
|
146
|
+
<input
|
|
147
|
+
id="preferred-branch"
|
|
148
|
+
class="input enqueue-branch-input"
|
|
149
|
+
type="text"
|
|
150
|
+
spellcheck="false"
|
|
151
|
+
autocomplete="off"
|
|
152
|
+
placeholder="your-branch-name"
|
|
153
|
+
>
|
|
154
|
+
<p class="enqueue-input-hint">
|
|
155
|
+
Leave empty to keep feat/fix + ticket title. If set, that name is used instead.
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
141
159
|
<div class="enqueue-info-banner" id="enqueue-info-banner" role="note">
|
|
142
160
|
<svg class="enqueue-info-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
143
161
|
<circle cx="12" cy="12" r="10"/>
|
package/public/styles.css
CHANGED
|
@@ -459,6 +459,38 @@ a { color: var(--primary); text-underline-offset: 3px; }
|
|
|
459
459
|
color: var(--text-muted);
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
.enqueue-branch-field {
|
|
463
|
+
display: flex;
|
|
464
|
+
flex-direction: column;
|
|
465
|
+
gap: 6px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.enqueue-branch-field .field-label {
|
|
469
|
+
margin-bottom: 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.enqueue-branch-field .enqueue-input-hint {
|
|
473
|
+
margin: 0;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.enqueue-branch-label-row {
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: baseline;
|
|
479
|
+
justify-content: space-between;
|
|
480
|
+
gap: 8px;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.enqueue-branch-optional {
|
|
484
|
+
font-size: 11px;
|
|
485
|
+
font-weight: 500;
|
|
486
|
+
color: var(--text-muted);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.enqueue-branch-input {
|
|
490
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
491
|
+
font-size: 13px;
|
|
492
|
+
}
|
|
493
|
+
|
|
462
494
|
.enqueue-kbd {
|
|
463
495
|
display: inline-block;
|
|
464
496
|
padding: 1px 6px;
|
package/src/git.js
CHANGED
|
@@ -103,6 +103,68 @@ export function buildBranchName(type, title) {
|
|
|
103
103
|
return `${type}/${slugifyTitle(title)}`;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
const PREFERRED_BRANCH_MAX = 100;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Sanitize a user-supplied git branch name. Preserves case (e.g. NC-2133).
|
|
110
|
+
* Empty / whitespace-only input returns ''.
|
|
111
|
+
* @param {unknown} raw
|
|
112
|
+
*/
|
|
113
|
+
export function normalizePreferredBranchName(raw) {
|
|
114
|
+
if (raw == null) return '';
|
|
115
|
+
let name = String(raw).trim().replace(/\s+/g, '-');
|
|
116
|
+
if (!name) return '';
|
|
117
|
+
|
|
118
|
+
name = name
|
|
119
|
+
.replace(/[~^:?*\[\\]+/g, '-')
|
|
120
|
+
.replace(/@{/g, '-at-')
|
|
121
|
+
.replace(/[^A-Za-z0-9._/-]+/g, '-')
|
|
122
|
+
.replace(/\/+/g, '/')
|
|
123
|
+
.replace(/\.{2,}/g, '.')
|
|
124
|
+
.replace(/-{2,}/g, '-')
|
|
125
|
+
.replace(/^[-./]+|[-./]+$/g, '');
|
|
126
|
+
|
|
127
|
+
if (/\.lock$/i.test(name)) {
|
|
128
|
+
name = name.replace(/\.lock$/i, '').replace(/[-./]+$/g, '');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
name = name.slice(0, PREFERRED_BRANCH_MAX).replace(/[-./]+$/g, '');
|
|
132
|
+
if (!name || name === '@' || /^HEAD$/i.test(name)) return '';
|
|
133
|
+
return name;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parse optional branchName from an API body.
|
|
138
|
+
* Omitted / blank → no preference (caller uses feat/fix slug).
|
|
139
|
+
* Provided but invalid after sanitize → error.
|
|
140
|
+
* @param {unknown} raw
|
|
141
|
+
* @returns {{ ok: true, value?: string } | { ok: false, error: string }}
|
|
142
|
+
*/
|
|
143
|
+
export function parsePreferredBranchName(raw) {
|
|
144
|
+
if (raw == null) return { ok: true };
|
|
145
|
+
if (typeof raw !== 'string') {
|
|
146
|
+
return { ok: false, error: 'branchName must be a string' };
|
|
147
|
+
}
|
|
148
|
+
if (!raw.trim()) return { ok: true };
|
|
149
|
+
const value = normalizePreferredBranchName(raw);
|
|
150
|
+
if (!value) {
|
|
151
|
+
return { ok: false, error: 'branchName is not a valid git branch name' };
|
|
152
|
+
}
|
|
153
|
+
return { ok: true, value };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Prefer a user-supplied branch name; otherwise feat/fix slug from the ticket.
|
|
158
|
+
* @param {string | undefined | null} preferred
|
|
159
|
+
* @param {'feat' | 'fix'} issueType
|
|
160
|
+
* @param {string} issueTitle
|
|
161
|
+
*/
|
|
162
|
+
export function resolveDesiredBranchName(preferred, issueType, issueTitle) {
|
|
163
|
+
const custom = normalizePreferredBranchName(preferred);
|
|
164
|
+
if (custom) return custom;
|
|
165
|
+
return buildBranchName(issueType, issueTitle);
|
|
166
|
+
}
|
|
167
|
+
|
|
106
168
|
/**
|
|
107
169
|
* @param {string} repoRoot
|
|
108
170
|
* @param {string | number} worktreeId GitHub issue number or Jira key
|
package/src/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { Store } from './store.js';
|
|
@@ -18,7 +19,8 @@ import {
|
|
|
18
19
|
getDiff,
|
|
19
20
|
pushBranch,
|
|
20
21
|
detectIssueType,
|
|
21
|
-
|
|
22
|
+
parsePreferredBranchName,
|
|
23
|
+
resolveDesiredBranchName,
|
|
22
24
|
sanitizeBranchCommits,
|
|
23
25
|
listChangedFiles,
|
|
24
26
|
applyFileExclusions,
|
|
@@ -341,7 +343,34 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
341
343
|
issueType = detectIssueType(issueDetails);
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
const desiredBranchName =
|
|
346
|
+
const desiredBranchName = resolveDesiredBranchName(
|
|
347
|
+
job.preferredBranchName,
|
|
348
|
+
issueType,
|
|
349
|
+
issueTitle
|
|
350
|
+
);
|
|
351
|
+
// #region agent log
|
|
352
|
+
try {
|
|
353
|
+
fs.appendFileSync(
|
|
354
|
+
'/Users/giancarlogarcia/Documents/Personal/Projects-2026/agent-mcp/.cursor/debug-473a78.log',
|
|
355
|
+
`${JSON.stringify({
|
|
356
|
+
sessionId: '473a78',
|
|
357
|
+
runId: 'pre-fix',
|
|
358
|
+
hypothesisId: 'C',
|
|
359
|
+
location: 'src/server.js:resolveDesiredBranchName',
|
|
360
|
+
message: 'resolved worktree branch name',
|
|
361
|
+
data: {
|
|
362
|
+
preferred: job.preferredBranchName ?? null,
|
|
363
|
+
issueType,
|
|
364
|
+
usedCustom: Boolean(job.preferredBranchName),
|
|
365
|
+
desiredBranchName,
|
|
366
|
+
},
|
|
367
|
+
timestamp: Date.now(),
|
|
368
|
+
})}\n`
|
|
369
|
+
);
|
|
370
|
+
} catch {
|
|
371
|
+
// ignore debug log failures
|
|
372
|
+
}
|
|
373
|
+
// #endregion
|
|
345
374
|
const worktreeId = worktreeIdForJob(job);
|
|
346
375
|
if (worktreeId == null) {
|
|
347
376
|
throw new Error('Job is missing issueNumber / jiraKey for worktree path');
|
|
@@ -657,6 +686,35 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
657
686
|
});
|
|
658
687
|
}
|
|
659
688
|
|
|
689
|
+
const preferredParsed = parsePreferredBranchName(req.body?.branchName);
|
|
690
|
+
if (!preferredParsed.ok) {
|
|
691
|
+
return res.status(400).json({ error: preferredParsed.error });
|
|
692
|
+
}
|
|
693
|
+
const preferredBranchName = preferredParsed.value;
|
|
694
|
+
// #region agent log
|
|
695
|
+
try {
|
|
696
|
+
fs.appendFileSync(
|
|
697
|
+
'/Users/giancarlogarcia/Documents/Personal/Projects-2026/agent-mcp/.cursor/debug-473a78.log',
|
|
698
|
+
`${JSON.stringify({
|
|
699
|
+
sessionId: '473a78',
|
|
700
|
+
runId: 'pre-fix',
|
|
701
|
+
hypothesisId: 'A',
|
|
702
|
+
location: 'src/server.js:POST /api/issues',
|
|
703
|
+
message: 'enqueue preferred branch parse',
|
|
704
|
+
data: {
|
|
705
|
+
rawProvided: req.body?.branchName != null,
|
|
706
|
+
rawType: typeof req.body?.branchName,
|
|
707
|
+
preferredBranchName: preferredBranchName ?? null,
|
|
708
|
+
urlCount: urls.length,
|
|
709
|
+
},
|
|
710
|
+
timestamp: Date.now(),
|
|
711
|
+
})}\n`
|
|
712
|
+
);
|
|
713
|
+
} catch {
|
|
714
|
+
// ignore debug log failures
|
|
715
|
+
}
|
|
716
|
+
// #endregion
|
|
717
|
+
|
|
660
718
|
const ticketSource =
|
|
661
719
|
req.body?.ticketSource === 'jira' || req.body?.ticketSource === 'github'
|
|
662
720
|
? req.body.ticketSource
|
|
@@ -751,6 +809,7 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
751
809
|
issueNumber: item.number,
|
|
752
810
|
ticketSource: item.ticketSource,
|
|
753
811
|
jiraKey: item.jiraKey,
|
|
812
|
+
...(preferredBranchName ? { preferredBranchName } : {}),
|
|
754
813
|
});
|
|
755
814
|
created.push(job);
|
|
756
815
|
}
|
package/src/store.js
CHANGED
|
@@ -55,6 +55,7 @@ export class Store {
|
|
|
55
55
|
* issueNumber?: number,
|
|
56
56
|
* ticketSource?: 'github' | 'jira',
|
|
57
57
|
* jiraKey?: string,
|
|
58
|
+
* preferredBranchName?: string,
|
|
58
59
|
* }} data
|
|
59
60
|
* @returns {Job}
|
|
60
61
|
*/
|
|
@@ -68,6 +69,7 @@ export class Store {
|
|
|
68
69
|
issueNumber: data.issueNumber,
|
|
69
70
|
ticketSource,
|
|
70
71
|
...(data.jiraKey ? { jiraKey: data.jiraKey } : {}),
|
|
72
|
+
...(data.preferredBranchName ? { preferredBranchName: data.preferredBranchName } : {}),
|
|
71
73
|
status: 'queued',
|
|
72
74
|
createdAt: now,
|
|
73
75
|
updatedAt: now,
|