@imdeadpool/guardex 7.0.38 → 7.0.39
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 +6 -0
- package/package.json +1 -1
- package/src/cli/main.js +4 -3
- package/src/sandbox/index.js +3 -2
- package/templates/scripts/agent-branch-finish.sh +2 -2
- package/templates/scripts/agent-branch-merge.sh +1 -1
- package/templates/scripts/agent-branch-start.sh +35 -2
- package/templates/vscode/guardex-active-agents/extension.js +111 -1
- package/templates/vscode/guardex-active-agents/package.json +1 -1
package/README.md
CHANGED
|
@@ -266,6 +266,12 @@ Being honest about where this still has issues:
|
|
|
266
266
|
<details open>
|
|
267
267
|
<summary><strong>v7.x</strong></summary>
|
|
268
268
|
|
|
269
|
+
### v7.0.39
|
|
270
|
+
- Bumped `@imdeadpool/guardex` from `7.0.38` to `7.0.39` so the current
|
|
271
|
+
`main` payload can publish under a fresh npm version after `7.0.38` reached
|
|
272
|
+
the registry.
|
|
273
|
+
- No new CLI command behavior is introduced in this release lane.
|
|
274
|
+
|
|
269
275
|
### v7.0.38
|
|
270
276
|
- Bumped `@imdeadpool/guardex` from `7.0.37` to `7.0.38` so the current
|
|
271
277
|
`main` payload can publish under a fresh npm version after `7.0.37` reached
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.39",
|
|
4
4
|
"description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"preferGlobal": true,
|
package/src/cli/main.js
CHANGED
|
@@ -408,8 +408,9 @@ function runSetupBootstrapInternal(options) {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
function extractAgentBranchStartMetadata(output) {
|
|
411
|
-
const
|
|
412
|
-
const
|
|
411
|
+
const outputText = String(output || '');
|
|
412
|
+
const branchMatch = outputText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch): (.+)$/m);
|
|
413
|
+
const worktreeMatch = outputText.match(/^\[agent-branch-start\] Worktree: (.+)$/m);
|
|
413
414
|
return {
|
|
414
415
|
branch: branchMatch ? branchMatch[1].trim() : '',
|
|
415
416
|
worktreePath: worktreeMatch ? worktreeMatch[1].trim() : '',
|
|
@@ -3466,7 +3467,7 @@ function pivot(rawArgs) {
|
|
|
3466
3467
|
}
|
|
3467
3468
|
const stdoutText = String(result.stdout || '');
|
|
3468
3469
|
const wtMatch = stdoutText.match(/^\[agent-branch-start\] Worktree:\s+(.+)$/m);
|
|
3469
|
-
const branchMatch = stdoutText.match(/^\[agent-branch-start\] Created branch:\s+(.+)$/m);
|
|
3470
|
+
const branchMatch = stdoutText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch):\s+(.+)$/m);
|
|
3470
3471
|
if (wtMatch) {
|
|
3471
3472
|
const wtPath = wtMatch[1].trim();
|
|
3472
3473
|
process.stdout.write('\n');
|
package/src/sandbox/index.js
CHANGED
|
@@ -65,8 +65,9 @@ function assertProtectedMainWriteAllowed(options, commandName) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function extractAgentBranchStartMetadata(output) {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
68
|
+
const outputText = String(output || '');
|
|
69
|
+
const branchMatch = outputText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch): (.+)$/m);
|
|
70
|
+
const worktreeMatch = outputText.match(/^\[agent-branch-start\] Worktree: (.+)$/m);
|
|
70
71
|
return {
|
|
71
72
|
branch: branchMatch ? branchMatch[1].trim() : '',
|
|
72
73
|
worktreePath: worktreeMatch ? worktreeMatch[1].trim() : '',
|
|
@@ -508,7 +508,7 @@ is_local_branch_delete_error() {
|
|
|
508
508
|
|
|
509
509
|
is_remote_branch_missing_error() {
|
|
510
510
|
local output="$1"
|
|
511
|
-
if [[ "$output" == *"remote ref does not exist"* ]]
|
|
511
|
+
if [[ "$output" == *"remote ref does not exist"* ]]; then
|
|
512
512
|
return 0
|
|
513
513
|
fi
|
|
514
514
|
return 1
|
|
@@ -893,8 +893,8 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
893
893
|
if is_remote_branch_missing_error "$remote_delete_output"; then
|
|
894
894
|
echo "[agent-branch-finish] Remote branch '${SOURCE_BRANCH}' was already deleted; continuing cleanup." >&2
|
|
895
895
|
else
|
|
896
|
+
echo "[agent-branch-finish] Warning: remote branch cleanup failed for '${SOURCE_BRANCH}' after merge; continuing local cleanup." >&2
|
|
896
897
|
echo "$remote_delete_output" >&2
|
|
897
|
-
exit 1
|
|
898
898
|
fi
|
|
899
899
|
fi
|
|
900
900
|
fi
|
|
@@ -288,7 +288,7 @@ if [[ -z "$TARGET_BRANCH" ]]; then
|
|
|
288
288
|
fi
|
|
289
289
|
|
|
290
290
|
printf '%s\n' "$start_output"
|
|
291
|
-
TARGET_BRANCH="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Created branch: //p' | head -n 1)"
|
|
291
|
+
TARGET_BRANCH="$(printf '%s\n' "$start_output" | sed -n -E 's/^\[agent-branch-start\] (Created branch|Reusing existing branch): //p' | head -n 1)"
|
|
292
292
|
target_worktree="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | head -n 1)"
|
|
293
293
|
if [[ -z "$TARGET_BRANCH" || -z "$target_worktree" ]]; then
|
|
294
294
|
echo "[agent-branch-merge] Unable to parse target branch/worktree from agent-branch-start output." >&2
|
|
@@ -15,6 +15,7 @@ OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
|
15
15
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
16
16
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
17
17
|
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T3}"
|
|
18
|
+
REUSE_EXISTING_RAW="${GUARDEX_BRANCH_START_REUSE_EXISTING:-true}"
|
|
18
19
|
PRINT_NAME_ONLY=0
|
|
19
20
|
POSITIONAL_ARGS=()
|
|
20
21
|
|
|
@@ -58,6 +59,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
58
59
|
OPENSPEC_TIER_RAW="${2:-$OPENSPEC_TIER_RAW}"
|
|
59
60
|
shift 2
|
|
60
61
|
;;
|
|
62
|
+
--reuse-existing|--reuse)
|
|
63
|
+
REUSE_EXISTING_RAW="true"
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
66
|
+
--new|--no-reuse|--no-reuse-existing)
|
|
67
|
+
REUSE_EXISTING_RAW="false"
|
|
68
|
+
shift
|
|
69
|
+
;;
|
|
61
70
|
--in-place|--allow-in-place)
|
|
62
71
|
echo "[agent-branch-start] In-place branch mode is disabled." >&2
|
|
63
72
|
echo "[agent-branch-start] This command always creates an isolated worktree to keep your active checkout unchanged." >&2
|
|
@@ -78,7 +87,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
78
87
|
;;
|
|
79
88
|
-*)
|
|
80
89
|
echo "[agent-branch-start] Unknown option: $1" >&2
|
|
81
|
-
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>] [--print-name-only]" >&2
|
|
90
|
+
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>] [--reuse-existing|--new] [--print-name-only]" >&2
|
|
82
91
|
exit 1
|
|
83
92
|
;;
|
|
84
93
|
*)
|
|
@@ -90,7 +99,7 @@ done
|
|
|
90
99
|
|
|
91
100
|
if [[ "${#POSITIONAL_ARGS[@]}" -gt 3 ]]; then
|
|
92
101
|
echo "[agent-branch-start] Too many positional arguments." >&2
|
|
93
|
-
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>]" >&2
|
|
102
|
+
echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>] [--reuse-existing|--new]" >&2
|
|
94
103
|
exit 1
|
|
95
104
|
fi
|
|
96
105
|
|
|
@@ -254,6 +263,7 @@ normalize_bool() {
|
|
|
254
263
|
}
|
|
255
264
|
|
|
256
265
|
OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
|
|
266
|
+
REUSE_EXISTING_WORKTREE="$(normalize_bool "$REUSE_EXISTING_RAW" "1")"
|
|
257
267
|
|
|
258
268
|
normalize_tier() {
|
|
259
269
|
local raw="${1:-}"
|
|
@@ -370,6 +380,22 @@ resolve_worktree_leaf() {
|
|
|
370
380
|
printf '%s' "${branch_name//\//__}"
|
|
371
381
|
}
|
|
372
382
|
|
|
383
|
+
print_reused_agent_worktree() {
|
|
384
|
+
local branch_name="$1"
|
|
385
|
+
local worktree_path="$2"
|
|
386
|
+
|
|
387
|
+
echo "[agent-branch-start] Reusing existing branch: ${branch_name}"
|
|
388
|
+
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
389
|
+
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
390
|
+
echo "[agent-branch-start] OpenSpec change: existing worktree"
|
|
391
|
+
echo "[agent-branch-start] OpenSpec plan: existing worktree"
|
|
392
|
+
echo "[agent-branch-start] Next steps:"
|
|
393
|
+
echo " cd \"${worktree_path}\""
|
|
394
|
+
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
395
|
+
echo " # continue work in this existing sandbox"
|
|
396
|
+
echo " gx branch finish --branch \"${branch_name}\" --via-pr --wait-for-merge"
|
|
397
|
+
}
|
|
398
|
+
|
|
373
399
|
has_local_changes() {
|
|
374
400
|
local root="$1"
|
|
375
401
|
if ! git -C "$root" diff --quiet; then
|
|
@@ -550,6 +576,12 @@ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
|
|
|
550
576
|
exit 1
|
|
551
577
|
fi
|
|
552
578
|
|
|
579
|
+
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
580
|
+
if [[ "$REUSE_EXISTING_WORKTREE" -eq 1 && "$current_branch" == agent/* ]]; then
|
|
581
|
+
print_reused_agent_worktree "$current_branch" "$repo_root"
|
|
582
|
+
exit 0
|
|
583
|
+
fi
|
|
584
|
+
|
|
553
585
|
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
554
586
|
agent_slug="$(normalize_role "$AGENT_NAME")"
|
|
555
587
|
if [[ "$WORKTREE_ROOT_EXPLICIT" -eq 0 ]]; then
|
|
@@ -681,6 +713,7 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
|
681
713
|
fi
|
|
682
714
|
fi
|
|
683
715
|
|
|
716
|
+
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" ".venv"
|
|
684
717
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
|
|
685
718
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
|
|
686
719
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
|
|
@@ -2395,6 +2395,74 @@ function isPathWithin(parentPath, targetPath) {
|
|
|
2395
2395
|
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
2396
2396
|
}
|
|
2397
2397
|
|
|
2398
|
+
function normalizeAbsolutePath(value) {
|
|
2399
|
+
return typeof value === 'string' && value.trim() ? path.resolve(value) : '';
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function isManagedWorktreePath(worktreePath) {
|
|
2403
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
2404
|
+
if (!normalizedWorktreePath) {
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
return MANAGED_WORKTREE_RELATIVE_ROOTS.some((relativeRoot) => {
|
|
2409
|
+
const normalizedRelativeRoot = path.normalize(relativeRoot);
|
|
2410
|
+
const marker = `${path.sep}${normalizedRelativeRoot}${path.sep}`;
|
|
2411
|
+
return normalizedWorktreePath.includes(marker);
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
function removeDeletedWorktreeWorkspaceFolder(worktreePath) {
|
|
2416
|
+
if (typeof vscode.workspace.updateWorkspaceFolders !== 'function') {
|
|
2417
|
+
return false;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
2421
|
+
if (!normalizedWorktreePath) {
|
|
2422
|
+
return false;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
const workspaceFolders = vscode.workspace.workspaceFolders || [];
|
|
2426
|
+
const folderIndex = workspaceFolders.findIndex((folder) => (
|
|
2427
|
+
normalizeAbsolutePath(folder?.uri?.fsPath) === normalizedWorktreePath
|
|
2428
|
+
));
|
|
2429
|
+
if (folderIndex < 0) {
|
|
2430
|
+
return false;
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
try {
|
|
2434
|
+
return vscode.workspace.updateWorkspaceFolders(folderIndex, 1) === true;
|
|
2435
|
+
} catch (_error) {
|
|
2436
|
+
return false;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
async function closeDeletedWorktreeRepository(worktreePath) {
|
|
2441
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
2442
|
+
if (!normalizedWorktreePath || fs.existsSync(normalizedWorktreePath)) {
|
|
2443
|
+
return false;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
try {
|
|
2447
|
+
await vscode.commands.executeCommand('git.close', vscode.Uri.file(normalizedWorktreePath));
|
|
2448
|
+
} catch (_error) {
|
|
2449
|
+
// The Git extension may have already removed this repository.
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
removeDeletedWorktreeWorkspaceFolder(normalizedWorktreePath);
|
|
2453
|
+
return true;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
function findDeletedManagedWorkspaceFolders() {
|
|
2457
|
+
return (vscode.workspace.workspaceFolders || [])
|
|
2458
|
+
.map((folder) => normalizeAbsolutePath(folder?.uri?.fsPath))
|
|
2459
|
+
.filter((workspacePath) => (
|
|
2460
|
+
workspacePath
|
|
2461
|
+
&& !fs.existsSync(workspacePath)
|
|
2462
|
+
&& isManagedWorktreePath(workspacePath)
|
|
2463
|
+
));
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2398
2466
|
function localizeChangeForSession(session, change) {
|
|
2399
2467
|
if (!change?.absolutePath || !isPathWithin(session.worktreePath, change.absolutePath)) {
|
|
2400
2468
|
return null;
|
|
@@ -3434,6 +3502,8 @@ class ActiveAgentsRefreshController {
|
|
|
3434
3502
|
this.inspectPanelManager = inspectPanelManager;
|
|
3435
3503
|
this.refreshTimer = null;
|
|
3436
3504
|
this.sessionWatchers = new Map();
|
|
3505
|
+
this.closedMissingWorktreeRepositories = new Set();
|
|
3506
|
+
this.observedWorktreePaths = new Set();
|
|
3437
3507
|
}
|
|
3438
3508
|
|
|
3439
3509
|
scheduleRefresh() {
|
|
@@ -3456,8 +3526,23 @@ class ActiveAgentsRefreshController {
|
|
|
3456
3526
|
const repoEntries = await findRepoSessionEntries();
|
|
3457
3527
|
const liveSessionKeys = new Set();
|
|
3458
3528
|
|
|
3529
|
+
for (const workspacePath of findDeletedManagedWorkspaceFolders()) {
|
|
3530
|
+
await this.closeMissingWorktreeRepository(workspacePath);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3459
3533
|
for (const entry of repoEntries) {
|
|
3460
3534
|
for (const session of entry.sessions) {
|
|
3535
|
+
const worktreePath = sessionWorktreePath(session);
|
|
3536
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
3537
|
+
if (normalizedWorktreePath && !fs.existsSync(normalizedWorktreePath)) {
|
|
3538
|
+
await this.closeMissingWorktreeRepository(normalizedWorktreePath);
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
if (normalizedWorktreePath) {
|
|
3542
|
+
this.closedMissingWorktreeRepositories.delete(normalizedWorktreePath);
|
|
3543
|
+
this.observedWorktreePaths.add(normalizedWorktreePath);
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3461
3546
|
const sessionKey = resolveSessionWatcherKey(session);
|
|
3462
3547
|
liveSessionKeys.add(sessionKey);
|
|
3463
3548
|
if (this.sessionWatchers.has(sessionKey)) {
|
|
@@ -3468,8 +3553,20 @@ class ActiveAgentsRefreshController {
|
|
|
3468
3553
|
resolveSessionGitIndexPath(session.worktreePath),
|
|
3469
3554
|
);
|
|
3470
3555
|
const disposables = bindRefreshWatcher(watcher, () => this.scheduleRefresh());
|
|
3471
|
-
this.sessionWatchers.set(sessionKey, {
|
|
3556
|
+
this.sessionWatchers.set(sessionKey, {
|
|
3557
|
+
watcher,
|
|
3558
|
+
disposables,
|
|
3559
|
+
worktreePath: normalizedWorktreePath,
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
for (const observedWorktreePath of this.observedWorktreePaths) {
|
|
3565
|
+
if (fs.existsSync(observedWorktreePath)) {
|
|
3566
|
+
this.closedMissingWorktreeRepositories.delete(observedWorktreePath);
|
|
3567
|
+
continue;
|
|
3472
3568
|
}
|
|
3569
|
+
await this.closeMissingWorktreeRepository(observedWorktreePath);
|
|
3473
3570
|
}
|
|
3474
3571
|
|
|
3475
3572
|
for (const [sessionKey, entry] of this.sessionWatchers) {
|
|
@@ -3477,12 +3574,25 @@ class ActiveAgentsRefreshController {
|
|
|
3477
3574
|
continue;
|
|
3478
3575
|
}
|
|
3479
3576
|
|
|
3577
|
+
if (entry.worktreePath && !fs.existsSync(entry.worktreePath)) {
|
|
3578
|
+
await this.closeMissingWorktreeRepository(entry.worktreePath);
|
|
3579
|
+
}
|
|
3480
3580
|
disposeAll(entry.disposables);
|
|
3481
3581
|
entry.watcher.dispose();
|
|
3482
3582
|
this.sessionWatchers.delete(sessionKey);
|
|
3483
3583
|
}
|
|
3484
3584
|
}
|
|
3485
3585
|
|
|
3586
|
+
async closeMissingWorktreeRepository(worktreePath) {
|
|
3587
|
+
const normalizedWorktreePath = normalizeAbsolutePath(worktreePath);
|
|
3588
|
+
if (!normalizedWorktreePath || this.closedMissingWorktreeRepositories.has(normalizedWorktreePath)) {
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
this.closedMissingWorktreeRepositories.add(normalizedWorktreePath);
|
|
3593
|
+
await closeDeletedWorktreeRepository(normalizedWorktreePath);
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3486
3596
|
dispose() {
|
|
3487
3597
|
if (this.refreshTimer) {
|
|
3488
3598
|
clearTimeout(this.refreshTimer);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "GitGuardex Active Agents",
|
|
4
4
|
"description": "Shows live Guardex sandbox sessions and repo changes in a dedicated VS Code Active Agents sidebar.",
|
|
5
5
|
"publisher": "Recodee",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.21",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"icon": "icon.png",
|
|
9
9
|
"engines": {
|