@imdeadpool/guardex 7.0.16 → 7.0.19
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/CONTRIBUTING.md +1 -1
- package/README.md +187 -53
- package/bin/multiagent-safety.js +863 -220
- package/package.json +2 -2
- package/templates/AGENTS.multiagent-safety.md +7 -4
- package/templates/codex/skills/gitguardex/SKILL.md +1 -1
- package/templates/codex/skills/guardex-merge-skills-to-dev/SKILL.md +3 -3
- package/templates/githooks/post-checkout +1 -1
- package/templates/githooks/post-merge +19 -6
- package/templates/githooks/pre-commit +29 -10
- package/templates/scripts/agent-branch-finish.sh +32 -19
- package/templates/scripts/agent-branch-merge.sh +24 -5
- package/templates/scripts/agent-branch-start.sh +23 -29
- package/templates/scripts/agent-file-locks.py +11 -11
- package/templates/scripts/agent-session-state.js +110 -0
- package/templates/scripts/codex-agent.sh +113 -54
- package/templates/scripts/install-vscode-active-agents-extension.js +92 -0
- package/templates/scripts/openspec/init-change-workspace.sh +77 -9
- package/templates/scripts/openspec/init-plan-workspace.sh +576 -74
- package/templates/scripts/review-bot-watch.sh +30 -7
- package/templates/vscode/guardex-active-agents/README.md +22 -0
- package/templates/vscode/guardex-active-agents/extension.js +357 -0
- package/templates/vscode/guardex-active-agents/package.json +57 -0
- package/templates/vscode/guardex-active-agents/session-schema.js +407 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "7.0.19",
|
|
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,
|
|
7
7
|
"bin": {
|
|
@@ -7,22 +7,25 @@
|
|
|
7
7
|
`GUARDEX_ON=0` disables Guardex for that repo.
|
|
8
8
|
`GUARDEX_ON=1` explicitly enables Guardex for that repo again.
|
|
9
9
|
|
|
10
|
-
**
|
|
10
|
+
**Task-size routing.** Small tasks stay in direct caveman-only mode. For typos, single-file tweaks, one-liners, version bumps, or similarly bounded asks, solve directly and do not escalate into heavy OMX orchestration just because a keyword appears. Treat `quick:`, `simple:`, `tiny:`, `minor:`, `small:`, `just:`, and `only:` as explicit lightweight escape hatches.
|
|
11
|
+
Promote to OMX orchestration only when the task is medium/large: multi-file behavior changes, API/schema work, refactors, migrations, architecture, cross-cutting scope, or long prompts. Heavy OMX modes (`ralph`, `autopilot`, `team`, `ultrawork`, `swarm`, `ralplan`) are for that larger scope. If the task grows while working, upgrade then.
|
|
12
|
+
|
|
13
|
+
**Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `gx branch start "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. Never `git checkout <branch>` on a primary working tree (including nested repos); use `git worktree add` instead. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions - bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
|
|
11
14
|
For every new task, including follow-up work in the same chat/session, if an assigned agent sub-branch/worktree is already open, continue in that sub-branch instead of creating a fresh lane unless the user explicitly redirects scope.
|
|
12
15
|
Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
13
16
|
|
|
14
|
-
**Ownership.** Before editing, claim files: `
|
|
17
|
+
**Ownership.** Before editing, claim files: `gx locks claim --branch "<agent-branch>" <file...>`. Before deleting, confirm the path is in your claim. Don't edit outside your scope unless reassigned.
|
|
15
18
|
|
|
16
19
|
**Handoff gate.** Post a one-line handoff note (plan/change, owned scope, intended action) before editing. Re-read the latest handoffs before replacing others' code.
|
|
17
20
|
|
|
18
|
-
**Completion.** Finish with `
|
|
21
|
+
**Completion.** Finish with `gx branch finish --branch "<agent-branch>" --via-pr --wait-for-merge --cleanup` (or `gx finish --all`). Task is only complete when: commit pushed, PR URL recorded, state = `MERGED`, sandbox worktree pruned. If anything blocks, append a `BLOCKED:` note and stop - don't half-finish.
|
|
19
22
|
OMX completion policy: when a task is done, the agent must commit the task changes, push the agent branch, and create/update a PR before considering the branch complete.
|
|
20
23
|
|
|
21
24
|
**Parallel safety.** Assume other agents edit nearby. Never revert unrelated changes. Report conflicts in the handoff.
|
|
22
25
|
|
|
23
26
|
**Reporting.** Every completion handoff includes: files changed, behavior touched, verification commands + results, risks/follow-ups.
|
|
24
27
|
|
|
25
|
-
**OpenSpec (when change-driven).** Keep `openspec/changes/<slug>/tasks.md` checkboxes current during work, not batched at the end. Task scaffolds and manual task edits must include an explicit final completion/cleanup section that ends with PR merge + sandbox cleanup (`gx finish --via-pr --wait-for-merge --cleanup` or `
|
|
28
|
+
**OpenSpec (when change-driven).** Keep `openspec/changes/<slug>/tasks.md` checkboxes current during work, not batched at the end. Task scaffolds and manual task edits must include an explicit final completion/cleanup section that ends with PR merge + sandbox cleanup (`gx finish --via-pr --wait-for-merge --cleanup` or `gx branch finish ... --cleanup`) and records PR URL + final `MERGED` evidence. Verify specs with `openspec validate --specs` before archive. Don't archive unverified.
|
|
26
29
|
|
|
27
30
|
**Version bumps.** If a change bumps a published version, the same PR updates release notes/changelog.
|
|
28
31
|
<!-- multiagent-safety:END -->
|
|
@@ -8,4 +8,4 @@ Use when repo safety may be broken.
|
|
|
8
8
|
`gx status` -> `gx doctor` -> `gx status --strict`
|
|
9
9
|
|
|
10
10
|
Bootstrap: `gx setup`
|
|
11
|
-
Ops: `
|
|
11
|
+
Ops: `gx branch start "<task>" "<agent>"`, `gx locks claim --branch "<agent-branch>" <file...>`, `gx branch finish --branch "<agent-branch>" --base <base> --via-pr --wait-for-merge --cleanup`, `gx finish --all`, `gx cleanup`
|
|
@@ -24,7 +24,7 @@ echo "$BASE_BRANCH"
|
|
|
24
24
|
2. Start a dedicated integration sandbox from base:
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
|
|
27
|
+
gx branch start "merge-skill-files-to-${BASE_BRANCH}" "skill-merge" "$BASE_BRANCH"
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
3. Enter the sandbox worktree printed by the command above.
|
|
@@ -48,11 +48,11 @@ git diff --name-only
|
|
|
48
48
|
```sh
|
|
49
49
|
git add .codex/skills templates/codex/skills
|
|
50
50
|
git commit -m "Merge skill file updates into ${BASE_BRANCH}"
|
|
51
|
-
|
|
51
|
+
gx branch finish --branch "$(git rev-parse --abbrev-ref HEAD)" --base "$BASE_BRANCH" --via-pr --wait-for-merge --cleanup
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
## Notes
|
|
55
55
|
|
|
56
56
|
- If a source branch has non-skill changes, this runbook keeps them out of the merge.
|
|
57
|
-
- If merge conflicts occur, resolve only within the skill files, then rerun `
|
|
57
|
+
- If merge conflicts occur, resolve only within the skill files, then rerun `gx branch finish`.
|
|
58
58
|
- Do not commit directly on `dev`/`main`; always merge through an agent branch/worktree.
|
|
@@ -64,7 +64,7 @@ echo "[agent-primary-branch-guard] Primary checkout switched branches." >&2
|
|
|
64
64
|
echo "[agent-primary-branch-guard] from: $prev_branch (protected)" >&2
|
|
65
65
|
echo "[agent-primary-branch-guard] to: $new_branch" >&2
|
|
66
66
|
echo "[agent-primary-branch-guard] The primary working tree must stay on its base/protected branch." >&2
|
|
67
|
-
echo "[agent-primary-branch-guard] Use 'git worktree add' (or
|
|
67
|
+
echo "[agent-primary-branch-guard] Use 'git worktree add' (or gx branch start) for feature work." >&2
|
|
68
68
|
|
|
69
69
|
if [[ "$is_agent" == "1" ]]; then
|
|
70
70
|
echo "[agent-primary-branch-guard] Agent session detected — reverting to '$prev_branch'." >&2
|
|
@@ -32,17 +32,30 @@ if [[ "$branch" != "$base_branch" ]]; then
|
|
|
32
32
|
exit 0
|
|
33
33
|
fi
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if [[ -n "${GUARDEX_CLI_ENTRY:-}" ]]; then
|
|
36
|
+
node_bin="${GUARDEX_NODE_BIN:-node}"
|
|
37
|
+
if command -v "$node_bin" >/dev/null 2>&1; then
|
|
38
|
+
"$node_bin" "$GUARDEX_CLI_ENTRY" cleanup \
|
|
39
|
+
--target "$repo_root" \
|
|
40
|
+
--base "$base_branch" \
|
|
41
|
+
--include-pr-merged \
|
|
42
|
+
--keep-clean-worktrees >/dev/null 2>&1 || true
|
|
43
|
+
fi
|
|
37
44
|
exit 0
|
|
38
45
|
fi
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
if
|
|
42
|
-
|
|
47
|
+
cli_bin="${GUARDEX_CLI_BIN:-}"
|
|
48
|
+
if [[ -z "$cli_bin" ]]; then
|
|
49
|
+
if command -v gx >/dev/null 2>&1; then
|
|
50
|
+
cli_bin="gx"
|
|
51
|
+
elif command -v gitguardex >/dev/null 2>&1; then
|
|
52
|
+
cli_bin="gitguardex"
|
|
53
|
+
else
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
43
56
|
fi
|
|
44
57
|
|
|
45
|
-
"$
|
|
58
|
+
"$cli_bin" cleanup \
|
|
46
59
|
--target "$repo_root" \
|
|
47
60
|
--base "$base_branch" \
|
|
48
61
|
--include-pr-merged \
|
|
@@ -13,6 +13,8 @@ repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
|
13
13
|
if [[ -z "$repo_root" ]]; then
|
|
14
14
|
exit 0
|
|
15
15
|
fi
|
|
16
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
17
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
16
18
|
guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
|
|
17
19
|
if [[ -f "$guardex_env_helper" ]]; then
|
|
18
20
|
# shellcheck source=/dev/null
|
|
@@ -22,6 +24,23 @@ if declare -F guardex_repo_is_enabled >/dev/null 2>&1 && ! guardex_repo_is_enabl
|
|
|
22
24
|
exit 0
|
|
23
25
|
fi
|
|
24
26
|
|
|
27
|
+
run_guardex_cli() {
|
|
28
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
29
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
30
|
+
return $?
|
|
31
|
+
fi
|
|
32
|
+
if command -v gx >/dev/null 2>&1; then
|
|
33
|
+
gx "$@"
|
|
34
|
+
return $?
|
|
35
|
+
fi
|
|
36
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
37
|
+
gitguardex "$@"
|
|
38
|
+
return $?
|
|
39
|
+
fi
|
|
40
|
+
echo "[agent-branch-guard] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
41
|
+
return 127
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
if [[ "${ALLOW_COMMIT_ON_PROTECTED_BRANCH:-0}" == "1" ]]; then
|
|
26
45
|
exit 0
|
|
27
46
|
fi
|
|
@@ -116,11 +135,11 @@ if [[ "$should_require_codex_agent_branch" == "1" && "${GUARDEX_ALLOW_CODEX_ON_N
|
|
|
116
135
|
|
|
117
136
|
cat >&2 <<'MSG'
|
|
118
137
|
[guardex-preedit-guard] Codex edit/commit detected on a protected branch.
|
|
119
|
-
|
|
138
|
+
GitGuardex requires Codex work to run from an isolated agent/* branch.
|
|
120
139
|
Start the sub-branch/worktree with:
|
|
121
|
-
|
|
140
|
+
gx branch start "<task-or-plan>" "<agent-name>"
|
|
122
141
|
Or manually:
|
|
123
|
-
|
|
142
|
+
gx branch start "<task-or-plan>" "<agent-name>"
|
|
124
143
|
Then commit from the created agent/* branch.
|
|
125
144
|
|
|
126
145
|
Temporary bypass (not recommended):
|
|
@@ -132,7 +151,7 @@ MSG
|
|
|
132
151
|
cat >&2 <<'MSG'
|
|
133
152
|
[codex-branch-guard] Codex agent commit blocked on non-agent branch.
|
|
134
153
|
Use isolated branch/worktree first:
|
|
135
|
-
|
|
154
|
+
gx branch start "<task-or-plan>" "<agent-name>"
|
|
136
155
|
Then commit from the created agent/* branch.
|
|
137
156
|
|
|
138
157
|
Temporary bypass (not recommended):
|
|
@@ -163,9 +182,9 @@ if [[ "$is_protected_branch" == "1" ]]; then
|
|
|
163
182
|
cat >&2 <<'MSG'
|
|
164
183
|
[agent-branch-guard] Direct commits on protected branches are blocked.
|
|
165
184
|
Use an agent branch first:
|
|
166
|
-
|
|
185
|
+
gx branch start "<task-or-plan>" "<agent-name>"
|
|
167
186
|
After finishing work:
|
|
168
|
-
|
|
187
|
+
gx branch finish
|
|
169
188
|
|
|
170
189
|
Temporary bypass (not recommended):
|
|
171
190
|
ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
|
|
@@ -177,7 +196,7 @@ if [[ "$is_agent_session" == "1" && "$branch" != agent/* ]]; then
|
|
|
177
196
|
cat >&2 <<'MSG'
|
|
178
197
|
[agent-branch-guard] Agent commits must run on dedicated agent/* branches.
|
|
179
198
|
Start an agent branch first:
|
|
180
|
-
|
|
199
|
+
gx branch start "<task-or-plan>" "<agent-name>"
|
|
181
200
|
Then commit on that branch.
|
|
182
201
|
|
|
183
202
|
Temporary bypass (not recommended):
|
|
@@ -191,15 +210,15 @@ if [[ "$branch" == agent/* ]]; then
|
|
|
191
210
|
while IFS= read -r staged_file; do
|
|
192
211
|
[[ -z "$staged_file" ]] && continue
|
|
193
212
|
[[ "$staged_file" == ".omx/state/agent-file-locks.json" ]] && continue
|
|
194
|
-
|
|
213
|
+
run_guardex_cli locks claim --branch "$branch" "$staged_file" >/dev/null 2>&1 || true
|
|
195
214
|
done < <(git diff --cached --name-only --diff-filter=ACMRDTUXB)
|
|
196
215
|
fi
|
|
197
216
|
|
|
198
|
-
if !
|
|
217
|
+
if ! run_guardex_cli locks validate --branch "$branch" --staged; then
|
|
199
218
|
cat >&2 <<'MSG'
|
|
200
219
|
[agent-branch-guard] Agent branch commits require file ownership locks.
|
|
201
220
|
Claim files first:
|
|
202
|
-
|
|
221
|
+
gx locks claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
|
|
203
222
|
MSG
|
|
204
223
|
exit 1
|
|
205
224
|
fi
|
|
@@ -9,11 +9,30 @@ DELETE_REMOTE_BRANCH=0
|
|
|
9
9
|
DELETE_REMOTE_BRANCH_EXPLICIT=0
|
|
10
10
|
MERGE_MODE="auto"
|
|
11
11
|
GH_BIN="${GUARDEX_GH_BIN:-gh}"
|
|
12
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
13
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
12
14
|
CLEANUP_AFTER_MERGE_RAW="${GUARDEX_FINISH_CLEANUP:-false}"
|
|
13
15
|
WAIT_FOR_MERGE_RAW="${GUARDEX_FINISH_WAIT_FOR_MERGE:-false}"
|
|
14
16
|
WAIT_TIMEOUT_SECONDS_RAW="${GUARDEX_FINISH_WAIT_TIMEOUT_SECONDS:-1800}"
|
|
15
17
|
WAIT_POLL_SECONDS_RAW="${GUARDEX_FINISH_WAIT_POLL_SECONDS:-10}"
|
|
16
18
|
|
|
19
|
+
run_guardex_cli() {
|
|
20
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
21
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
22
|
+
return $?
|
|
23
|
+
fi
|
|
24
|
+
if command -v gx >/dev/null 2>&1; then
|
|
25
|
+
gx "$@"
|
|
26
|
+
return $?
|
|
27
|
+
fi
|
|
28
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
29
|
+
gitguardex "$@"
|
|
30
|
+
return $?
|
|
31
|
+
fi
|
|
32
|
+
echo "[agent-branch-finish] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
33
|
+
return 127
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
normalize_bool() {
|
|
18
37
|
local raw="${1:-}"
|
|
19
38
|
local fallback="${2:-0}"
|
|
@@ -431,7 +450,7 @@ run_pr_flow() {
|
|
|
431
450
|
if [[ -z "$pr_title" ]]; then
|
|
432
451
|
pr_title="Merge ${SOURCE_BRANCH} into ${BASE_BRANCH}"
|
|
433
452
|
fi
|
|
434
|
-
pr_body="Automated by
|
|
453
|
+
pr_body="Automated by gx branch finish (PR flow)."
|
|
435
454
|
|
|
436
455
|
"$GH_BIN" pr create \
|
|
437
456
|
--base "$BASE_BRANCH" \
|
|
@@ -517,9 +536,7 @@ if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
|
517
536
|
fi
|
|
518
537
|
fi
|
|
519
538
|
|
|
520
|
-
|
|
521
|
-
python3 "${repo_root}/scripts/agent-file-locks.py" release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
522
|
-
fi
|
|
539
|
+
run_guardex_cli locks release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
523
540
|
|
|
524
541
|
base_worktree="$(get_worktree_for_branch "$BASE_BRANCH")"
|
|
525
542
|
if [[ -n "$base_worktree" ]] && is_clean_worktree "$base_worktree" && [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
@@ -555,29 +572,25 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
555
572
|
fi
|
|
556
573
|
fi
|
|
557
574
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
echo "[agent-branch-finish] You can run manual cleanup: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH} --delete-branches" >&2
|
|
566
|
-
fi
|
|
575
|
+
prune_args=(--base "$BASE_BRANCH" --only-dirty-worktrees --delete-branches)
|
|
576
|
+
if [[ "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
577
|
+
prune_args+=(--delete-remote-branches)
|
|
578
|
+
fi
|
|
579
|
+
if ! run_guardex_cli worktree prune "${prune_args[@]}"; then
|
|
580
|
+
echo "[agent-branch-finish] Warning: automatic worktree prune failed." >&2
|
|
581
|
+
echo "[agent-branch-finish] You can run manual cleanup: gx cleanup --base ${BASE_BRANCH}" >&2
|
|
567
582
|
fi
|
|
568
583
|
|
|
569
584
|
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and cleaned source branch/worktree."
|
|
570
585
|
if [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
571
586
|
echo "[agent-branch-finish] Current worktree '${source_worktree}' still exists because it is the active shell cwd." >&2
|
|
572
|
-
echo "[agent-branch-finish] Leave this directory, then run:
|
|
587
|
+
echo "[agent-branch-finish] Leave this directory, then run: gx cleanup --base ${BASE_BRANCH}" >&2
|
|
573
588
|
fi
|
|
574
589
|
else
|
|
575
|
-
if
|
|
576
|
-
|
|
577
|
-
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
578
|
-
fi
|
|
590
|
+
if ! run_guardex_cli worktree prune --base "$BASE_BRANCH"; then
|
|
591
|
+
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
579
592
|
fi
|
|
580
593
|
|
|
581
594
|
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and kept source branch/worktree."
|
|
582
|
-
echo "[agent-branch-finish] Cleanup later with:
|
|
595
|
+
echo "[agent-branch-finish] Cleanup later with: gx cleanup --base ${BASE_BRANCH}"
|
|
583
596
|
fi
|
|
@@ -6,18 +6,37 @@ BASE_BRANCH_EXPLICIT=0
|
|
|
6
6
|
TARGET_BRANCH=""
|
|
7
7
|
TASK_NAME=""
|
|
8
8
|
AGENT_NAME="${GUARDEX_MERGE_AGENT_NAME:-codex}"
|
|
9
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
10
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
9
11
|
declare -a SOURCE_BRANCHES=()
|
|
10
12
|
|
|
11
13
|
usage() {
|
|
12
14
|
cat <<'EOF'
|
|
13
|
-
Usage:
|
|
15
|
+
Usage: gx branch merge --branch <agent/...> [--branch <agent/...> ...] [--into <agent/...>] [--task <task>] [--agent <agent>] [--base <branch>]
|
|
14
16
|
|
|
15
17
|
Examples:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
gx branch merge --branch agent/codex/ui-a --branch agent/codex/ui-b
|
|
19
|
+
gx branch merge --into agent/codex/owner-lane --branch agent/codex/helper-a --branch agent/codex/helper-b
|
|
18
20
|
EOF
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
run_guardex_cli() {
|
|
24
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
25
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
26
|
+
return $?
|
|
27
|
+
fi
|
|
28
|
+
if command -v gx >/dev/null 2>&1; then
|
|
29
|
+
gx "$@"
|
|
30
|
+
return $?
|
|
31
|
+
fi
|
|
32
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
33
|
+
gitguardex "$@"
|
|
34
|
+
return $?
|
|
35
|
+
fi
|
|
36
|
+
echo "[agent-branch-merge] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
37
|
+
return 127
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
sanitize_slug() {
|
|
22
41
|
local raw="$1"
|
|
23
42
|
local fallback="${2:-merge-agent-branches}"
|
|
@@ -262,7 +281,7 @@ if [[ -z "$TARGET_BRANCH" ]]; then
|
|
|
262
281
|
start_output=""
|
|
263
282
|
if ! start_output="$(
|
|
264
283
|
cd "$repo_root"
|
|
265
|
-
|
|
284
|
+
GUARDEX_OPENSPEC_AUTO_INIT=1 run_guardex_cli branch start "$TASK_NAME" "$AGENT_NAME" "$BASE_BRANCH" 2>&1
|
|
266
285
|
)"; then
|
|
267
286
|
printf '%s\n' "$start_output" >&2
|
|
268
287
|
exit 1
|
|
@@ -418,4 +437,4 @@ echo "[agent-branch-merge] Merge sequence complete for '${TARGET_BRANCH}'."
|
|
|
418
437
|
if [[ "$target_created" -eq 1 ]]; then
|
|
419
438
|
echo "[agent-branch-merge] Review and verify in '${target_worktree}', then finish the integration branch when ready."
|
|
420
439
|
fi
|
|
421
|
-
echo "[agent-branch-merge] Next step:
|
|
440
|
+
echo "[agent-branch-merge] Next step: gx branch finish --branch \"${TARGET_BRANCH}\" --base \"${BASE_BRANCH}\" --via-pr --wait-for-merge --cleanup"
|
|
@@ -7,6 +7,8 @@ BASE_BRANCH=""
|
|
|
7
7
|
BASE_BRANCH_EXPLICIT=0
|
|
8
8
|
WORKTREE_ROOT_REL=""
|
|
9
9
|
WORKTREE_ROOT_EXPLICIT=0
|
|
10
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
11
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
10
12
|
OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-false}"
|
|
11
13
|
OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
12
14
|
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
@@ -15,6 +17,23 @@ OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
|
15
17
|
PRINT_NAME_ONLY=0
|
|
16
18
|
POSITIONAL_ARGS=()
|
|
17
19
|
|
|
20
|
+
run_guardex_cli() {
|
|
21
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
22
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
23
|
+
return $?
|
|
24
|
+
fi
|
|
25
|
+
if command -v gx >/dev/null 2>&1; then
|
|
26
|
+
gx "$@"
|
|
27
|
+
return $?
|
|
28
|
+
fi
|
|
29
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
30
|
+
gitguardex "$@"
|
|
31
|
+
return $?
|
|
32
|
+
fi
|
|
33
|
+
echo "[agent-branch-start] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
34
|
+
return 127
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
while [[ $# -gt 0 ]]; do
|
|
19
38
|
case "$1" in
|
|
20
39
|
--task)
|
|
@@ -385,26 +404,14 @@ initialize_openspec_plan_workspace() {
|
|
|
385
404
|
local worktree="$2"
|
|
386
405
|
local plan_slug="$3"
|
|
387
406
|
|
|
388
|
-
hydrate_local_helper_in_worktree "$repo" "$worktree" "scripts/openspec/init-plan-workspace.sh"
|
|
389
|
-
|
|
390
407
|
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
391
408
|
return 0
|
|
392
409
|
fi
|
|
393
410
|
|
|
394
|
-
local openspec_script="${worktree}/scripts/openspec/init-plan-workspace.sh"
|
|
395
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
396
|
-
echo "[agent-branch-start] OpenSpec init script is missing in sandbox worktree." >&2
|
|
397
|
-
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
|
|
398
|
-
return 1
|
|
399
|
-
fi
|
|
400
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
401
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
402
|
-
fi
|
|
403
|
-
|
|
404
411
|
local init_output=""
|
|
405
412
|
if ! init_output="$(
|
|
406
413
|
cd "$worktree"
|
|
407
|
-
|
|
414
|
+
run_guardex_cli internal run-shell planInit "$plan_slug" 2>&1
|
|
408
415
|
)"; then
|
|
409
416
|
printf '%s\n' "$init_output" >&2
|
|
410
417
|
echo "[agent-branch-start] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
|
|
@@ -423,26 +430,14 @@ initialize_openspec_change_workspace() {
|
|
|
423
430
|
local change_slug="$3"
|
|
424
431
|
local capability_slug="$4"
|
|
425
432
|
|
|
426
|
-
hydrate_local_helper_in_worktree "$repo" "$worktree" "scripts/openspec/init-change-workspace.sh"
|
|
427
|
-
|
|
428
433
|
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
429
434
|
return 0
|
|
430
435
|
fi
|
|
431
436
|
|
|
432
|
-
local openspec_script="${worktree}/scripts/openspec/init-change-workspace.sh"
|
|
433
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
434
|
-
echo "[agent-branch-start] OpenSpec change init script is missing in sandbox worktree." >&2
|
|
435
|
-
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
|
|
436
|
-
return 1
|
|
437
|
-
fi
|
|
438
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
439
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
440
|
-
fi
|
|
441
|
-
|
|
442
437
|
local init_output=""
|
|
443
438
|
if ! init_output="$(
|
|
444
439
|
cd "$worktree"
|
|
445
|
-
|
|
440
|
+
run_guardex_cli internal run-shell changeInit "$change_slug" "$capability_slug" 2>&1
|
|
446
441
|
)"; then
|
|
447
442
|
printf '%s\n' "$init_output" >&2
|
|
448
443
|
echo "[agent-branch-start] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
@@ -592,7 +587,6 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
|
592
587
|
fi
|
|
593
588
|
fi
|
|
594
589
|
|
|
595
|
-
hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-agent.sh"
|
|
596
590
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
|
|
597
591
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
|
|
598
592
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
|
|
@@ -609,6 +603,6 @@ echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_s
|
|
|
609
603
|
echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
|
|
610
604
|
echo "[agent-branch-start] Next steps:"
|
|
611
605
|
echo " cd \"${worktree_path}\""
|
|
612
|
-
echo "
|
|
606
|
+
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
613
607
|
echo " # implement + commit"
|
|
614
|
-
echo "
|
|
608
|
+
echo " gx branch finish --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge"
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"""Per-file lock registry for concurrent agent branches.
|
|
3
3
|
|
|
4
4
|
Usage examples:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
gx locks claim --branch agent/a path/to/file1 path/to/file2
|
|
6
|
+
gx locks claim --branch agent/a --allow-delete path/to/obsolete-file
|
|
7
|
+
gx locks allow-delete --branch agent/a path/to/obsolete-file
|
|
8
|
+
gx locks validate --branch agent/a --staged
|
|
9
|
+
gx locks release --branch agent/a
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -27,9 +27,9 @@ CRITICAL_GUARDRAIL_PATHS = {
|
|
|
27
27
|
'AGENTS.md',
|
|
28
28
|
'.githooks/pre-commit',
|
|
29
29
|
'.githooks/pre-push',
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'scripts/
|
|
30
|
+
'.githooks/post-merge',
|
|
31
|
+
'.githooks/post-checkout',
|
|
32
|
+
'scripts/guardex-env.sh',
|
|
33
33
|
}
|
|
34
34
|
ALLOW_GUARDRAIL_DELETE_ENV = 'AGENT_ALLOW_GUARDRAIL_DELETE'
|
|
35
35
|
|
|
@@ -326,11 +326,11 @@ def cmd_validate(args: argparse.Namespace, repo_root: Path) -> int:
|
|
|
326
326
|
print(f' - {file_path}', file=sys.stderr)
|
|
327
327
|
print(' Approve explicit deletions with one of:', file=sys.stderr)
|
|
328
328
|
print(
|
|
329
|
-
f'
|
|
329
|
+
f' gx locks claim --branch "{args.branch}" --allow-delete <file...>',
|
|
330
330
|
file=sys.stderr,
|
|
331
331
|
)
|
|
332
332
|
print(
|
|
333
|
-
f'
|
|
333
|
+
f' gx locks allow-delete --branch "{args.branch}" <file...>',
|
|
334
334
|
file=sys.stderr,
|
|
335
335
|
)
|
|
336
336
|
if guardrail_delete_blocked:
|
|
@@ -343,7 +343,7 @@ def cmd_validate(args: argparse.Namespace, repo_root: Path) -> int:
|
|
|
343
343
|
)
|
|
344
344
|
|
|
345
345
|
print('\nClaim files with:', file=sys.stderr)
|
|
346
|
-
print(f'
|
|
346
|
+
print(f' gx locks claim --branch "{args.branch}" <file...>', file=sys.stderr)
|
|
347
347
|
return 1
|
|
348
348
|
|
|
349
349
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
function resolveSessionSchemaModule() {
|
|
7
|
+
const candidates = [
|
|
8
|
+
path.resolve(__dirname, '..', 'vscode', 'guardex-active-agents', 'session-schema.js'),
|
|
9
|
+
path.resolve(__dirname, '..', 'templates', 'vscode', 'guardex-active-agents', 'session-schema.js'),
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
for (const candidate of candidates) {
|
|
13
|
+
if (fs.existsSync(candidate)) {
|
|
14
|
+
return require(candidate);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
throw new Error('Could not resolve Guardex active-agent session schema module.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sessionSchema = resolveSessionSchemaModule();
|
|
22
|
+
|
|
23
|
+
function usage() {
|
|
24
|
+
return (
|
|
25
|
+
'Usage:\n' +
|
|
26
|
+
' node scripts/agent-session-state.js start --repo <path> --branch <name> --task <task> --agent <agent> --worktree <path> --pid <pid> --cli <name>\n' +
|
|
27
|
+
' node scripts/agent-session-state.js stop --repo <path> --branch <name>\n'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseOptions(argv) {
|
|
32
|
+
const options = {};
|
|
33
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
34
|
+
const token = argv[index];
|
|
35
|
+
if (!token.startsWith('--')) {
|
|
36
|
+
throw new Error(`Unexpected argument: ${token}`);
|
|
37
|
+
}
|
|
38
|
+
const key = token.slice(2);
|
|
39
|
+
const value = argv[index + 1];
|
|
40
|
+
if (!value || value.startsWith('--')) {
|
|
41
|
+
throw new Error(`Missing value for --${key}`);
|
|
42
|
+
}
|
|
43
|
+
options[key] = value;
|
|
44
|
+
index += 1;
|
|
45
|
+
}
|
|
46
|
+
return options;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function requireOption(options, key) {
|
|
50
|
+
const value = options[key];
|
|
51
|
+
if (!value) {
|
|
52
|
+
throw new Error(`Missing required option --${key}`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeSessionRecord(options) {
|
|
58
|
+
const repoRoot = requireOption(options, 'repo');
|
|
59
|
+
const branch = requireOption(options, 'branch');
|
|
60
|
+
const record = sessionSchema.buildSessionRecord({
|
|
61
|
+
repoRoot,
|
|
62
|
+
branch,
|
|
63
|
+
taskName: requireOption(options, 'task'),
|
|
64
|
+
agentName: requireOption(options, 'agent'),
|
|
65
|
+
worktreePath: requireOption(options, 'worktree'),
|
|
66
|
+
pid: requireOption(options, 'pid'),
|
|
67
|
+
cliName: requireOption(options, 'cli'),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
|
|
71
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
72
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function removeSessionRecord(options) {
|
|
76
|
+
const repoRoot = requireOption(options, 'repo');
|
|
77
|
+
const branch = requireOption(options, 'branch');
|
|
78
|
+
const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
|
|
79
|
+
if (fs.existsSync(targetPath)) {
|
|
80
|
+
fs.unlinkSync(targetPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function main() {
|
|
85
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
86
|
+
if (!command || ['-h', '--help', 'help'].includes(command)) {
|
|
87
|
+
process.stdout.write(usage());
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const options = parseOptions(rest);
|
|
92
|
+
if (command === 'start') {
|
|
93
|
+
writeSessionRecord(options);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (command === 'stop') {
|
|
97
|
+
removeSessionRecord(options);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(`Unknown subcommand: ${command}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
main();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
process.stderr.write(`[guardex-active-session] ${error.message}\n`);
|
|
108
|
+
process.stderr.write(usage());
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
}
|