@imdeadpool/guardex 7.0.26 → 7.0.31
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 +38 -7
- package/SECURITY.md +1 -1
- package/package.json +4 -4
- package/src/cli/main.js +279 -23
- package/src/context.js +64 -21
- package/src/doctor/index.js +42 -20
- package/src/finish/index.js +1 -0
- package/src/output/index.js +151 -15
- package/src/toolchain/index.js +6 -0
- package/templates/scripts/agent-branch-finish.sh +48 -24
- package/templates/scripts/agent-branch-start.sh +29 -5
- package/templates/scripts/codex-agent.sh +16 -1
- package/templates/vscode/guardex-active-agents/extension.js +126 -5
- package/templates/vscode/guardex-active-agents/package.json +17 -7
- package/templates/vscode/guardex-active-agents/session-schema.js +23 -5
package/src/doctor/index.js
CHANGED
|
@@ -31,6 +31,7 @@ const {
|
|
|
31
31
|
} = require('../sandbox');
|
|
32
32
|
const { ensureOmxScaffold, configureHooks } = require('../scaffold');
|
|
33
33
|
const { detectRecoverableAutoFinishConflict, printAutoFinishSummary } = require('../output');
|
|
34
|
+
const { autoCommitWorktreeForFinish } = require('../finish');
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* @typedef {Object} SandboxMetadata
|
|
@@ -887,23 +888,25 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
887
888
|
return summary;
|
|
888
889
|
}
|
|
889
890
|
|
|
890
|
-
|
|
891
|
-
summary.enabled = false;
|
|
892
|
-
summary.details.push('Skipped auto-finish sweep (origin remote missing).');
|
|
893
|
-
return summary;
|
|
894
|
-
}
|
|
891
|
+
const originAvailable = hasOriginRemote(repoRoot);
|
|
895
892
|
const explicitGhBin = Boolean(String(process.env.GUARDEX_GH_BIN || '').trim());
|
|
896
|
-
if (!explicitGhBin && !originRemoteLooksLikeGithub(repoRoot)) {
|
|
897
|
-
summary.enabled = false;
|
|
898
|
-
summary.details.push('Skipped auto-finish sweep (origin remote is not GitHub).');
|
|
899
|
-
return summary;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
893
|
const ghBin = process.env.GUARDEX_GH_BIN || 'gh';
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
894
|
+
const ghAvailable =
|
|
895
|
+
originAvailable &&
|
|
896
|
+
(explicitGhBin || originRemoteLooksLikeGithub(repoRoot)) &&
|
|
897
|
+
run(ghBin, ['--version']).status === 0;
|
|
898
|
+
|
|
899
|
+
let fallbackMode = '';
|
|
900
|
+
if (!originAvailable) {
|
|
901
|
+
fallbackMode = 'local';
|
|
902
|
+
summary.details.push('origin remote missing; falling back to local direct merge (no push, no PR).');
|
|
903
|
+
} else if (!ghAvailable) {
|
|
904
|
+
fallbackMode = 'direct';
|
|
905
|
+
if (!explicitGhBin && !originRemoteLooksLikeGithub(repoRoot)) {
|
|
906
|
+
summary.details.push('origin remote is not GitHub; falling back to direct merge + push.');
|
|
907
|
+
} else {
|
|
908
|
+
summary.details.push(`${ghBin} not available; falling back to direct merge + push.`);
|
|
909
|
+
}
|
|
907
910
|
}
|
|
908
911
|
|
|
909
912
|
const branchWorktrees = mapWorktreePathsByBranch(repoRoot);
|
|
@@ -936,16 +939,29 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
936
939
|
continue;
|
|
937
940
|
}
|
|
938
941
|
|
|
942
|
+
const branchWorktree = branchWorktrees.get(branch) || '';
|
|
943
|
+
if (branchWorktree && hasSignificantWorkingTreeChanges(branchWorktree)) {
|
|
944
|
+
try {
|
|
945
|
+
const commitResult = autoCommitWorktreeForFinish(repoRoot, branchWorktree, branch, {});
|
|
946
|
+
if (commitResult.committed) {
|
|
947
|
+
counts = aheadBehind(repoRoot, branch, baseBranch);
|
|
948
|
+
}
|
|
949
|
+
} catch (error) {
|
|
950
|
+
summary.failed += 1;
|
|
951
|
+
summary.details.push(`[fail] ${branch}: auto-commit failed (${error.message}).`);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
939
956
|
if (counts.ahead <= 0) {
|
|
940
957
|
summary.skipped += 1;
|
|
941
958
|
summary.details.push(`[skip] ${branch}: already merged into ${baseBranch}.`);
|
|
942
959
|
continue;
|
|
943
960
|
}
|
|
944
961
|
|
|
945
|
-
const branchWorktree = branchWorktrees.get(branch) || '';
|
|
946
962
|
if (branchWorktree && hasSignificantWorkingTreeChanges(branchWorktree)) {
|
|
947
963
|
summary.skipped += 1;
|
|
948
|
-
summary.details.push(`[skip] ${branch}: dirty worktree (${branchWorktree}).`);
|
|
964
|
+
summary.details.push(`[skip] ${branch}: dirty worktree after auto-commit (${branchWorktree}).`);
|
|
949
965
|
continue;
|
|
950
966
|
}
|
|
951
967
|
|
|
@@ -955,10 +971,16 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
955
971
|
branch,
|
|
956
972
|
'--base',
|
|
957
973
|
baseBranch,
|
|
958
|
-
'--via-pr',
|
|
959
|
-
waitForMerge ? '--wait-for-merge' : '--no-wait-for-merge',
|
|
960
|
-
'--cleanup',
|
|
961
974
|
];
|
|
975
|
+
if (fallbackMode === 'local') {
|
|
976
|
+
finishArgs.push('--direct-only', '--no-push');
|
|
977
|
+
} else if (fallbackMode === 'direct') {
|
|
978
|
+
finishArgs.push('--direct-only');
|
|
979
|
+
} else {
|
|
980
|
+
finishArgs.push('--via-pr');
|
|
981
|
+
}
|
|
982
|
+
finishArgs.push(waitForMerge ? '--wait-for-merge' : '--no-wait-for-merge');
|
|
983
|
+
finishArgs.push('--cleanup');
|
|
962
984
|
const finishResult = runPackageAsset('branchFinish', finishArgs, { cwd: repoRoot });
|
|
963
985
|
const combinedOutput = [finishResult.stdout || '', finishResult.stderr || ''].join('\n').trim();
|
|
964
986
|
|
package/src/finish/index.js
CHANGED
package/src/output/index.js
CHANGED
|
@@ -6,6 +6,8 @@ const {
|
|
|
6
6
|
LEGACY_NAMES,
|
|
7
7
|
GUARDEX_REPO_TOGGLE_ENV,
|
|
8
8
|
CLI_COMMAND_DESCRIPTIONS,
|
|
9
|
+
CLI_COMMAND_GROUPS,
|
|
10
|
+
CLI_QUICKSTART_STEPS,
|
|
9
11
|
AGENT_BOT_DESCRIPTIONS,
|
|
10
12
|
DOCTOR_AUTO_FINISH_DETAIL_LIMIT,
|
|
11
13
|
DOCTOR_AUTO_FINISH_BRANCH_LABEL_MAX,
|
|
@@ -166,6 +168,41 @@ function commandCatalogLines(indent = ' ') {
|
|
|
166
168
|
);
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
// groupedCommandCatalogLines renders CLI_COMMAND_GROUPS as a nested list with
|
|
172
|
+
// group headers separated by blank lines. It accepts an optional `colorize`
|
|
173
|
+
// callback so the caller can decide whether to decorate the group label (tty
|
|
174
|
+
// mode) or leave it plain (non-tty / NO_COLOR). Returns an array of lines;
|
|
175
|
+
// `null` entries mean "emit a blank line" so tree renderers can echo pipe
|
|
176
|
+
// characters on the separator rows.
|
|
177
|
+
function groupedCommandCatalogLines(indent = ' ', options = {}) {
|
|
178
|
+
const colorizeLabel = typeof options.colorizeLabel === 'function'
|
|
179
|
+
? options.colorizeLabel
|
|
180
|
+
: (text) => text;
|
|
181
|
+
const maxCommandLength = CLI_COMMAND_DESCRIPTIONS.reduce(
|
|
182
|
+
(max, [command]) => Math.max(max, command.length),
|
|
183
|
+
0,
|
|
184
|
+
);
|
|
185
|
+
const lines = [];
|
|
186
|
+
for (let groupIndex = 0; groupIndex < CLI_COMMAND_GROUPS.length; groupIndex += 1) {
|
|
187
|
+
const group = CLI_COMMAND_GROUPS[groupIndex];
|
|
188
|
+
const header = group.description
|
|
189
|
+
? `${colorizeLabel(group.label)} — ${group.description}`
|
|
190
|
+
: colorizeLabel(group.label);
|
|
191
|
+
lines.push(`${indent}${header}`);
|
|
192
|
+
for (const [command, description] of group.commands) {
|
|
193
|
+
lines.push(`${indent} ${command.padEnd(maxCommandLength + 2)}${description}`);
|
|
194
|
+
}
|
|
195
|
+
if (groupIndex < CLI_COMMAND_GROUPS.length - 1) {
|
|
196
|
+
lines.push(null);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return lines;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function quickstartLines(indent = ' ') {
|
|
203
|
+
return CLI_QUICKSTART_STEPS.map((step, index) => `${indent}${index + 1}. ${step}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
169
206
|
function agentBotCatalogLines(indent = ' ') {
|
|
170
207
|
const maxCommandLength = AGENT_BOT_DESCRIPTIONS.reduce(
|
|
171
208
|
(max, [command]) => Math.max(max, command.length),
|
|
@@ -182,19 +219,43 @@ function repoToggleLines(indent = ' ') {
|
|
|
182
219
|
];
|
|
183
220
|
}
|
|
184
221
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
const KNOWN_CLI_BASENAMES = new Set(['gx', 'gitguardex', 'guardex']);
|
|
223
|
+
|
|
224
|
+
function getInvokedCliName() {
|
|
225
|
+
const raw = path.basename(String(process.argv[1] || '')).replace(/\.js$/, '');
|
|
226
|
+
if (!KNOWN_CLI_BASENAMES.has(raw)) {
|
|
227
|
+
return SHORT_TOOL_NAME;
|
|
228
|
+
}
|
|
229
|
+
return raw;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function printToolLogsSummary(options = {}) {
|
|
233
|
+
const invoked = options.invokedBasename || getInvokedCliName();
|
|
234
|
+
const compact = Boolean(options.compact);
|
|
235
|
+
|
|
236
|
+
if (compact) {
|
|
237
|
+
const helpLine = `Try '${invoked} help' for commands, or '${invoked} status --verbose' for full service details.`;
|
|
238
|
+
console.log(`[${TOOL_NAME}] ${colorize(helpLine, '2')}`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const usageLine = ` $ ${invoked} <command> [options]`;
|
|
243
|
+
const quickstartDetails = quickstartLines(' ');
|
|
188
244
|
const agentBotDetails = agentBotCatalogLines(' ');
|
|
189
245
|
const repoToggleDetails = repoToggleLines(' ');
|
|
190
246
|
|
|
191
247
|
if (!supportsAnsiColors()) {
|
|
192
|
-
|
|
248
|
+
const commandDetails = groupedCommandCatalogLines(' ');
|
|
249
|
+
console.log(`${invoked} help:`);
|
|
193
250
|
console.log(' USAGE');
|
|
194
251
|
console.log(usageLine);
|
|
252
|
+
console.log(' QUICKSTART');
|
|
253
|
+
for (const line of quickstartDetails) {
|
|
254
|
+
console.log(line);
|
|
255
|
+
}
|
|
195
256
|
console.log(' COMMANDS');
|
|
196
257
|
for (const line of commandDetails) {
|
|
197
|
-
console.log(line);
|
|
258
|
+
console.log(line ?? '');
|
|
198
259
|
}
|
|
199
260
|
console.log(' AGENT BOT');
|
|
200
261
|
for (const line of agentBotDetails) {
|
|
@@ -204,24 +265,33 @@ function printToolLogsSummary() {
|
|
|
204
265
|
for (const line of repoToggleDetails) {
|
|
205
266
|
console.log(line);
|
|
206
267
|
}
|
|
268
|
+
console.log(` Try '${invoked} doctor' for one-step repair + verification.`);
|
|
207
269
|
return;
|
|
208
270
|
}
|
|
209
271
|
|
|
210
|
-
const title = colorize(`${
|
|
272
|
+
const title = colorize(`${invoked} help`, '1;36');
|
|
211
273
|
const usageHeader = colorize('USAGE', '1');
|
|
274
|
+
const quickstartHeader = colorize('QUICKSTART', '1');
|
|
212
275
|
const commandsHeader = colorize('COMMANDS', '1');
|
|
213
276
|
const agentBotHeader = colorize('AGENT BOT', '1');
|
|
214
277
|
const repoToggleHeader = colorize('REPO TOGGLE', '1');
|
|
215
278
|
const pipe = colorize('│', '90');
|
|
216
279
|
const tee = colorize('├', '90');
|
|
217
280
|
const corner = colorize('└', '90');
|
|
281
|
+
const commandDetails = groupedCommandCatalogLines(' ', {
|
|
282
|
+
colorizeLabel: (text) => colorize(text, '1;36'),
|
|
283
|
+
});
|
|
218
284
|
|
|
219
285
|
console.log(`${title}:`);
|
|
220
286
|
console.log(` ${tee}─ ${usageHeader}`);
|
|
221
287
|
console.log(` ${pipe}${usageLine}`);
|
|
288
|
+
console.log(` ${tee}─ ${quickstartHeader}`);
|
|
289
|
+
for (const line of quickstartDetails) {
|
|
290
|
+
console.log(` ${pipe}${line.slice(2)}`);
|
|
291
|
+
}
|
|
222
292
|
console.log(` ${tee}─ ${commandsHeader}`);
|
|
223
293
|
for (const line of commandDetails) {
|
|
224
|
-
if (
|
|
294
|
+
if (line == null) {
|
|
225
295
|
console.log(` ${pipe}`);
|
|
226
296
|
continue;
|
|
227
297
|
}
|
|
@@ -243,11 +313,18 @@ function printToolLogsSummary() {
|
|
|
243
313
|
}
|
|
244
314
|
console.log(` ${pipe}${line.slice(2)}`);
|
|
245
315
|
}
|
|
246
|
-
console.log(` ${corner}─ ${colorize(`Try '${
|
|
316
|
+
console.log(` ${corner}─ ${colorize(`Try '${invoked} doctor' for one-step repair + verification.`, '2')}`);
|
|
247
317
|
}
|
|
248
318
|
|
|
249
319
|
function usage(options = {}) {
|
|
250
320
|
const { outsideGitRepo = false } = options;
|
|
321
|
+
const invoked = options.invokedBasename || getInvokedCliName();
|
|
322
|
+
|
|
323
|
+
const groupedCommandLines = groupedCommandCatalogLines(' ', {
|
|
324
|
+
colorizeLabel: (text) => colorize(text, '1;36'),
|
|
325
|
+
})
|
|
326
|
+
.map((line) => (line == null ? '' : line))
|
|
327
|
+
.join('\n');
|
|
251
328
|
|
|
252
329
|
console.log(`A command-line tool that sets up hardened multi-agent safety for git repositories.
|
|
253
330
|
|
|
@@ -255,10 +332,13 @@ VERSION
|
|
|
255
332
|
${runtimeVersion()}
|
|
256
333
|
|
|
257
334
|
USAGE
|
|
258
|
-
$ ${
|
|
335
|
+
$ ${invoked} <command> [options]
|
|
336
|
+
|
|
337
|
+
QUICKSTART
|
|
338
|
+
${quickstartLines().join('\n')}
|
|
259
339
|
|
|
260
340
|
COMMANDS
|
|
261
|
-
${
|
|
341
|
+
${groupedCommandLines}
|
|
262
342
|
|
|
263
343
|
AGENT BOT
|
|
264
344
|
${agentBotCatalogLines().join('\n')}
|
|
@@ -267,19 +347,20 @@ REPO TOGGLE
|
|
|
267
347
|
${repoToggleLines().join('\n')}
|
|
268
348
|
|
|
269
349
|
NOTES
|
|
270
|
-
- No command = ${
|
|
350
|
+
- No command = ${invoked} status (compact in a TTY; pass --verbose for full services + help tree).
|
|
351
|
+
- ${invoked} init is an alias of ${invoked} setup.
|
|
271
352
|
- Global installs need Y/N approval; GitHub CLI (gh) is required for PR automation.
|
|
272
|
-
- Target another repo: ${
|
|
353
|
+
- Target another repo: ${invoked} <cmd> --target <repo-path>.
|
|
273
354
|
- On protected main, setup/install/fix/doctor auto-sandbox via agent branch + PR flow.
|
|
274
|
-
- Run '${
|
|
355
|
+
- Run '${invoked} cleanup' to prune merged agent branches/worktrees.
|
|
275
356
|
- Legacy aliases: ${LEGACY_NAMES.join(', ')}.`);
|
|
276
357
|
|
|
277
358
|
if (outsideGitRepo) {
|
|
278
359
|
console.log(`
|
|
279
360
|
[${TOOL_NAME}] No git repository detected in current directory.
|
|
280
361
|
[${TOOL_NAME}] Start from a repo root, or pass an explicit target:
|
|
281
|
-
${
|
|
282
|
-
${
|
|
362
|
+
${invoked} setup --target <path-to-git-repo>
|
|
363
|
+
${invoked} doctor --target <path-to-git-repo>`);
|
|
283
364
|
}
|
|
284
365
|
}
|
|
285
366
|
|
|
@@ -294,6 +375,59 @@ function formatElapsedDuration(ms) {
|
|
|
294
375
|
return `${Math.round(durationMs / 1000)}s`;
|
|
295
376
|
}
|
|
296
377
|
|
|
378
|
+
function startTransientSpinner(message, options = {}) {
|
|
379
|
+
const stream = options.stream || process.stdout;
|
|
380
|
+
if (!stream || !stream.isTTY || typeof stream.write !== 'function') {
|
|
381
|
+
return {
|
|
382
|
+
stop() {},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const frames = supportsAnsiColors()
|
|
387
|
+
? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
388
|
+
: ['-', '\\', '|', '/'];
|
|
389
|
+
const intervalMs = Number.isFinite(options.intervalMs) ? Math.max(60, options.intervalMs) : 80;
|
|
390
|
+
const prefix = String(options.prefix || `[${TOOL_NAME}]`).trim();
|
|
391
|
+
const text = String(message || '').trim();
|
|
392
|
+
let frameIndex = 0;
|
|
393
|
+
let stopped = false;
|
|
394
|
+
|
|
395
|
+
const render = () => {
|
|
396
|
+
const frame = frames[frameIndex % frames.length];
|
|
397
|
+
frameIndex += 1;
|
|
398
|
+
const indicator = supportsAnsiColors() ? colorize(frame, '36') : frame;
|
|
399
|
+
stream.write(`\r${prefix} ${indicator} ${text}`);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const clear = () => {
|
|
403
|
+
stream.write('\r');
|
|
404
|
+
if (typeof stream.clearLine === 'function') {
|
|
405
|
+
stream.clearLine(0);
|
|
406
|
+
}
|
|
407
|
+
if (typeof stream.cursorTo === 'function') {
|
|
408
|
+
stream.cursorTo(0);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
render();
|
|
413
|
+
const timer = setInterval(render, intervalMs);
|
|
414
|
+
if (typeof timer.unref === 'function') {
|
|
415
|
+
timer.unref();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
stop(finalLine = '') {
|
|
420
|
+
if (stopped) return;
|
|
421
|
+
stopped = true;
|
|
422
|
+
clearInterval(timer);
|
|
423
|
+
clear();
|
|
424
|
+
if (finalLine) {
|
|
425
|
+
stream.write(`${finalLine}\n`);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
297
431
|
function truncateMiddle(value, maxLength) {
|
|
298
432
|
const text = String(value || '');
|
|
299
433
|
const limit = Number.isFinite(maxLength) ? Math.max(4, maxLength) : 0;
|
|
@@ -454,8 +588,10 @@ module.exports = {
|
|
|
454
588
|
agentBotCatalogLines,
|
|
455
589
|
repoToggleLines,
|
|
456
590
|
printToolLogsSummary,
|
|
591
|
+
getInvokedCliName,
|
|
457
592
|
usage,
|
|
458
593
|
formatElapsedDuration,
|
|
594
|
+
startTransientSpinner,
|
|
459
595
|
truncateMiddle,
|
|
460
596
|
truncateTail,
|
|
461
597
|
compactAutoFinishPathSegments,
|
package/src/toolchain/index.js
CHANGED
|
@@ -545,6 +545,10 @@ function installGlobalToolchain(options) {
|
|
|
545
545
|
};
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
+
return performCompanionInstall(missingPackages, missingLocalTools);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function performCompanionInstall(missingPackages, missingLocalTools) {
|
|
548
552
|
const installed = [];
|
|
549
553
|
if (missingPackages.length > 0) {
|
|
550
554
|
console.log(
|
|
@@ -593,6 +597,7 @@ module.exports = {
|
|
|
593
597
|
formatGlobalToolchainServiceName,
|
|
594
598
|
describeMissingGlobalDependencyWarnings,
|
|
595
599
|
describeCompanionInstallCommands,
|
|
600
|
+
buildMissingCompanionInstallPrompt,
|
|
596
601
|
detectGlobalToolchainPackages,
|
|
597
602
|
detectRequiredSystemTools,
|
|
598
603
|
detectOptionalLocalCompanionTools,
|
|
@@ -600,4 +605,5 @@ module.exports = {
|
|
|
600
605
|
maybeSelfUpdateBeforeStatus,
|
|
601
606
|
maybeOpenSpecUpdateBeforeStatus,
|
|
602
607
|
installGlobalToolchain,
|
|
608
|
+
performCompanionInstall,
|
|
603
609
|
};
|
|
@@ -277,6 +277,10 @@ created_source_probe=0
|
|
|
277
277
|
source_probe_path=""
|
|
278
278
|
integration_worktree=""
|
|
279
279
|
integration_branch=""
|
|
280
|
+
merge_completed=0
|
|
281
|
+
merge_status="pr"
|
|
282
|
+
direct_push_error=""
|
|
283
|
+
pr_url=""
|
|
280
284
|
|
|
281
285
|
cleanup() {
|
|
282
286
|
if [[ -n "$integration_worktree" && -d "$integration_worktree" ]]; then
|
|
@@ -358,22 +362,6 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
|
|
|
358
362
|
fi
|
|
359
363
|
fi
|
|
360
364
|
|
|
361
|
-
integration_stamp="$(date +%Y%m%d-%H%M%S)"
|
|
362
|
-
integration_worktree_base="${temp_worktree_root}/__integrate-${BASE_BRANCH//\//__}-${integration_stamp}"
|
|
363
|
-
integration_branch_base="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
|
|
364
|
-
integration_worktree="$integration_worktree_base"
|
|
365
|
-
integration_branch="$integration_branch_base"
|
|
366
|
-
integration_suffix=1
|
|
367
|
-
while [[ -e "$integration_worktree" ]] || git -C "$repo_root" show-ref --verify --quiet "refs/heads/${integration_branch}"; do
|
|
368
|
-
integration_worktree="${integration_worktree_base}-${integration_suffix}"
|
|
369
|
-
integration_branch="${integration_branch_base}_${integration_suffix}"
|
|
370
|
-
integration_suffix=$((integration_suffix + 1))
|
|
371
|
-
done
|
|
372
|
-
mkdir -p "$(dirname "$integration_worktree")"
|
|
373
|
-
|
|
374
|
-
git -C "$repo_root" worktree add "$integration_worktree" "$start_ref" >/dev/null
|
|
375
|
-
git -C "$integration_worktree" checkout -b "$integration_branch" >/dev/null
|
|
376
|
-
|
|
377
365
|
if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
|
|
378
366
|
git -C "$source_worktree" fetch origin "$BASE_BRANCH" --quiet
|
|
379
367
|
|
|
@@ -395,16 +383,52 @@ if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRA
|
|
|
395
383
|
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
396
384
|
fi
|
|
397
385
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
exit 1
|
|
386
|
+
should_create_integration_helper=1
|
|
387
|
+
if [[ "$MERGE_MODE" == "pr" && "$PUSH_ENABLED" -eq 1 ]]; then
|
|
388
|
+
should_create_integration_helper=0
|
|
402
389
|
fi
|
|
403
390
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
391
|
+
if [[ "$should_create_integration_helper" -eq 1 ]]; then
|
|
392
|
+
existing_base_worktree=""
|
|
393
|
+
if [[ "$PUSH_ENABLED" -eq 0 ]]; then
|
|
394
|
+
existing_base_worktree="$(get_worktree_for_branch "$BASE_BRANCH")"
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
if [[ -n "$existing_base_worktree" ]] && is_clean_worktree "$existing_base_worktree"; then
|
|
398
|
+
if ! git -C "$existing_base_worktree" merge --no-ff --no-edit "$SOURCE_BRANCH"; then
|
|
399
|
+
echo "[agent-branch-finish] Merge conflict detected while merging '${SOURCE_BRANCH}' into '${BASE_BRANCH}'." >&2
|
|
400
|
+
git -C "$existing_base_worktree" merge --abort >/dev/null 2>&1 || true
|
|
401
|
+
exit 1
|
|
402
|
+
fi
|
|
403
|
+
merge_completed=1
|
|
404
|
+
merge_status="direct"
|
|
405
|
+
else
|
|
406
|
+
integration_stamp="$(date +%Y%m%d-%H%M%S)"
|
|
407
|
+
integration_worktree_base="${temp_worktree_root}/__integrate-${BASE_BRANCH//\//__}-${integration_stamp}"
|
|
408
|
+
integration_branch_base="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
|
|
409
|
+
integration_worktree="$integration_worktree_base"
|
|
410
|
+
integration_branch="$integration_branch_base"
|
|
411
|
+
integration_suffix=1
|
|
412
|
+
while [[ -e "$integration_worktree" ]] || git -C "$repo_root" show-ref --verify --quiet "refs/heads/${integration_branch}"; do
|
|
413
|
+
integration_worktree="${integration_worktree_base}-${integration_suffix}"
|
|
414
|
+
integration_branch="${integration_branch_base}_${integration_suffix}"
|
|
415
|
+
integration_suffix=$((integration_suffix + 1))
|
|
416
|
+
done
|
|
417
|
+
mkdir -p "$(dirname "$integration_worktree")"
|
|
418
|
+
|
|
419
|
+
git -C "$repo_root" worktree add "$integration_worktree" "$start_ref" >/dev/null
|
|
420
|
+
git -C "$integration_worktree" checkout -b "$integration_branch" >/dev/null
|
|
421
|
+
|
|
422
|
+
if ! git -C "$integration_worktree" merge --no-ff --no-edit "$SOURCE_BRANCH"; then
|
|
423
|
+
echo "[agent-branch-finish] Merge conflict detected while merging '${SOURCE_BRANCH}' into '${BASE_BRANCH}'." >&2
|
|
424
|
+
git -C "$integration_worktree" merge --abort >/dev/null 2>&1 || true
|
|
425
|
+
exit 1
|
|
426
|
+
fi
|
|
427
|
+
|
|
428
|
+
merge_completed=1
|
|
429
|
+
merge_status="direct"
|
|
430
|
+
fi
|
|
431
|
+
fi
|
|
408
432
|
|
|
409
433
|
is_local_branch_delete_error() {
|
|
410
434
|
local output="$1"
|
|
@@ -340,16 +340,30 @@ resolve_openspec_capability_slug() {
|
|
|
340
340
|
sanitize_slug "$task_slug" "general-behavior"
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
resolve_repo_prefix() {
|
|
344
|
+
local root
|
|
345
|
+
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
346
|
+
basename "$root"
|
|
347
|
+
}
|
|
348
|
+
|
|
343
349
|
resolve_worktree_leaf() {
|
|
344
350
|
local branch_name="$1"
|
|
345
351
|
local agent_slug="$2"
|
|
346
352
|
local masterplan_label=""
|
|
347
353
|
local branch_leaf=""
|
|
354
|
+
local repo_prefix
|
|
355
|
+
repo_prefix="$(resolve_repo_prefix)"
|
|
348
356
|
|
|
349
357
|
masterplan_label="$(resolve_openspec_masterplan_label)"
|
|
350
358
|
if [[ -n "$masterplan_label" ]] && [[ "$branch_name" == "agent/${agent_slug}/"* ]]; then
|
|
351
359
|
branch_leaf="${branch_name#agent/${agent_slug}/}"
|
|
352
|
-
printf '
|
|
360
|
+
printf '%s__%s__%s__%s' "$repo_prefix" "$agent_slug" "$masterplan_label" "$branch_leaf"
|
|
361
|
+
return 0
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
if [[ "$branch_name" == agent/*/* ]]; then
|
|
365
|
+
local without_agent="${branch_name#agent/}"
|
|
366
|
+
printf '%s__%s' "$repo_prefix" "${without_agent//\//__}"
|
|
353
367
|
return 0
|
|
354
368
|
fi
|
|
355
369
|
|
|
@@ -370,6 +384,19 @@ has_local_changes() {
|
|
|
370
384
|
return 1
|
|
371
385
|
}
|
|
372
386
|
|
|
387
|
+
resolve_stash_ref_by_message() {
|
|
388
|
+
local root="$1"
|
|
389
|
+
local message="$2"
|
|
390
|
+
local stash_list
|
|
391
|
+
stash_list="$(git -C "$root" stash list 2>/dev/null || true)"
|
|
392
|
+
if [[ -z "$stash_list" ]]; then
|
|
393
|
+
printf ''
|
|
394
|
+
return 0
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
awk -v msg="$message" '$0 ~ msg { ref=$1; sub(/:$/, "", ref); print ref; exit }' <<<"$stash_list"
|
|
398
|
+
}
|
|
399
|
+
|
|
373
400
|
resolve_protected_branches() {
|
|
374
401
|
local root="$1"
|
|
375
402
|
local raw
|
|
@@ -616,10 +643,7 @@ if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_bra
|
|
|
616
643
|
if has_local_changes "$repo_root"; then
|
|
617
644
|
auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
|
|
618
645
|
if git -C "$repo_root" stash push --include-untracked --message "$auto_transfer_message" >/dev/null 2>&1; then
|
|
619
|
-
auto_transfer_stash_ref="$(
|
|
620
|
-
git -C "$repo_root" stash list \
|
|
621
|
-
| awk -v msg="$auto_transfer_message" '$0 ~ msg { ref=$1; sub(/:$/, "", ref); print ref; exit }'
|
|
622
|
-
)"
|
|
646
|
+
auto_transfer_stash_ref="$(resolve_stash_ref_by_message "$repo_root" "$auto_transfer_message")"
|
|
623
647
|
if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
624
648
|
auto_transfer_source_branch="$current_branch"
|
|
625
649
|
echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}'..."
|
|
@@ -372,17 +372,32 @@ resolve_openspec_capability_slug() {
|
|
|
372
372
|
sanitize_slug "$task_slug" "general-behavior"
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
resolve_repo_prefix() {
|
|
376
|
+
local root
|
|
377
|
+
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
378
|
+
basename "$root"
|
|
379
|
+
}
|
|
380
|
+
|
|
375
381
|
resolve_worktree_leaf() {
|
|
376
382
|
local branch_name="$1"
|
|
377
383
|
local masterplan_label=""
|
|
378
384
|
local branch_role=""
|
|
379
385
|
local branch_leaf=""
|
|
386
|
+
local repo_prefix
|
|
387
|
+
repo_prefix="$(resolve_repo_prefix)"
|
|
380
388
|
|
|
381
389
|
masterplan_label="$(resolve_openspec_masterplan_label)"
|
|
382
390
|
if [[ -n "$masterplan_label" ]] && [[ "$branch_name" =~ ^agent/([^/]+)/(.+)$ ]]; then
|
|
383
391
|
branch_role="${BASH_REMATCH[1]}"
|
|
384
392
|
branch_leaf="${BASH_REMATCH[2]}"
|
|
385
|
-
printf '
|
|
393
|
+
printf '%s__%s__%s__%s' "$repo_prefix" "$branch_role" "$masterplan_label" "$branch_leaf"
|
|
394
|
+
return 0
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
if [[ "$branch_name" =~ ^agent/([^/]+)/(.+)$ ]]; then
|
|
398
|
+
branch_role="${BASH_REMATCH[1]}"
|
|
399
|
+
branch_leaf="${BASH_REMATCH[2]}"
|
|
400
|
+
printf '%s__%s__%s' "$repo_prefix" "$branch_role" "$branch_leaf"
|
|
386
401
|
return 0
|
|
387
402
|
fi
|
|
388
403
|
|