specrails-desktop 2.24.2 → 2.24.3
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/client/dist/assets/{ActivityFeedPage-D0HbUDeS.js → ActivityFeedPage-CxnjeMnO.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-Brth-3zu.js → AgentBrowserCapture-c-Cr7q94.js} +1 -1
- package/client/dist/assets/{AgentModeCodePane-UuKgXzPX.js → AgentModeCodePane-CoZZ2_sJ.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-_P5Xm6ij.js → AgentModeJobsPane-Dua5r_jk.js} +1 -1
- package/client/dist/assets/{AgentsPage-BHpan6kZ.js → AgentsPage-L-pmBTbc.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-kryIHuZy.js → AnalyticsPage-B82qtTYV.js} +1 -1
- package/client/dist/assets/{BarChart-C5W41oN4.js → BarChart-JoGhQq6b.js} +1 -1
- package/client/dist/assets/{CodePage-B0WhKBeS.js → CodePage-CIBbOHTk.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-u9a30yar.js → DesktopAnalyticsPage-D94CWcV4.js} +1 -1
- package/client/dist/assets/{DocsDialog-GSzYhxfg.js → DocsDialog-DIubUwd_.js} +1 -1
- package/client/dist/assets/{DocsPage-Bm0J873n.js → DocsPage-A9PpvdW3.js} +1 -1
- package/client/dist/assets/{ExportDropdown-CXc3MBkq.js → ExportDropdown-CWelhih_.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-Z5Qy3n6G.js → IntegrationsPage-DtcigyHI.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-BbBSAQm1.js → InteractiveJobComposer-CtkXJ1gD.js} +1 -1
- package/client/dist/assets/{JobDetailModal-CdPgkHdb.js → JobDetailModal-C-AewB0N.js} +1 -1
- package/client/dist/assets/{JobDetailPage-CprNUYlt.js → JobDetailPage-C2nAjNRu.js} +1 -1
- package/client/dist/assets/{JobsPage-ClIaG5ow.js → JobsPage-CtDu1BqI.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-rWwSzVr_.js → LoopBuilderPage-CLikTQXx.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-B0VUdegW.js → LoopPreviewModal-SEb8gedX.js} +1 -1
- package/client/dist/assets/{LoopsPage-Brrl03uv.js → LoopsPage-Pf_0o0Yf.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-i1aAs49_.js → ProjectSettingsDialog-C6_NZLxv.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-C92-Ygm2.js → TemplatePreviewModal-CcTeX1GA.js} +1 -1
- package/client/dist/assets/agent-CXGbWkpN2.js +1 -0
- package/client/dist/assets/{agent-CbHTqhBt.js → agent-Cjiy_5TS.js} +1 -1
- package/client/dist/assets/agent-DBo9_-zx.js +1 -0
- package/client/dist/assets/agent-DUmd88ng.js +1 -0
- package/client/dist/assets/{agent-i5VEzNfj2.js → agent-N5jFquC82.js} +1 -1
- package/client/dist/assets/{agent-Bq74VvOq.js → agent-ay1iYN3L.js} +1 -1
- package/client/dist/assets/agent-dMyoDKGk.js +1 -0
- package/client/dist/assets/agent-opeyUUUG2.js +1 -0
- package/client/dist/assets/{brain-CJ92kxwM.js → brain-BVlxI-OH.js} +1 -1
- package/client/dist/assets/{dist-js-CAU8gTUN.js → dist-js-DOGUoTVT.js} +1 -1
- package/client/dist/assets/{dist-js-BVonQLiD.js → dist-js-DtG-ECSR.js} +1 -1
- package/client/dist/assets/{index-CfHBmcjb.js → index-B0fq5d1Z.js} +21 -21
- package/client/dist/assets/index-DZ2Beygz.css +2 -0
- package/client/dist/assets/{lib-DJy6HBRz.js → lib-B-CmfZ34.js} +1 -1
- package/client/dist/assets/{upload-D_1UxNS_.js → upload-B22mO1uT.js} +1 -1
- package/client/dist/assets/{value-BH0Vlt96.js → value-DltGn-Ab.js} +1 -1
- package/client/dist/index.html +3 -3
- package/package.json +1 -1
- package/server/dist/active-pr-continuation.js +84 -13
- package/server/dist/jira/jira-adf.js +317 -36
- package/server/dist/loop-factory.js +1 -1
- package/server/dist/loop-run-manager.js +49 -6
- package/server/dist/project-router-git.js +26 -0
- package/server/dist/rail-isolated-launch.js +9 -0
- package/server/dist/rail-pr-store.js +1 -0
- package/server/dist/worktree-manager.js +24 -1
- package/client/dist/assets/agent-2gmrdgKk.js +0 -1
- package/client/dist/assets/agent-BrhvIxZF.js +0 -1
- package/client/dist/assets/agent-Ch_OIjXA2.js +0 -1
- package/client/dist/assets/agent-E-X9b1uz2.js +0 -1
- package/client/dist/assets/agent-zI95bSiF.js +0 -1
- package/client/dist/assets/index-B9483NYu.css +0 -2
|
@@ -16,15 +16,195 @@ exports.railReviewCommentMarker = railReviewCommentMarker;
|
|
|
16
16
|
exports.bodyContainsMarker = bodyContainsMarker;
|
|
17
17
|
exports.commentHasMarker = commentHasMarker;
|
|
18
18
|
exports.adfToText = adfToText;
|
|
19
|
-
|
|
19
|
+
function textNode(text, marks) {
|
|
20
|
+
if (!text)
|
|
21
|
+
return [];
|
|
22
|
+
return marks && Array.isArray(marks) && marks.length > 0
|
|
23
|
+
? [{ type: 'text', text, marks }]
|
|
24
|
+
: [{ type: 'text', text }];
|
|
25
|
+
}
|
|
26
|
+
function parseInline(input) {
|
|
27
|
+
const out = [];
|
|
28
|
+
let i = 0;
|
|
29
|
+
const pushPlainUntil = (next) => {
|
|
30
|
+
if (next > i)
|
|
31
|
+
out.push(...textNode(input.slice(i, next)));
|
|
32
|
+
i = next;
|
|
33
|
+
};
|
|
34
|
+
while (i < input.length) {
|
|
35
|
+
const starts = [
|
|
36
|
+
input.indexOf('`', i),
|
|
37
|
+
input.indexOf('**', i),
|
|
38
|
+
input.indexOf('*', i),
|
|
39
|
+
input.indexOf('[', i),
|
|
40
|
+
].filter((n) => n >= 0);
|
|
41
|
+
const next = starts.length > 0 ? Math.min(...starts) : -1;
|
|
42
|
+
if (next < 0) {
|
|
43
|
+
out.push(...textNode(input.slice(i)));
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
pushPlainUntil(next);
|
|
47
|
+
if (input.startsWith('`', i)) {
|
|
48
|
+
const end = input.indexOf('`', i + 1);
|
|
49
|
+
if (end > i + 1) {
|
|
50
|
+
out.push(...textNode(input.slice(i + 1, end), [{ type: 'code' }]));
|
|
51
|
+
i = end + 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (input.startsWith('**', i)) {
|
|
56
|
+
const end = input.indexOf('**', i + 2);
|
|
57
|
+
if (end > i + 2) {
|
|
58
|
+
out.push(...textNode(input.slice(i + 2, end), [{ type: 'strong' }]));
|
|
59
|
+
i = end + 2;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (input.startsWith('[', i)) {
|
|
64
|
+
const close = input.indexOf('](', i + 1);
|
|
65
|
+
const end = close >= 0 ? input.indexOf(')', close + 2) : -1;
|
|
66
|
+
if (close > i + 1 && end > close + 2) {
|
|
67
|
+
const label = input.slice(i + 1, close);
|
|
68
|
+
const href = input.slice(close + 2, end);
|
|
69
|
+
out.push(...textNode(label, [{ type: 'link', attrs: { href } }]));
|
|
70
|
+
i = end + 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (input.startsWith('*', i) && !input.startsWith('**', i)) {
|
|
75
|
+
const end = input.indexOf('*', i + 1);
|
|
76
|
+
if (end > i + 1) {
|
|
77
|
+
out.push(...textNode(input.slice(i + 1, end), [{ type: 'em' }]));
|
|
78
|
+
i = end + 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
out.push(...textNode(input[i]));
|
|
83
|
+
i += 1;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function paragraphFromLines(lines) {
|
|
88
|
+
if (lines.length === 0 || lines.every((line) => line.length === 0))
|
|
89
|
+
return { type: 'paragraph' };
|
|
90
|
+
const content = [];
|
|
91
|
+
lines.forEach((line, index) => {
|
|
92
|
+
if (index > 0)
|
|
93
|
+
content.push({ type: 'hardBreak' });
|
|
94
|
+
content.push(...parseInline(line));
|
|
95
|
+
});
|
|
96
|
+
return content.length > 0 ? { type: 'paragraph', content } : { type: 'paragraph' };
|
|
97
|
+
}
|
|
98
|
+
function isSpecialMarkdownLine(line) {
|
|
99
|
+
return /^```/.test(line) ||
|
|
100
|
+
/^(#{1,6})\s+\S/.test(line) ||
|
|
101
|
+
/^\s*[-*+]\s+\S/.test(line) ||
|
|
102
|
+
/^\s*\d+[.)]\s+\S/.test(line) ||
|
|
103
|
+
/^>\s?/.test(line) ||
|
|
104
|
+
/^\s*---+\s*$/.test(line);
|
|
105
|
+
}
|
|
106
|
+
function listItem(text) {
|
|
107
|
+
return { type: 'listItem', content: [paragraphFromLines([text])] };
|
|
108
|
+
}
|
|
109
|
+
/** Build an ADF document from Specrails Markdown. */
|
|
20
110
|
function textToAdf(text) {
|
|
21
|
-
const
|
|
111
|
+
const lines = text.split('\n');
|
|
112
|
+
const content = [];
|
|
113
|
+
let i = 0;
|
|
114
|
+
const pushParagraph = (paragraphLines) => {
|
|
115
|
+
content.push(paragraphFromLines(paragraphLines));
|
|
116
|
+
};
|
|
117
|
+
while (i < lines.length) {
|
|
118
|
+
const line = lines[i];
|
|
119
|
+
if (line.length === 0) {
|
|
120
|
+
content.push({ type: 'paragraph' });
|
|
121
|
+
i += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const fence = /^```([A-Za-z0-9_-]+)?\s*$/.exec(line);
|
|
125
|
+
if (fence) {
|
|
126
|
+
const codeLines = [];
|
|
127
|
+
i += 1;
|
|
128
|
+
while (i < lines.length && !/^```\s*$/.test(lines[i])) {
|
|
129
|
+
codeLines.push(lines[i]);
|
|
130
|
+
i += 1;
|
|
131
|
+
}
|
|
132
|
+
if (i < lines.length)
|
|
133
|
+
i += 1;
|
|
134
|
+
content.push({
|
|
135
|
+
type: 'codeBlock',
|
|
136
|
+
...(fence[1] ? { attrs: { language: fence[1] } } : {}),
|
|
137
|
+
content: codeLines.length > 0 ? [{ type: 'text', text: codeLines.join('\n') }] : [],
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
142
|
+
if (heading) {
|
|
143
|
+
content.push({
|
|
144
|
+
type: 'heading',
|
|
145
|
+
attrs: { level: heading[1].length },
|
|
146
|
+
content: parseInline(heading[2]),
|
|
147
|
+
});
|
|
148
|
+
i += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const bullet = /^\s*[-*+]\s+(.+)$/.exec(line);
|
|
152
|
+
if (bullet) {
|
|
153
|
+
const items = [];
|
|
154
|
+
while (i < lines.length) {
|
|
155
|
+
const m = /^\s*[-*+]\s+(.+)$/.exec(lines[i]);
|
|
156
|
+
if (!m)
|
|
157
|
+
break;
|
|
158
|
+
items.push(listItem(m[1]));
|
|
159
|
+
i += 1;
|
|
160
|
+
}
|
|
161
|
+
content.push({ type: 'bulletList', content: items });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const ordered = /^\s*(\d+)[.)]\s+(.+)$/.exec(line);
|
|
165
|
+
if (ordered) {
|
|
166
|
+
const items = [];
|
|
167
|
+
const order = parseInt(ordered[1], 10);
|
|
168
|
+
while (i < lines.length) {
|
|
169
|
+
const m = /^\s*\d+[.)]\s+(.+)$/.exec(lines[i]);
|
|
170
|
+
if (!m)
|
|
171
|
+
break;
|
|
172
|
+
items.push(listItem(m[1]));
|
|
173
|
+
i += 1;
|
|
174
|
+
}
|
|
175
|
+
content.push({ type: 'orderedList', attrs: { order }, content: items });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const quote = /^>\s?(.*)$/.exec(line);
|
|
179
|
+
if (quote) {
|
|
180
|
+
const quoteLines = [];
|
|
181
|
+
while (i < lines.length) {
|
|
182
|
+
const m = /^>\s?(.*)$/.exec(lines[i]);
|
|
183
|
+
if (!m)
|
|
184
|
+
break;
|
|
185
|
+
quoteLines.push(m[1]);
|
|
186
|
+
i += 1;
|
|
187
|
+
}
|
|
188
|
+
content.push({ type: 'blockquote', content: [paragraphFromLines(quoteLines)] });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (/^\s*---+\s*$/.test(line)) {
|
|
192
|
+
content.push({ type: 'rule' });
|
|
193
|
+
i += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const paragraphLines = [line];
|
|
197
|
+
i += 1;
|
|
198
|
+
while (i < lines.length && lines[i].length > 0 && !isSpecialMarkdownLine(lines[i])) {
|
|
199
|
+
paragraphLines.push(lines[i]);
|
|
200
|
+
i += 1;
|
|
201
|
+
}
|
|
202
|
+
pushParagraph(paragraphLines);
|
|
203
|
+
}
|
|
22
204
|
return {
|
|
23
205
|
type: 'doc',
|
|
24
206
|
version: 1,
|
|
25
|
-
content:
|
|
26
|
-
? { type: 'paragraph' }
|
|
27
|
-
: { type: 'paragraph', content: [{ type: 'text', text: line }] }),
|
|
207
|
+
content: content.length > 0 ? content : [{ type: 'paragraph' }],
|
|
28
208
|
};
|
|
29
209
|
}
|
|
30
210
|
/** Render a body for the target deployment: ADF for Cloud v3, plain for DC v2. */
|
|
@@ -74,6 +254,8 @@ function railReviewCommentMarker(refId, ticketId) {
|
|
|
74
254
|
function bodyContainsMarker(body, marker) {
|
|
75
255
|
if (typeof body === 'string')
|
|
76
256
|
return body.includes(marker);
|
|
257
|
+
if (adfToText(body).includes(marker))
|
|
258
|
+
return true;
|
|
77
259
|
try {
|
|
78
260
|
return JSON.stringify(body).includes(marker);
|
|
79
261
|
}
|
|
@@ -100,42 +282,141 @@ function commentHasMarker(comment, marker) {
|
|
|
100
282
|
}
|
|
101
283
|
return bodyContainsMarker(comment.body, marker);
|
|
102
284
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Flatten an ADF document (or plain string) back to text — used to read inbound
|
|
105
|
-
* Jira descriptions/comments into the local cache.
|
|
106
|
-
*/
|
|
107
|
-
const BLOCK_SEPARATOR_TYPES = new Set([
|
|
108
|
-
'paragraph',
|
|
109
|
-
'heading',
|
|
110
|
-
'codeBlock',
|
|
111
|
-
'blockquote',
|
|
112
|
-
'panel',
|
|
113
|
-
'tableRow',
|
|
114
|
-
]);
|
|
115
285
|
function adfToText(body) {
|
|
116
286
|
if (body == null)
|
|
117
287
|
return '';
|
|
118
288
|
if (typeof body === 'string')
|
|
119
289
|
return body;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
290
|
+
const plainParagraphLines = (nodes) => {
|
|
291
|
+
if (!Array.isArray(nodes))
|
|
292
|
+
return null;
|
|
293
|
+
const lines = [];
|
|
294
|
+
for (const raw of nodes) {
|
|
295
|
+
if (!raw || typeof raw !== 'object')
|
|
296
|
+
return null;
|
|
297
|
+
const node = raw;
|
|
298
|
+
if (node.type !== 'paragraph')
|
|
299
|
+
return null;
|
|
300
|
+
if (node.content === undefined) {
|
|
301
|
+
lines.push('');
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (!Array.isArray(node.content))
|
|
305
|
+
return null;
|
|
306
|
+
let line = '';
|
|
307
|
+
for (const child of node.content) {
|
|
308
|
+
if (!child || typeof child !== 'object')
|
|
309
|
+
return null;
|
|
310
|
+
const inline = child;
|
|
311
|
+
if (inline.type !== 'text' || typeof inline.text !== 'string')
|
|
312
|
+
return null;
|
|
313
|
+
if (Array.isArray(inline.marks) && inline.marks.length > 0)
|
|
314
|
+
return null;
|
|
315
|
+
line += inline.text;
|
|
316
|
+
}
|
|
317
|
+
lines.push(line);
|
|
137
318
|
}
|
|
319
|
+
return lines;
|
|
320
|
+
};
|
|
321
|
+
const inlineText = (nodes) => {
|
|
322
|
+
if (!Array.isArray(nodes))
|
|
323
|
+
return '';
|
|
324
|
+
return nodes.map((node) => {
|
|
325
|
+
if (!node || typeof node !== 'object')
|
|
326
|
+
return '';
|
|
327
|
+
const n = node;
|
|
328
|
+
if (n.type === 'hardBreak')
|
|
329
|
+
return '\n';
|
|
330
|
+
if (n.type === 'mention' && typeof n.attrs?.text === 'string')
|
|
331
|
+
return n.attrs.text;
|
|
332
|
+
if (n.type !== 'text' || typeof n.text !== 'string')
|
|
333
|
+
return '';
|
|
334
|
+
let text = n.text;
|
|
335
|
+
const marks = Array.isArray(n.marks) ? n.marks : [];
|
|
336
|
+
for (const mark of marks) {
|
|
337
|
+
if (mark.type === 'code')
|
|
338
|
+
text = `\`${text}\``;
|
|
339
|
+
else if (mark.type === 'strong')
|
|
340
|
+
text = `**${text}**`;
|
|
341
|
+
else if (mark.type === 'em')
|
|
342
|
+
text = `*${text}*`;
|
|
343
|
+
else if (mark.type === 'strike')
|
|
344
|
+
text = `~~${text}~~`;
|
|
345
|
+
}
|
|
346
|
+
const link = marks.find((mark) => mark.type === 'link' && typeof mark.attrs?.href === 'string');
|
|
347
|
+
if (link?.attrs?.href)
|
|
348
|
+
text = `[${text}](${link.attrs.href})`;
|
|
349
|
+
return text;
|
|
350
|
+
}).join('');
|
|
138
351
|
};
|
|
139
|
-
|
|
140
|
-
|
|
352
|
+
const renderBlocks = (nodes) => {
|
|
353
|
+
if (!Array.isArray(nodes))
|
|
354
|
+
return [];
|
|
355
|
+
const blocks = [];
|
|
356
|
+
const renderListItem = (node, prefix) => {
|
|
357
|
+
const rendered = renderBlocks(node?.content);
|
|
358
|
+
const body = rendered.length > 0 ? rendered.join('\n\n') : '';
|
|
359
|
+
const lines = body.split('\n');
|
|
360
|
+
return lines.map((line, index) => index === 0 ? `${prefix}${line}` : ` ${line}`).join('\n');
|
|
361
|
+
};
|
|
362
|
+
for (const raw of nodes) {
|
|
363
|
+
if (!raw || typeof raw !== 'object')
|
|
364
|
+
continue;
|
|
365
|
+
const node = raw;
|
|
366
|
+
if (node.type === 'paragraph') {
|
|
367
|
+
blocks.push(inlineText(node.content));
|
|
368
|
+
}
|
|
369
|
+
else if (node.type === 'heading') {
|
|
370
|
+
const level = Math.min(6, Math.max(1, Number(node.attrs?.level) || 2));
|
|
371
|
+
blocks.push(`${'#'.repeat(level)} ${inlineText(node.content)}`.trim());
|
|
372
|
+
}
|
|
373
|
+
else if (node.type === 'bulletList') {
|
|
374
|
+
const items = Array.isArray(node.content) ? node.content.map((item) => renderListItem(item, '- ')) : [];
|
|
375
|
+
blocks.push(items.join('\n'));
|
|
376
|
+
}
|
|
377
|
+
else if (node.type === 'orderedList') {
|
|
378
|
+
const start = Number(node.attrs?.order) || 1;
|
|
379
|
+
const items = Array.isArray(node.content)
|
|
380
|
+
? node.content.map((item, index) => renderListItem(item, `${start + index}. `))
|
|
381
|
+
: [];
|
|
382
|
+
blocks.push(items.join('\n'));
|
|
383
|
+
}
|
|
384
|
+
else if (node.type === 'listItem') {
|
|
385
|
+
blocks.push(...renderBlocks(node.content));
|
|
386
|
+
}
|
|
387
|
+
else if (node.type === 'codeBlock') {
|
|
388
|
+
const lang = typeof node.attrs?.language === 'string' ? node.attrs.language : '';
|
|
389
|
+
blocks.push(`\`\`\`${lang}\n${inlineText(node.content)}\n\`\`\``);
|
|
390
|
+
}
|
|
391
|
+
else if (node.type === 'blockquote') {
|
|
392
|
+
const quote = renderBlocks(node.content).join('\n\n');
|
|
393
|
+
blocks.push(quote.split('\n').map((line) => line ? `> ${line}` : '>').join('\n'));
|
|
394
|
+
}
|
|
395
|
+
else if (node.type === 'rule') {
|
|
396
|
+
blocks.push('---');
|
|
397
|
+
}
|
|
398
|
+
else if (node.type === 'panel') {
|
|
399
|
+
blocks.push(renderBlocks(node.content).join('\n\n'));
|
|
400
|
+
}
|
|
401
|
+
else if (node.type === 'table') {
|
|
402
|
+
blocks.push(renderBlocks(node.content).join('\n\n'));
|
|
403
|
+
}
|
|
404
|
+
else if (node.type === 'tableRow') {
|
|
405
|
+
blocks.push(renderBlocks(node.content).join('\n'));
|
|
406
|
+
}
|
|
407
|
+
else if (node.type === 'tableCell' || node.type === 'tableHeader') {
|
|
408
|
+
blocks.push(renderBlocks(node.content).join(' '));
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
blocks.push(...renderBlocks(node.content));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return blocks;
|
|
415
|
+
};
|
|
416
|
+
const root = body;
|
|
417
|
+
const legacyLines = plainParagraphLines(root.content);
|
|
418
|
+
if (legacyLines) {
|
|
419
|
+
return legacyLines.join('\n').replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
420
|
+
}
|
|
421
|
+
return renderBlocks(root.content).join('\n\n').replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
141
422
|
}
|
|
@@ -6,7 +6,7 @@ exports.isFactoryLoopId = isFactoryLoopId;
|
|
|
6
6
|
exports.factoryLoopMode = factoryLoopMode;
|
|
7
7
|
exports.factoryLoopForMode = factoryLoopForMode;
|
|
8
8
|
const loop_templates_1 = require("./loop-templates");
|
|
9
|
-
const GREEN_GOAL = '
|
|
9
|
+
const GREEN_GOAL = 'Stop only when the latest verification step reports {{const:VERIFICATION_PASS}} and the history proves the spec is implemented with all required tests/build checks passing.';
|
|
10
10
|
// Factory loops run the WHOLE architect→developer→reviewer pipeline inside a
|
|
11
11
|
// single AI step (`/specrails:implement` etc.), so they need far more headroom
|
|
12
12
|
// than the engine defaults (loop 30 min / step 15 min): a real implement can run
|
|
@@ -17,6 +17,37 @@ const loop_decider_1 = require("./loop-decider");
|
|
|
17
17
|
const loop_runs_store_1 = require("./loop-runs-store");
|
|
18
18
|
const ai_invocations_1 = require("./ai-invocations");
|
|
19
19
|
const ids_1 = require("./ids");
|
|
20
|
+
const IMPLEMENT_CMD_TOKEN_RE = /\{\{\s*cmd:(?:implement|batch)\s*\}\}/;
|
|
21
|
+
const IMPLEMENT_CMD_TEXT_RE = /(?:^|\s)(?:\/(?:specrails:|sr:)?(?:implement|batch-implement)|\$(?:implement|batch-implement))\b/;
|
|
22
|
+
const LOOP_BLOCKED_LINE_RE = /(?:^|\n)\s*LOOP_BLOCKED:\s*(.+?)(?:\n|$)/i;
|
|
23
|
+
const HUMAN_PROCEED_QUESTION_RE = /(?:^|\n)\s*(?:\*\*)?How would you like to proceed\??/i;
|
|
24
|
+
const HUMAN_PROCEED_NO_WORK_RE = /\b(?:no code has changed|I haven't launched any agents|I have not launched any agents|not a fresh feature to build|would duplicate work|sync first|treat this as)\b/i;
|
|
25
|
+
function withReviewContinuationContext(base, rawTemplate, spec) {
|
|
26
|
+
if (spec?.status !== 'on_review')
|
|
27
|
+
return base;
|
|
28
|
+
if (!IMPLEMENT_CMD_TOKEN_RE.test(rawTemplate) && !IMPLEMENT_CMD_TEXT_RE.test(rawTemplate) && !IMPLEMENT_CMD_TEXT_RE.test(base))
|
|
29
|
+
return base;
|
|
30
|
+
return [
|
|
31
|
+
base,
|
|
32
|
+
'',
|
|
33
|
+
'---',
|
|
34
|
+
'Specrails rail continuation context:',
|
|
35
|
+
'- This ticket is already on_review and this run is fully unattended; the user has already chosen to continue implementation work.',
|
|
36
|
+
'- If the current branch has an existing open PR, or the feature appears already implemented, do NOT pause to ask whether to proceed and do NOT restart from scratch.',
|
|
37
|
+
'- Treat the run as review follow-ups on the current PR/worktree: inspect the ticket description, PR context, current branch, and working diff; implement only the missing requested deltas.',
|
|
38
|
+
'- If the local branch is ahead/behind its remote, do not stop for confirmation. Continue safely on the current worktree; only pull/rebase when it is clearly safe and necessary.',
|
|
39
|
+
'- If truly no code change is needed, state that clearly and leave the tree unchanged; otherwise make the smallest correct change and let the following verify step prove it.',
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
function aiStepBlockedReason(text) {
|
|
43
|
+
const explicit = LOOP_BLOCKED_LINE_RE.exec(text);
|
|
44
|
+
if (explicit?.[1]?.trim())
|
|
45
|
+
return explicit[1].trim();
|
|
46
|
+
if (HUMAN_PROCEED_QUESTION_RE.test(text) && HUMAN_PROCEED_NO_WORK_RE.test(text)) {
|
|
47
|
+
return 'The AI step asked how to proceed instead of performing unattended work.';
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
20
51
|
const HISTORY_MAX_CHARS = 1500;
|
|
21
52
|
/** Caps for the `loop_step` detail fields (`template` / `command`) — the
|
|
22
53
|
* payload persists per event, so a runaway prompt must not bloat the events
|
|
@@ -46,9 +77,10 @@ function truncate(s, max = 600) {
|
|
|
46
77
|
// ai-step prompts and shell commands as `{{run.<name>}}`. v1 captures exactly one
|
|
47
78
|
// — `changeId`, the OpenSpec change id — written so the family can generalize.
|
|
48
79
|
const RUN_TOKEN_RE = /\{\{\s*run\.(\w+)\s*\}\}/g;
|
|
49
|
-
/** First `openspec/changes/<id>` path mentioned in a step's output
|
|
50
|
-
*
|
|
51
|
-
|
|
80
|
+
/** First ACTIVE `openspec/changes/<id>` path mentioned in a step's output.
|
|
81
|
+
* Archive paths (`openspec/changes/archive/...`) are historical destinations,
|
|
82
|
+
* not runnable change ids for later `{{run.changeId}}` commands. */
|
|
83
|
+
const CHANGE_ID_RE = /openspec\/changes\/(?!archive(?:\/|$))([A-Za-z0-9._-]+)(?=\/|\s|$)/g;
|
|
52
84
|
/** Replace `{{run.<name>}}` with the captured value; uncaptured → '' (never a
|
|
53
85
|
* leaked literal token). Applied AFTER `{{cmd:*}}` and `{{spec.*}}`. */
|
|
54
86
|
function resolveRunVars(text, vars) {
|
|
@@ -57,6 +89,7 @@ function resolveRunVars(text, vars) {
|
|
|
57
89
|
/** Extract the OpenSpec change id from a step's output (first match wins), or
|
|
58
90
|
* undefined when none is present. */
|
|
59
91
|
function extractChangeId(text) {
|
|
92
|
+
CHANGE_ID_RE.lastIndex = 0;
|
|
60
93
|
const m = CHANGE_ID_RE.exec(text);
|
|
61
94
|
return m ? m[1] : undefined;
|
|
62
95
|
}
|
|
@@ -478,7 +511,8 @@ class LoopRunManager {
|
|
|
478
511
|
// --yes`, codex `$implement #<id> --yes`) — then resolve `{{spec.*}}`
|
|
479
512
|
// data tokens and finally `{{const:*}}` library constants.
|
|
480
513
|
const rawTemplate = String(node.data?.prompt ?? '');
|
|
481
|
-
const
|
|
514
|
+
const expanded = (0, loop_constants_1.resolveConstants)(resolveRunVars((0, loop_graph_1.interpolateSpec)((0, loop_command_catalog_1.expandCommands)(rawTemplate, { provider: nodeProvider, ticketIds: req.spec?.ticketIds, specId: req.spec?.id }), req.spec), runVars), constMap);
|
|
515
|
+
const base = withReviewContinuationContext(expanded, rawTemplate, req.spec);
|
|
482
516
|
// Inject the cross-iteration history only when there's no live session
|
|
483
517
|
// to carry it (a fresh pass) OR right after a Decider 'continue' (so the
|
|
484
518
|
// step sees the verdict). A mid-body resumed step already has it.
|
|
@@ -542,10 +576,13 @@ class LoopRunManager {
|
|
|
542
576
|
resultText: res.text.trim() ? res.text : null,
|
|
543
577
|
});
|
|
544
578
|
const zeroWork = res.zeroWork === true || oneShotZeroWork;
|
|
545
|
-
const
|
|
579
|
+
const blockedReason = aiStepBlockedReason(res.text);
|
|
580
|
+
const stepFailed = res.failed === true || zeroWork || blockedReason !== null;
|
|
546
581
|
const stepErrorText = res.errorText ?? (zeroWork
|
|
547
582
|
? `zero work performed — the command never ran${res.text.trim() ? `: ${res.text.trim()}` : ''}`
|
|
548
|
-
:
|
|
583
|
+
: blockedReason
|
|
584
|
+
? `blocked — ${blockedReason}`
|
|
585
|
+
: undefined);
|
|
549
586
|
// Make the failure reason land visibly INSIDE the step's log
|
|
550
587
|
// segment (the interactive session already surfaced its own note at
|
|
551
588
|
// settle; the Template/Command flat-log lines are gone, so without
|
|
@@ -568,6 +605,12 @@ class LoopRunManager {
|
|
|
568
605
|
aiSessionId = res.sessionId;
|
|
569
606
|
history.push(`AI Step: ${truncate(res.text)}`);
|
|
570
607
|
record(`loop:${runId}`, { ...res, failed: stepFailed }, aiStepStart);
|
|
608
|
+
if (blockedReason) {
|
|
609
|
+
logLine(`\n■ Loop blocked — needs a human decision: ${blockedReason}`, 'stderr');
|
|
610
|
+
outcome = 'blocked';
|
|
611
|
+
settled = true;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
571
614
|
// Capture the OpenSpec change id from a step's output the FIRST time it
|
|
572
615
|
// appears (first-match-wins, kept stable across loop-back iterations so
|
|
573
616
|
// the re-pass amends the same change). Used by `{{run.changeId}}` in the
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerGitRoutes = registerGitRoutes;
|
|
4
4
|
const project_git_1 = require("./project-git");
|
|
5
5
|
const git_diagnostics_1 = require("./git-diagnostics");
|
|
6
|
+
const pr_publisher_1 = require("./pr-publisher");
|
|
6
7
|
// ─── Git domain routes (/api/projects/:projectId/git) ─────────────────────────
|
|
7
8
|
//
|
|
8
9
|
// Backs the Agent-Mode git bar: current branch + last commit + local branches,
|
|
@@ -10,6 +11,7 @@ const git_diagnostics_1 = require("./git-diagnostics");
|
|
|
10
11
|
// REAL repo — under artifact relocation the workspace never holds the code).
|
|
11
12
|
function registerGitRoutes(deps) {
|
|
12
13
|
const { router, ctx } = deps;
|
|
14
|
+
const exec = deps.exec ?? pr_publisher_1.defaultExec;
|
|
13
15
|
router.get('/:projectId/git', async (req, res) => {
|
|
14
16
|
try {
|
|
15
17
|
res.json(await (0, project_git_1.getProjectGitInfo)(ctx(req).project.path));
|
|
@@ -19,6 +21,30 @@ function registerGitRoutes(deps) {
|
|
|
19
21
|
res.status(500).json({ error: 'Failed to read git info' });
|
|
20
22
|
}
|
|
21
23
|
});
|
|
24
|
+
router.get('/:projectId/git/pull-requests/:number', async (req, res) => {
|
|
25
|
+
const prNumber = Number.parseInt(String(req.params.number ?? ''), 10);
|
|
26
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
27
|
+
res.status(400).json({ error: 'invalid_pull_request_number' });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const r = await exec.run('gh', ['pr', 'view', String(prNumber), '--json', 'url'], ctx(req).project.path);
|
|
32
|
+
if (r.code !== 0) {
|
|
33
|
+
res.status(404).json({ error: 'pull_request_not_found' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const parsed = JSON.parse(r.stdout);
|
|
37
|
+
if (typeof parsed.url !== 'string' || !parsed.url) {
|
|
38
|
+
res.status(404).json({ error: 'pull_request_not_found' });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
res.json({ prNumber, url: parsed.url });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.error('[project-git] pull request lookup failed:', err);
|
|
45
|
+
res.status(500).json({ error: 'pull_request_lookup_failed' });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
22
48
|
// READ-ONLY git/gh diagnostics (backs the specrails_git MCP tool). A fixed
|
|
23
49
|
// allowlist of read-only commands — the query only names an action, never
|
|
24
50
|
// arguments. Git runs against project.path (the real repo; the workspace never
|
|
@@ -301,6 +301,14 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
301
301
|
originSurface: input.originSurface ?? 'dashboard',
|
|
302
302
|
originConversationId: input.originConversationId ?? null,
|
|
303
303
|
}).id;
|
|
304
|
+
if (launchContinuation) {
|
|
305
|
+
(0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
|
|
306
|
+
branch: launchContinuation.branch,
|
|
307
|
+
prUrl: launchContinuation.prUrl,
|
|
308
|
+
prNumber: launchContinuation.prNumber,
|
|
309
|
+
prState: 'pr-created',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
304
312
|
broadcastPrState();
|
|
305
313
|
syncOriginCard('post');
|
|
306
314
|
}
|
|
@@ -336,6 +344,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
336
344
|
ticketId: unit.ticketId,
|
|
337
345
|
baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
|
|
338
346
|
branch: unitBranchName(unit.ticketId),
|
|
347
|
+
refreshFromBaseRef: Boolean(continuationTarget?.baseRef),
|
|
339
348
|
});
|
|
340
349
|
// Per-run overlay: merge-link the framework surface the checkout didn't
|
|
341
350
|
// bring into the worktree (idempotent; resume-safe via its manifest).
|
|
@@ -102,6 +102,22 @@ function worktreeBranch(slug, ticketId) {
|
|
|
102
102
|
function worktreePath(worktreesRoot, ticketId) {
|
|
103
103
|
return path.join(worktreesRoot, `ticket-${ticketId}`);
|
|
104
104
|
}
|
|
105
|
+
async function fastForwardExistingBranch(git, repoDir, branch, baseRef, mountedWorktreePath) {
|
|
106
|
+
if (!baseRef)
|
|
107
|
+
return;
|
|
108
|
+
if (mountedWorktreePath) {
|
|
109
|
+
const ancestor = await git.run(['merge-base', '--is-ancestor', 'HEAD', baseRef], mountedWorktreePath);
|
|
110
|
+
if (ancestor.code !== 0)
|
|
111
|
+
return;
|
|
112
|
+
await git.run(['merge', '--ff-only', baseRef], mountedWorktreePath).catch(() => { });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const localRef = `refs/heads/${branch}`;
|
|
116
|
+
const ancestor = await git.run(['merge-base', '--is-ancestor', localRef, baseRef], repoDir);
|
|
117
|
+
if (ancestor.code !== 0)
|
|
118
|
+
return;
|
|
119
|
+
await git.run(['update-ref', localRef, baseRef], repoDir).catch(() => { });
|
|
120
|
+
}
|
|
105
121
|
/**
|
|
106
122
|
* Create a fresh worktree+branch for a ticket. Idempotent-ish: if the branch
|
|
107
123
|
* already exists it is reused with `-B`. Throws on git failure so the caller can
|
|
@@ -128,9 +144,16 @@ async function createWorktree(git, input) {
|
|
|
128
144
|
// at local-only. Detached HEAD / a git failure falls back to the input.
|
|
129
145
|
const head = await git.run(['rev-parse', '--abbrev-ref', 'HEAD'], wt);
|
|
130
146
|
const actual = head.code === 0 ? head.stdout.trim() : '';
|
|
131
|
-
|
|
147
|
+
const actualBranch = actual && actual !== 'HEAD' ? actual : branch;
|
|
148
|
+
if (input.refreshFromBaseRef && actualBranch === branch) {
|
|
149
|
+
await fastForwardExistingBranch(git, input.repoDir, branch, input.baseRef, wt);
|
|
150
|
+
}
|
|
151
|
+
return { branch: actualBranch, worktreePath: wt };
|
|
132
152
|
}
|
|
133
153
|
const hasBranch = (await git.run(['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], input.repoDir)).code === 0;
|
|
154
|
+
if (hasBranch && input.refreshFromBaseRef) {
|
|
155
|
+
await fastForwardExistingBranch(git, input.repoDir, branch, input.baseRef);
|
|
156
|
+
}
|
|
134
157
|
const args = hasBranch
|
|
135
158
|
? ['worktree', 'add', wt, branch]
|
|
136
159
|
: ['worktree', 'add', '-b', branch, wt, base];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=`Missionszentrale`,t=`Specrails per Chat bedienen`,n=`Missionszentrale`,r=`Bitte den Agenten um alles…`,i=`Senden`,a=`Stopp`,o=`Kopieren`,s=`Maximieren`,c=`Wiederherstellen`,l=`Minimieren`,u=`Schließen`,d=`Neue Mission`,f=`Wie lautet die Mission?`,p=`Wähle ein Projekt und frag dann. Das Board aktualisiert sich live, während der Agent arbeitet.`,m=`Denkt nach…`,h=`Verbinden…`,g=`App wird abgefragt…`,_={compact:`Kompakt`,comfortable:`Komfortabel`,max:`Maximieren`},v={label:`Modell`},y={hint:`Verlauf durchsuchen — tippen zum Bearbeiten, ↑↓ zum Navigieren`},b={queued:`In Warteschlange`,send:`In Warteschlange senden`,sendHint:`Der Agent arbeitet noch — diese Nachricht wird eingereiht und danach ausgeführt`,placeholder:`Mehr hinzufügen, während der Agent arbeitet — wird eingereiht…`},x={editing:`Bearbeite eingereihte Nachricht {{n}} von {{m}}`,hint:`↑↓ zum Navigieren · Enter speichert · Esc bricht ab`,save:`Eingereihte Nachricht speichern`,cancel:`Bearbeitung abbrechen`,dispatched:`Diese eingereihte Nachricht wurde bereits gesendet — dein Text bleibt als Entwurf erhalten`,saveFailed:`Eingereihte Nachricht konnte nicht gespeichert werden`},S={branch:`Branch`,detached:`(detached)`,dirty:`Nicht committete Änderungen`,switched:`Zu {{branch}} gewechselt`,switchFailed:`Branch-Wechsel fehlgeschlagen`,lockedWhileWorking:`Branch gesperrt, während der Agent arbeitet`,searchPlaceholder:`Branches durchsuchen…`,noMatches:`Keine passenden Branches`,worktrees:`Worktrees`,worktreePathCopied:`Worktree-Pfad kopiert`,copyWorktreePath:`Worktree-Pfad kopieren`},C={label:`Anbieter`,claude:`Claude`,codex:`Codex`,gemini:`Gemini`},w={cycleHint:`⇧⇥ zum Wechseln`,observe:{name:`Beobachten`,desc:`Nur lesen — nichts ändert sich`},edit:{name:`Bearbeiten`,desc:`Erstellen und bearbeiten (umkehrbar)`},operate:{name:`Ausführen`,desc:`KI starten — kostet Geld`},autonomous:{name:`Autonom`,desc:`Löschen und beenden — unumkehrbar`},lockedTitle:`Höhere Stufe erforderlich`,lockedBody:`Diese Aktion erfordert die Stufe „{{level}}“. Du bist auf „{{current}}“.`,raiseHint:`⇧⇥ um die Stufe zu erhöhen, oder frag anders.`},T={title:`Aktion bestätigen`,costAction:`{{action}} · ~{{cost}} · {{tier}}`,approve:`Genehmigen`,cancel:`Abbrechen`,dontAskAgain:`In dieser Sitzung nicht mehr fragen`,approved:`Genehmigt`,cancelled:`Abgebrochen`},E={home:`Start`,searchPlaceholder:`Mit Specrails sprechen…`,recents:`Zuletzt verwendet`,allProjects:`Alle Projekte`,searchAll:`In allen Projekten suchen`,addProject:`Projekt hinzufügen…`,homeHint:`Kein Projekt ausgewählt — über alle Projekte hinweg.`,askCreateOrSearch:`Es ist kein Projekt ausgewählt. Möchtest du ein neues Projekt erstellen oder über alle Projekte hinweg suchen?`},D={label:`Mission wechseln`,untitled:`Neue Mission`,new:`Neue Mission`,searchPlaceholder:`Missionen durchsuchen…`,noMatches:`Keine Mission gefunden`,working:`Agent arbeitet`,queuedBadge:`{{n}} in Warteschlange`,delete:`Mission "{{title}}" löschen`,deleteConfirm:`Löschen?`,deleteLiveConfirm:`Der Agent arbeitet — trotzdem löschen?`,confirmDelete:`Löschen bestätigen`,cancelDelete:`Mission behalten`,deleteFailed:`Mission konnte nicht gelöscht werden`},O={title:`Kein KI-Anbieter installiert`,body:`Der Agent benötigt Claude, Codex oder Gemini auf deinem Rechner. Installiere einen und öffne den Agenten erneut, um zu chatten.`,placeholder:`Installiere einen KI-Anbieter, um den Agenten zu nutzen…`},k={title:`Specrails MCP ist aus`,body:`Der Agent braucht den eingebetteten MCP-Server, um die App zu bedienen.`,enable:`Specrails MCP aktivieren`,enabling:`Wird aktiviert…`,enabled:`MCP aktiviert`},A={live:`live`,queued:`in Warteschlange`,running:`läuft`,done:`fertig`,failed:`fehlgeschlagen`},j={generic:`Etwas ist schiefgelaufen. Versuch es erneut.`,busy:`Der Agent ist beschäftigt. Versuch es gleich noch einmal.`},M={kindAgent:`Agenten-Mission`},N=`Neue Mission`,P=`Zur Missionszentrale wechseln`,F=`Zum Board wechseln`,I=`Suchen`,L=`Start`,R=`Ohne Titel`,z={title:`Im Workspace`,terminal:`Terminal`,files:`Dateien`,browser:`Browser`,requiresProject:`Wähle ein Projekt, um dieses Tool zu nutzen`,resizeCode:`Code-Bereich anpassen`,closeFiles:`Code schließen`,browserConfirm:`Zum Agenten hinzufügen`,browserCaptured:`Aufnahme hinzugefügt`,uploadFailed:`Upload fehlgeschlagen`,requiresConversation:`Starte eine Mission, um dieses Werkzeug zu nutzen`,jobs:`Jobs`,closeJobs:`Jobs schließen`,resizeJobs:`Jobs-Bereich anpassen`,noJobs:`Noch keine Jobs — bitte den Agenten, einen zu starten`},B=`Datei anhängen`,V=`„{{title}}“ löschen`,H=`Löschen von „{{title}}“ bestätigen`,U=`bestätigen?`,W=`Dieser Anbieter kann noch keine Bilder lesen — hänge stattdessen Textdateien an`,G={label:`Aufwand`,minimal:`Minimal`,low:`Niedrig`,medium:`Mittel`,high:`Hoch`,xhigh:`Sehr hoch`},K=`Wie lautet die Mission, Neo?`,q={rail:`Rail {{index}}`,specCount_one:`{{count}} Spec`,specCount_other:`{{count}} Specs`,base:`Basis-Branch`,openPr:`PR öffnen`,title:{building:`Implementierung in einem isolierten Worktree…`,on_review:`Implementierung bereit zur Prüfung`,pr_draft:`Entwurfs-PR erstellt`,pr_draft_degraded:`PR-Auslieferung unvollständig`,pr_ready:`PR bereit zum Merge`,pr_failed:`PR-Erstellung fehlgeschlagen`,merged:`PR gemergt`,discarded:`Verworfen`},buildingHint:`Nach Abschluss wirst du gefragt, ob der PR erstellt werden soll.`,createPr:`PR erstellen`,publish:`Veröffentlichen`,retry:`Erneut versuchen`,retryPr:`PR erneut versuchen`,checkMerge:`Merge prüfen`,discard:`Verwerfen`,confirmDiscard:`Verwerfen bestätigen?`,mergedNote:`Gemergt — Specs nach Fertig verschoben`,discardedNote:`Verworfen — Specs zurück ins Backlog`,degraded:{pushed:`Der Branch wurde gepusht, aber die PR konnte nicht erstellt werden — erneut versuchen, um abzuschließen.`,localOnly:`Die Änderungen wurden lokal zusammengeführt, aber nicht gepusht — erneut versuchen, um die PR zu veröffentlichen.`},alreadyResolved:`Bereits anderweitig erledigt`,actionFailed:`Aktion fehlgeschlagen`,notMergedYet:`Noch nicht gemergt`,viewLog:`Ausführungsprotokoll für {{ref}} anzeigen`,runElapsedAria:`{{elapsed}} vergangen`,runCostAria:`{{cost}} ausgegeben`,pinned:{label:`Angeheftet`,marker:`Implementierungskarte — oben angeheftet`,collapse:`Angeheftete Karte minimieren`,expand:`Angeheftete Karte ausklappen`,expandCard:`Diese Karte ausklappen`,collapseCard:`Diese Karte minimieren`,collapsed_one:`Implementierungskarte`,collapsed_other:`{{count}} Implementierungskarten`,state:{building:`Läuft`,on_review:`Review`,pr_draft:`Entwurfs-PR`,pr_ready:`Veröffentlicht`,pr_failed:`Fehlgeschlagen`,merged:`Gemerged`,discarded:`Verworfen`}},mergeLocal:`Lokal integrieren`,mergeLocalTooltip:`Führt die gelieferten Branches lokal in {{base}} zusammen (ohne GitHub)`,mergeLocalBlockedDirty:`Integration nicht möglich: dein Arbeitsverzeichnis auf {{base}} hat uncommittete Änderungen. Committe oder stashe sie und versuche es erneut.`,mergeLocalBlockedBranch:`Integration nicht möglich: dein Checkout steht auf {{current}}, nicht auf {{base}}. Wechsle zu {{base}} und versuche es erneut.`,mergedLocally:`Lokal in {{base}} integriert`},J={heading:`Spec-Entwurf`,drafting:`Entwurf wird aktualisiert…`,acceptanceCriteria:`Akzeptanzkriterien`,untitled:`Spec ohne Titel`,expand:`Vollständige Beschreibung anzeigen`,collapse:`Beschreibung einklappen`},Y={openTicket:`Spec #{{id}} öffnen`,openJob:`Job {{id}} öffnen`,openLoop:`Loop {{id}} öffnen`,ticketNotFound:`Spec #{{id}} nicht gefunden — möglicherweise gelöscht`,jobNotFound:`Job nicht gefunden — möglicherweise gelöscht`,loopNotFound:`Loop nicht gefunden — er wurde möglicherweise gelöscht`,lookupFailed:`Referenz konnte nicht geprüft werden — bitte erneut versuchen`},X={untitled:`Unbenannte Mission`,menu:`Missionsmenü`,rename:`Umbenennen`,renamePlaceholder:`Name der Mission`,renameHint:`Zum Umbenennen doppelklicken`,renameFailed:`Umbenennen fehlgeschlagen`,copyName:`Missionsname kopieren`,copyId:`Missions-ID kopieren`,copyProject:`Projektname kopieren`,copyProjectPath:`Projektpfad kopieren`,copyFailed:`Kopieren fehlgeschlagen`,delete:`Mission löschen`,deleteConfirm:`Diese Mission löschen? Kann nicht rückgängig gemacht werden.`,deleteFailed:`Mission konnte nicht gelöscht werden`},Z={title:e,subtitle:t,trigger:n,composerPlaceholder:r,send:i,stop:a,copy:o,maximize:s,restore:c,minimize:l,close:u,newConversation:d,emptyTitle:f,emptyHint:p,thinking:m,connecting:h,consultingCode:g,size:_,model:v,history:y,queue:b,queueEdit:x,git:S,provider:C,tier:w,approval:T,project:E,mission:D,noProvider:O,degraded:k,toolCard:A,error:j,chip:M,newAgent:N,switchToAgent:P,switchToKanban:F,search:I,home:L,untitled:R,workspace:z,attachFile:B,deleteConversation:V,confirmDeleteConversation:H,confirmShort:U,imagesUnsupported:W,effort:G,emptyTitleMatrix:K,prCard:q,specDraft:J,refs:Y,header:X};export{T as approval,B as attachFile,M as chip,u as close,r as composerPlaceholder,H as confirmDeleteConversation,U as confirmShort,h as connecting,g as consultingCode,o as copy,Z as default,k as degraded,V as deleteConversation,G as effort,p as emptyHint,f as emptyTitle,K as emptyTitleMatrix,j as error,S as git,X as header,y as history,L as home,W as imagesUnsupported,s as maximize,l as minimize,D as mission,v as model,N as newAgent,d as newConversation,O as noProvider,q as prCard,E as project,C as provider,b as queue,x as queueEdit,Y as refs,c as restore,I as search,i as send,_ as size,J as specDraft,a as stop,t as subtitle,P as switchToAgent,F as switchToKanban,m as thinking,w as tier,e as title,A as toolCard,n as trigger,R as untitled,z as workspace};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=`Controle de missões`,t=`Opere o Specrails conversando`,n=`Controle de missões`,r=`Peça ao agente o que quiser…`,i=`Enviar`,a=`Parar`,o=`Copiar`,s=`Maximizar`,c=`Restaurar`,l=`Minimizar`,u=`Fechar`,d=`Nova missão`,f=`Qual é a missão?`,p=`Escolha um projeto e depois pergunte. O quadro é atualizado ao vivo enquanto o agente trabalha.`,m=`Pensando…`,h=`Conectando…`,g=`Consultando o app…`,_={compact:`Compacto`,comfortable:`Confortável`,max:`Maximizar`},v={label:`Modelo`},y={hint:`Explorando histórico — escreva para editar, ↑↓ para navegar`},b={queued:`Na fila`,send:`Enviar para a fila`,sendHint:`O agente ainda está trabalhando — esta mensagem entrará na fila e será executada em seguida`,placeholder:`Adicione mais enquanto o agente trabalha — entrará na fila…`},x={editing:`Editando mensagem na fila {{n}} de {{m}}`,hint:`↑↓ para navegar · Enter salva · Esc cancela`,save:`Salvar mensagem na fila`,cancel:`Cancelar edição`,dispatched:`Essa mensagem na fila já foi enviada — seu texto foi mantido como rascunho`,saveFailed:`Não foi possível salvar a mensagem na fila`},S={branch:`Branch`,detached:`(detached)`,dirty:`Alterações sem commit`,switched:`Mudou para {{branch}}`,switchFailed:`Não foi possível trocar de branch`,lockedWhileWorking:`Branch bloqueada enquanto o agente trabalha`,searchPlaceholder:`Buscar branches…`,noMatches:`Nenhuma branch corresponde`,worktrees:`Worktrees`,worktreePathCopied:`Caminho do worktree copiado`,copyWorktreePath:`Copiar caminho do worktree`},C={label:`Provedor`,claude:`Claude`,codex:`Codex`,gemini:`Gemini`},w={cycleHint:`⇧⇥ para mudar`,observe:{name:`Observa`,desc:`Somente leitura — nada muda`},edit:{name:`Edita`,desc:`Criar e editar (reversível)`},operate:{name:`Opera`,desc:`Lançar IA — tem custo`},autonomous:{name:`Autônomo`,desc:`Excluir e encerrar — irreversível`},lockedTitle:`Precisa de um nível superior`,lockedBody:`Essa ação precisa do nível "{{level}}". Você está no "{{current}}".`,raiseHint:`⇧⇥ para aumentar o nível, ou peça de outra forma.`},T={title:`Confirmar ação`,costAction:`{{action}} · ~{{cost}} · {{tier}}`,approve:`Aprovar`,cancel:`Cancelar`,dontAskAgain:`Não perguntar de novo nesta sessão`,approved:`Aprovado`,cancelled:`Cancelado`},E={home:`Início`,searchPlaceholder:`Fale com o Specrails…`,recents:`Recentes`,allProjects:`Todos os projetos`,searchAll:`Buscar em todos os projetos`,addProject:`Adicionar projeto…`,homeHint:`Nenhum projeto selecionado — operando em todos os projetos.`,askCreateOrSearch:`Nenhum projeto está selecionado. Você quer criar um novo projeto ou buscar em todos os projetos?`},D={label:`Trocar de missão`,untitled:`Nova missão`,new:`Nova missão`,searchPlaceholder:`Pesquisar missões…`,noMatches:`Nenhuma missão corresponde`,working:`O agente está trabalhando`,queuedBadge:`{{n}} na fila`,delete:`Excluir missão "{{title}}"`,deleteConfirm:`Excluir?`,deleteLiveConfirm:`O agente está trabalhando — excluir mesmo assim?`,confirmDelete:`Confirmar exclusão`,cancelDelete:`Manter missão`,deleteFailed:`Não foi possível excluir a missão`},O={title:`Nenhum provedor de IA instalado`,body:`O agente precisa de Claude, Codex ou Gemini na sua máquina. Instale um e reabra o agente para começar a conversar.`,placeholder:`Instale um provedor de IA para usar o agente…`},k={title:`O MCP do Specrails está desligado`,body:`O agente precisa do servidor MCP integrado para operar o app.`,enable:`Ativar o MCP do Specrails`,enabling:`Ativando…`,enabled:`MCP ativado`},A={live:`ao vivo`,queued:`na fila`,running:`em execução`,done:`concluído`,failed:`falhou`},j={generic:`Algo deu errado. Tente novamente.`,busy:`O agente está ocupado. Tente novamente em um instante.`},M={kindAgent:`Missão do agente`},N=`Nova missão`,P=`Mudar para o Controle de missões`,F=`Mudar para o quadro`,I=`Buscar`,L=`Início`,R=`Sem título`,z={title:`No workspace`,terminal:`Terminal`,files:`Arquivos`,browser:`Navegador`,requiresProject:`Selecione um projeto para usar esta ferramenta`,resizeCode:`Redimensionar painel de código`,closeFiles:`Fechar código`,browserConfirm:`Adicionar ao agente`,browserCaptured:`Captura adicionada`,uploadFailed:`Falha no upload`,requiresConversation:`Inicie uma missão para usar esta ferramenta`,jobs:`Jobs`,closeJobs:`Fechar jobs`,resizeJobs:`Redimensionar painel de jobs`,noJobs:`Ainda não há jobs — peça ao agente para lançar um`},B=`Anexar arquivo`,V=`Excluir "{{title}}"`,H=`Confirmar exclusão de "{{title}}"`,U=`confirmar?`,W=`Este provedor ainda não lê imagens — anexe arquivos de texto`,G={label:`Esforço`,minimal:`Mínimo`,low:`Baixo`,medium:`Médio`,high:`Alto`,xhigh:`Extra alto`},K=`Qual é a missão, Neo?`,q={rail:`Rail {{index}}`,specCount_one:`{{count}} spec`,specCount_other:`{{count}} specs`,base:`Ramo base`,openPr:`Abrir PR`,title:{building:`A implementar num worktree isolado…`,on_review:`Implementação pronta para revisão`,pr_draft:`PR em rascunho criada`,pr_draft_degraded:`Entrega da PR incompleta`,pr_ready:`PR pronta para merge`,pr_failed:`Falha ao criar a PR`,merged:`Merge da PR concluído`,discarded:`Descartada`},buildingHint:`Quando terminar, ser-lhe-á perguntado se quer criar a PR.`,createPr:`Criar PR`,publish:`Publicar`,retry:`Tentar novamente`,retryPr:`Tentar PR novamente`,checkMerge:`Verificar merge`,discard:`Descartar`,confirmDiscard:`Confirmar descarte?`,mergedNote:`Merge concluído — specs movidas para Concluído`,discardedNote:`Descartada — specs devolvidas ao backlog`,degraded:{pushed:`O ramo foi enviado mas não foi possível criar a PR — tente novamente para concluir.`,localOnly:`As alterações foram montadas localmente mas não enviadas — tente novamente para publicar a PR.`},alreadyResolved:`Já resolvida noutro lugar`,actionFailed:`A ação falhou`,notMergedYet:`Ainda sem merge`,viewLog:`Ver o registro de execução de {{ref}}`,runElapsedAria:`{{elapsed}} decorridos`,runCostAria:`{{cost}} gastos`,pinned:{label:`Fixada`,marker:`Cartão de implementação — fixado acima`,collapse:`Minimizar cartão fixado`,expand:`Expandir cartão fixado`,expandCard:`Expandir este cartão`,collapseCard:`Minimizar este cartão`,collapsed_one:`Cartão de implementação`,collapsed_other:`{{count}} cartões de implementação`,state:{building:`Em curso`,on_review:`Revisão`,pr_draft:`PR rascunho`,pr_ready:`Publicada`,pr_failed:`Falhou`,merged:`Integrada`,discarded:`Descartada`}},mergeLocal:`Integrar localmente`,mergeLocalTooltip:`Faz merge dos branches entregues em {{base}} no seu checkout local (sem GitHub)`,mergeLocalBlockedDirty:`Não é possível integrar: sua árvore de trabalho em {{base}} tem mudanças sem commit. Faça commit ou stash e tente de novo.`,mergeLocalBlockedBranch:`Não é possível integrar: seu checkout está em {{current}}, não em {{base}}. Mude para {{base}} e tente de novo.`,mergedLocally:`Integrado em {{base}} localmente`},J={heading:`Rascunho da spec`,drafting:`Atualizando rascunho…`,acceptanceCriteria:`Critérios de aceitação`,untitled:`Spec sem título`,expand:`Ver descrição completa`,collapse:`Recolher descrição`},Y={openTicket:`Abrir spec #{{id}}`,openJob:`Abrir job {{id}}`,openLoop:`Abrir loop {{id}}`,ticketNotFound:`Spec #{{id}} não encontrada — pode ter sido excluída`,jobNotFound:`Job não encontrado — pode ter sido excluído`,loopNotFound:`Loop não encontrado — pode ter sido excluído`,lookupFailed:`Não foi possível verificar a referência — tente novamente`},X={untitled:`Missão sem título`,menu:`Menu da missão`,rename:`Renomear`,renamePlaceholder:`Nome da missão`,renameHint:`Duplo clique para renomear`,renameFailed:`Não foi possível renomear`,copyName:`Copiar nome da missão`,copyId:`Copiar ID da missão`,copyProject:`Copiar nome do projeto`,copyProjectPath:`Copiar caminho do projeto`,copyFailed:`Não foi possível copiar`,delete:`Excluir missão`,deleteConfirm:`Excluir esta missão? Não pode ser desfeito.`,deleteFailed:`Não foi possível excluir a missão`},Z={title:e,subtitle:t,trigger:n,composerPlaceholder:r,send:i,stop:a,copy:o,maximize:s,restore:c,minimize:l,close:u,newConversation:d,emptyTitle:f,emptyHint:p,thinking:m,connecting:h,consultingCode:g,size:_,model:v,history:y,queue:b,queueEdit:x,git:S,provider:C,tier:w,approval:T,project:E,mission:D,noProvider:O,degraded:k,toolCard:A,error:j,chip:M,newAgent:N,switchToAgent:P,switchToKanban:F,search:I,home:L,untitled:R,workspace:z,attachFile:B,deleteConversation:V,confirmDeleteConversation:H,confirmShort:U,imagesUnsupported:W,effort:G,emptyTitleMatrix:K,prCard:q,specDraft:J,refs:Y,header:X};export{T as approval,B as attachFile,M as chip,u as close,r as composerPlaceholder,H as confirmDeleteConversation,U as confirmShort,h as connecting,g as consultingCode,o as copy,Z as default,k as degraded,V as deleteConversation,G as effort,p as emptyHint,f as emptyTitle,K as emptyTitleMatrix,j as error,S as git,X as header,y as history,L as home,W as imagesUnsupported,s as maximize,l as minimize,D as mission,v as model,N as newAgent,d as newConversation,O as noProvider,q as prCard,E as project,C as provider,b as queue,x as queueEdit,Y as refs,c as restore,I as search,i as send,_ as size,J as specDraft,a as stop,t as subtitle,P as switchToAgent,F as switchToKanban,m as thinking,w as tier,e as title,A as toolCard,n as trigger,R as untitled,z as workspace};
|