@imdeadpool/guardex 7.0.41 → 7.0.43
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 +68 -13
- package/package.json +2 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/detect.js +160 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +189 -0
- package/src/agents/launch.js +240 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +143 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +343 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +305 -1
- package/src/cli/main.js +262 -132
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/keybindings.js +224 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/layout.js +224 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +76 -33
- package/src/doctor/index.js +3 -2
- package/src/finish/index.js +39 -2
- package/src/git/index.js +65 -0
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/output/index.js +1 -1
- package/src/pr-review.js +241 -0
- package/src/scaffold/index.js +19 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +120 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +126 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/templates/AGENTS.multiagent-safety.md +27 -1
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -1
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +544 -26
- package/templates/scripts/agent-branch-start.sh +89 -22
- package/templates/scripts/agent-preflight.sh +89 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -6
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
|
@@ -9,13 +9,16 @@ WORKTREE_ROOT_REL=""
|
|
|
9
9
|
WORKTREE_ROOT_EXPLICIT=0
|
|
10
10
|
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
11
11
|
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
12
|
-
OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-
|
|
12
|
+
OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-true}"
|
|
13
13
|
OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
14
14
|
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
18
|
REUSE_EXISTING_RAW="${GUARDEX_BRANCH_START_REUSE_EXISTING:-true}"
|
|
19
|
+
AUTO_TRANSFER_ENABLED_RAW="${GUARDEX_AUTO_TRANSFER:-true}"
|
|
20
|
+
AUTO_TRANSFER_EXCLUDE_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.agents/settings.local.json:.codex/state/**:.claude/state/**'
|
|
21
|
+
AUTO_TRANSFER_EXCLUDE_RAW="${GUARDEX_AUTO_TRANSFER_EXCLUDE-$AUTO_TRANSFER_EXCLUDE_DEFAULT}"
|
|
19
22
|
PRINT_NAME_ONLY=0
|
|
20
23
|
POSITIONAL_ARGS=()
|
|
21
24
|
|
|
@@ -67,6 +70,18 @@ while [[ $# -gt 0 ]]; do
|
|
|
67
70
|
REUSE_EXISTING_RAW="false"
|
|
68
71
|
shift
|
|
69
72
|
;;
|
|
73
|
+
--no-transfer)
|
|
74
|
+
AUTO_TRANSFER_ENABLED_RAW="false"
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
--transfer)
|
|
78
|
+
AUTO_TRANSFER_ENABLED_RAW="true"
|
|
79
|
+
shift
|
|
80
|
+
;;
|
|
81
|
+
--transfer-exclude)
|
|
82
|
+
AUTO_TRANSFER_EXCLUDE_RAW="${2:-}"
|
|
83
|
+
shift 2
|
|
84
|
+
;;
|
|
70
85
|
--in-place|--allow-in-place)
|
|
71
86
|
echo "[agent-branch-start] In-place branch mode is disabled." >&2
|
|
72
87
|
echo "[agent-branch-start] This command always creates an isolated worktree to keep your active checkout unchanged." >&2
|
|
@@ -389,11 +404,27 @@ print_reused_agent_worktree() {
|
|
|
389
404
|
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
390
405
|
echo "[agent-branch-start] OpenSpec change: existing worktree"
|
|
391
406
|
echo "[agent-branch-start] OpenSpec plan: existing worktree"
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
407
|
+
print_agent_next_steps "$branch_name" "$worktree_path" "continue work in this existing sandbox" "$BASE_BRANCH"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
print_agent_next_steps() {
|
|
411
|
+
local branch_name="$1"
|
|
412
|
+
local worktree_path="$2"
|
|
413
|
+
local work_step="$3"
|
|
414
|
+
local base_branch="${4:-main}"
|
|
415
|
+
|
|
416
|
+
if [[ -z "$base_branch" ]]; then
|
|
417
|
+
base_branch="main"
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
echo "[agent-branch-start] Ready:"
|
|
421
|
+
echo " branch: ${branch_name}"
|
|
422
|
+
echo " worktree: ${worktree_path}"
|
|
423
|
+
echo " next:"
|
|
424
|
+
echo " cd \"${worktree_path}\""
|
|
425
|
+
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
426
|
+
echo " # ${work_step}"
|
|
427
|
+
echo " gx branch finish --branch \"${branch_name}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
|
|
397
428
|
}
|
|
398
429
|
|
|
399
430
|
has_local_changes() {
|
|
@@ -419,7 +450,7 @@ meaningful_slug_tokens() {
|
|
|
419
450
|
| awk '
|
|
420
451
|
length($0) < 4 { next }
|
|
421
452
|
$0 ~ /^[0-9]+$/ { next }
|
|
422
|
-
$0 ~ /^(agent|agents|branch|codex|claude|continue|dirty|existing|fix|from|implement|make|matching|reuse|start|task|that|this|update|with|worktree|worktrees)$/ { next }
|
|
453
|
+
$0 ~ /^(agent|agents|branch|codex|claude|continue|dirty|existing|fix|from|implement|make|matching|openspec|reuse|start|task|that|this|update|with|worktree|worktrees)$/ { next }
|
|
423
454
|
!seen[$0]++ { print }
|
|
424
455
|
'
|
|
425
456
|
}
|
|
@@ -774,18 +805,54 @@ restore_auto_transfer_stash_on_failure() {
|
|
|
774
805
|
|
|
775
806
|
trap 'restore_auto_transfer_stash_on_failure "$?"' EXIT
|
|
776
807
|
|
|
808
|
+
auto_transfer_enabled_lc="$(printf '%s' "$AUTO_TRANSFER_ENABLED_RAW" | tr '[:upper:]' '[:lower:]')"
|
|
809
|
+
case "$auto_transfer_enabled_lc" in
|
|
810
|
+
0|false|no|off) AUTO_TRANSFER_ENABLED=0 ;;
|
|
811
|
+
*) AUTO_TRANSFER_ENABLED=1 ;;
|
|
812
|
+
esac
|
|
813
|
+
|
|
814
|
+
build_auto_transfer_stash_argv() {
|
|
815
|
+
# Emit NUL-separated argv: --include-untracked --message <msg> [-- :/ :(exclude,glob)PAT ...]
|
|
816
|
+
local msg="$1"
|
|
817
|
+
printf '%s\0' --include-untracked --message "$msg"
|
|
818
|
+
if [[ -z "${AUTO_TRANSFER_EXCLUDE_RAW:-}" ]]; then
|
|
819
|
+
return 0
|
|
820
|
+
fi
|
|
821
|
+
printf '%s\0' '--' ':/'
|
|
822
|
+
local -a patterns=()
|
|
823
|
+
IFS=':' read -ra patterns <<< "$AUTO_TRANSFER_EXCLUDE_RAW"
|
|
824
|
+
local pattern
|
|
825
|
+
for pattern in "${patterns[@]}"; do
|
|
826
|
+
[[ -z "$pattern" ]] && continue
|
|
827
|
+
printf '%s\0' ":(exclude,glob)${pattern}"
|
|
828
|
+
done
|
|
829
|
+
}
|
|
830
|
+
|
|
777
831
|
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
778
832
|
protected_branches_raw="$(resolve_protected_branches "$repo_root")"
|
|
779
833
|
if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
|
|
780
834
|
if has_local_changes "$repo_root"; then
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
835
|
+
if [[ "$AUTO_TRANSFER_ENABLED" -eq 0 ]]; then
|
|
836
|
+
echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'; --no-transfer set, leaving them in place." >&2
|
|
837
|
+
echo "[agent-branch-start] If you intended those changes for '${branch_name}', stash them manually and apply inside the worktree." >&2
|
|
838
|
+
else
|
|
839
|
+
auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
|
|
840
|
+
stash_argv=()
|
|
841
|
+
while IFS= read -r -d '' arg; do
|
|
842
|
+
stash_argv+=("$arg")
|
|
843
|
+
done < <(build_auto_transfer_stash_argv "$auto_transfer_message")
|
|
844
|
+
if git -C "$repo_root" stash push "${stash_argv[@]}" >/dev/null 2>&1; then
|
|
845
|
+
auto_transfer_stash_ref="$(resolve_stash_ref_by_message "$repo_root" "$auto_transfer_message")"
|
|
846
|
+
if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
847
|
+
auto_transfer_source_branch="$current_branch"
|
|
848
|
+
echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}' (state-file globs excluded)..."
|
|
849
|
+
if ! maybe_fail_after_auto_transfer_stash; then
|
|
850
|
+
exit 1
|
|
851
|
+
fi
|
|
852
|
+
else
|
|
853
|
+
if has_local_changes "$repo_root"; then
|
|
854
|
+
echo "[agent-branch-start] Local changes on '${current_branch}' all match the auto-transfer exclude list; leaving them in place on '${current_branch}'." >&2
|
|
855
|
+
fi
|
|
789
856
|
fi
|
|
790
857
|
fi
|
|
791
858
|
fi
|
|
@@ -832,18 +899,18 @@ fi
|
|
|
832
899
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
833
900
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
834
901
|
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
835
|
-
if [[ "$
|
|
902
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
903
|
+
echo "[agent-branch-start] OpenSpec change: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
|
|
904
|
+
elif [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
836
905
|
echo "[agent-branch-start] OpenSpec change: skipped by tier ${OPENSPEC_TIER}"
|
|
837
906
|
else
|
|
838
907
|
echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
|
|
839
908
|
fi
|
|
840
|
-
if [[ "$
|
|
909
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
910
|
+
echo "[agent-branch-start] OpenSpec plan: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
|
|
911
|
+
elif [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
|
|
841
912
|
echo "[agent-branch-start] OpenSpec plan: skipped by tier ${OPENSPEC_TIER}"
|
|
842
913
|
else
|
|
843
914
|
echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
|
|
844
915
|
fi
|
|
845
|
-
|
|
846
|
-
echo " cd \"${worktree_path}\""
|
|
847
|
-
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
848
|
-
echo " # implement + commit"
|
|
849
|
-
echo " gx branch finish --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge"
|
|
916
|
+
print_agent_next_steps "$branch_name" "$worktree_path" "implement + commit" "$BASE_BRANCH"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-flight verification gate for gitguardex-managed projects.
|
|
3
|
+
#
|
|
4
|
+
# Runs in the agent's worktree from `gx branch finish` BEFORE the push
|
|
5
|
+
# happens. Returns non-zero to refuse the push so a broken commit
|
|
6
|
+
# never reaches the PR / CI / merge funnel.
|
|
7
|
+
#
|
|
8
|
+
# Auto-detects the project's stack and runs conventional verification:
|
|
9
|
+
# - Node/pnpm: pnpm typecheck && pnpm lint && pnpm test (each only
|
|
10
|
+
# if the script exists in package.json)
|
|
11
|
+
# - Node/npm: npm test (only if defined)
|
|
12
|
+
# - Rust: cargo check
|
|
13
|
+
# - Python: ruff check (only if ruff is installed)
|
|
14
|
+
#
|
|
15
|
+
# Override per-project by replacing this file (delete the symlink under
|
|
16
|
+
# scripts/agent-preflight.sh and write your own).
|
|
17
|
+
#
|
|
18
|
+
# Skip a single run with `gx branch finish --no-preflight`.
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
23
|
+
cd "$repo_root"
|
|
24
|
+
|
|
25
|
+
ran=0
|
|
26
|
+
fail=0
|
|
27
|
+
run_step() {
|
|
28
|
+
local label="$1"
|
|
29
|
+
shift
|
|
30
|
+
echo "[agent-preflight] -> $label"
|
|
31
|
+
if "$@"; then
|
|
32
|
+
ran=$((ran + 1))
|
|
33
|
+
echo "[agent-preflight] ok"
|
|
34
|
+
else
|
|
35
|
+
echo "[agent-preflight] FAIL: $label" >&2
|
|
36
|
+
fail=1
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
has_package_script() {
|
|
41
|
+
local script_name="$1"
|
|
42
|
+
[[ -f package.json ]] || return 1
|
|
43
|
+
grep -E "\"${script_name}\"\\s*:" package.json >/dev/null 2>&1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Node detection
|
|
47
|
+
if [[ -f package.json ]]; then
|
|
48
|
+
pkg_manager=""
|
|
49
|
+
if command -v pnpm >/dev/null 2>&1 && [[ -f pnpm-lock.yaml ]]; then
|
|
50
|
+
pkg_manager="pnpm"
|
|
51
|
+
elif command -v npm >/dev/null 2>&1 && [[ -f package-lock.json ]]; then
|
|
52
|
+
pkg_manager="npm"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
case "$pkg_manager" in
|
|
56
|
+
pnpm)
|
|
57
|
+
has_package_script typecheck && run_step "pnpm typecheck" pnpm typecheck
|
|
58
|
+
has_package_script lint && run_step "pnpm lint" pnpm lint
|
|
59
|
+
has_package_script test && run_step "pnpm test" pnpm test
|
|
60
|
+
;;
|
|
61
|
+
npm)
|
|
62
|
+
has_package_script test && run_step "npm test" npm test
|
|
63
|
+
;;
|
|
64
|
+
esac
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Rust detection
|
|
68
|
+
if [[ -f Cargo.toml ]] && command -v cargo >/dev/null 2>&1; then
|
|
69
|
+
run_step "cargo check" cargo check --quiet
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Python detection (ruff if available; pytest is too project-specific to default)
|
|
73
|
+
if [[ -f pyproject.toml ]] && command -v ruff >/dev/null 2>&1; then
|
|
74
|
+
run_step "ruff check" ruff check .
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [[ "$ran" -eq 0 ]]; then
|
|
78
|
+
echo "[agent-preflight] No recognized project stack detected; skipping checks." >&2
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
if [[ "$fail" -ne 0 ]]; then
|
|
83
|
+
echo "[agent-preflight] Verification failed; refusing push." >&2
|
|
84
|
+
echo "[agent-preflight] Fix the issues, or re-run with: gx branch finish --no-preflight ..." >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
echo "[agent-preflight] ${ran} step(s) passed."
|
|
89
|
+
exit 0
|
|
@@ -82,7 +82,12 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
82
82
|
fi
|
|
83
83
|
|
|
84
84
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
85
|
-
current_pwd="$(pwd -P)"
|
|
85
|
+
current_pwd="${GUARDEX_PRUNE_ACTIVE_CWD:-$(pwd -P)}"
|
|
86
|
+
if [[ -d "$current_pwd" ]]; then
|
|
87
|
+
current_pwd="$(cd "$current_pwd" && pwd -P)"
|
|
88
|
+
else
|
|
89
|
+
current_pwd=""
|
|
90
|
+
fi
|
|
86
91
|
repo_common_dir="$(
|
|
87
92
|
git -C "$repo_root" rev-parse --git-common-dir \
|
|
88
93
|
| awk -v root="$repo_root" '{ if ($0 ~ /^\//) { print $0 } else { print root "/" $0 } }'
|
|
@@ -244,11 +249,71 @@ branch_has_worktree() {
|
|
|
244
249
|
git -C "$repo_root" worktree list --porcelain | grep -q "^branch refs/heads/${branch}$"
|
|
245
250
|
}
|
|
246
251
|
|
|
252
|
+
# Globs treated as agent state, not real work. Worktrees whose only "dirty"
|
|
253
|
+
# content matches these are considered clean for prune purposes. Mirrors the
|
|
254
|
+
# auto-transfer + auto-resolve allowlist from PRs #546/#547 (state-file globs
|
|
255
|
+
# never carry authoritative content out of an agent branch).
|
|
256
|
+
WORKTREE_STATE_EXCLUDE_GLOBS_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.agents/settings.local.json:.codex/state/**:.claude/state/**'
|
|
257
|
+
WORKTREE_STATE_EXCLUDE_GLOBS_RAW="${GUARDEX_PRUNE_STATE_EXCLUDE_GLOBS-$WORKTREE_STATE_EXCLUDE_GLOBS_DEFAULT}"
|
|
258
|
+
|
|
259
|
+
build_state_exclude_pathspec_args() {
|
|
260
|
+
# Emit one ':(exclude,glob)<pat>' arg per non-empty pattern.
|
|
261
|
+
if [[ -z "$WORKTREE_STATE_EXCLUDE_GLOBS_RAW" ]]; then
|
|
262
|
+
return 0
|
|
263
|
+
fi
|
|
264
|
+
local -a globs=()
|
|
265
|
+
IFS=':' read -ra globs <<< "$WORKTREE_STATE_EXCLUDE_GLOBS_RAW"
|
|
266
|
+
local pattern
|
|
267
|
+
for pattern in "${globs[@]}"; do
|
|
268
|
+
[[ -z "$pattern" ]] && continue
|
|
269
|
+
printf '%s\0' ":(exclude,glob)${pattern}"
|
|
270
|
+
done
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# Capture the state-exclude pathspecs once; reused by is_clean_worktree and
|
|
274
|
+
# the dirt-summary logger below.
|
|
275
|
+
STATE_EXCLUDE_PATHSPEC_ARGS=()
|
|
276
|
+
while IFS= read -r -d '' arg; do
|
|
277
|
+
STATE_EXCLUDE_PATHSPEC_ARGS+=("$arg")
|
|
278
|
+
done < <(build_state_exclude_pathspec_args)
|
|
279
|
+
|
|
247
280
|
is_clean_worktree() {
|
|
248
281
|
local wt="$1"
|
|
249
|
-
git -C "$wt" diff --quiet -- . "
|
|
250
|
-
&& git -C "$wt" diff --cached --quiet -- . "
|
|
251
|
-
&& [[ -z "$(git -C "$wt" ls-files --others --exclude-standard)" ]]
|
|
282
|
+
git -C "$wt" diff --quiet -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" \
|
|
283
|
+
&& git -C "$wt" diff --cached --quiet -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" \
|
|
284
|
+
&& [[ -z "$(git -C "$wt" ls-files --others --exclude-standard -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}")" ]]
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# Returns a short, human-readable summary of why a worktree is considered dirty:
|
|
288
|
+
# up to 3 modified-tracked paths + up to 3 untracked paths + a "(+N more)" tail.
|
|
289
|
+
# Used to surface actionable context next to "Skipping dirty worktree" log lines
|
|
290
|
+
# (previously gave no clue what was actually dirty).
|
|
291
|
+
summarize_worktree_dirt() {
|
|
292
|
+
local wt="$1"
|
|
293
|
+
local modified_paths untracked_paths
|
|
294
|
+
modified_paths="$(git -C "$wt" diff --name-only --diff-filter=AMDR -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | head -3)"
|
|
295
|
+
local modified_count
|
|
296
|
+
modified_count="$(git -C "$wt" diff --name-only --diff-filter=AMDR -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | wc -l | tr -d ' ')"
|
|
297
|
+
untracked_paths="$(git -C "$wt" ls-files --others --exclude-standard -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | head -3)"
|
|
298
|
+
local untracked_count
|
|
299
|
+
untracked_count="$(git -C "$wt" ls-files --others --exclude-standard -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | wc -l | tr -d ' ')"
|
|
300
|
+
|
|
301
|
+
if [[ -n "$modified_paths" ]]; then
|
|
302
|
+
while IFS= read -r p; do
|
|
303
|
+
[[ -n "$p" ]] && echo " modified: ${p}"
|
|
304
|
+
done <<< "$modified_paths"
|
|
305
|
+
if [[ "$modified_count" -gt 3 ]]; then
|
|
306
|
+
echo " modified: (+$((modified_count - 3)) more)"
|
|
307
|
+
fi
|
|
308
|
+
fi
|
|
309
|
+
if [[ -n "$untracked_paths" ]]; then
|
|
310
|
+
while IFS= read -r p; do
|
|
311
|
+
[[ -n "$p" ]] && echo " untracked: ${p}"
|
|
312
|
+
done <<< "$untracked_paths"
|
|
313
|
+
if [[ "$untracked_count" -gt 3 ]]; then
|
|
314
|
+
echo " untracked: (+$((untracked_count - 3)) more)"
|
|
315
|
+
fi
|
|
316
|
+
fi
|
|
252
317
|
}
|
|
253
318
|
|
|
254
319
|
resolve_worktree_common_dir() {
|
|
@@ -317,6 +382,26 @@ read_branch_activity_epoch() {
|
|
|
317
382
|
|
|
318
383
|
skipped_recent=0
|
|
319
384
|
|
|
385
|
+
has_live_process_in_worktree() {
|
|
386
|
+
local wt="$1"
|
|
387
|
+
local proc_cwd=""
|
|
388
|
+
|
|
389
|
+
[[ -d /proc ]] || return 1
|
|
390
|
+
|
|
391
|
+
for proc_cwd in /proc/[0-9]*/cwd; do
|
|
392
|
+
[[ -e "$proc_cwd" ]] || continue
|
|
393
|
+
local live_cwd=""
|
|
394
|
+
live_cwd="$(readlink "$proc_cwd" 2>/dev/null || true)"
|
|
395
|
+
[[ -n "$live_cwd" ]] || continue
|
|
396
|
+
live_cwd="${live_cwd% (deleted)}"
|
|
397
|
+
if [[ "$live_cwd" == "$wt" || "$live_cwd" == "${wt}"/* ]]; then
|
|
398
|
+
return 0
|
|
399
|
+
fi
|
|
400
|
+
done
|
|
401
|
+
|
|
402
|
+
return 1
|
|
403
|
+
}
|
|
404
|
+
|
|
320
405
|
branch_idle_gate() {
|
|
321
406
|
local branch="$1"
|
|
322
407
|
local wt="$2"
|
|
@@ -431,11 +516,16 @@ process_entry() {
|
|
|
431
516
|
return
|
|
432
517
|
fi
|
|
433
518
|
|
|
434
|
-
if [[ "$wt" == "$current_pwd" ]]; then
|
|
519
|
+
if [[ -n "$current_pwd" && ( "$wt" == "$current_pwd" || "$current_pwd" == "${wt}"/* ) ]]; then
|
|
435
520
|
skipped_active=$((skipped_active + 1))
|
|
436
521
|
echo "[agent-worktree-prune] Skipping active cwd worktree: ${wt}"
|
|
437
522
|
return
|
|
438
523
|
fi
|
|
524
|
+
if has_live_process_in_worktree "$wt"; then
|
|
525
|
+
skipped_active=$((skipped_active + 1))
|
|
526
|
+
echo "[agent-worktree-prune] Skipping live process worktree: ${wt}"
|
|
527
|
+
return
|
|
528
|
+
fi
|
|
439
529
|
|
|
440
530
|
local remove_reason=""
|
|
441
531
|
local branch_delete_mode="safe"
|
|
@@ -477,6 +567,7 @@ process_entry() {
|
|
|
477
567
|
if [[ "$FORCE_DIRTY" -ne 1 ]] && ! is_clean_worktree "$wt"; then
|
|
478
568
|
skipped_dirty=$((skipped_dirty + 1))
|
|
479
569
|
echo "[agent-worktree-prune] Skipping dirty worktree (${remove_reason}): ${wt}"
|
|
570
|
+
summarize_worktree_dirt "$wt"
|
|
480
571
|
return
|
|
481
572
|
fi
|
|
482
573
|
|
|
@@ -6,6 +6,7 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
|
|
|
6
6
|
BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
|
|
7
7
|
BASE_BRANCH_EXPLICIT=0
|
|
8
8
|
CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
|
|
9
|
+
CODEX_APPROVAL_POLICY="${GUARDEX_CODEX_APPROVAL_POLICY-never}"
|
|
9
10
|
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
10
11
|
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
11
12
|
AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
|
|
@@ -67,6 +68,29 @@ normalize_tier() {
|
|
|
67
68
|
esac
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
codex_args_include_approval_policy() {
|
|
72
|
+
local arg
|
|
73
|
+
for arg in "$@"; do
|
|
74
|
+
case "$arg" in
|
|
75
|
+
-a|--ask-for-approval|--approval-policy|--dangerously-bypass-approvals-and-sandbox)
|
|
76
|
+
return 0
|
|
77
|
+
;;
|
|
78
|
+
--ask-for-approval=*|--approval-policy=*)
|
|
79
|
+
return 0
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
return 1
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
build_codex_launch_args() {
|
|
87
|
+
CODEX_LAUNCH_ARGS=()
|
|
88
|
+
if [[ -n "$CODEX_APPROVAL_POLICY" ]] && ! codex_args_include_approval_policy "$@"; then
|
|
89
|
+
CODEX_LAUNCH_ARGS+=("-a" "$CODEX_APPROVAL_POLICY")
|
|
90
|
+
fi
|
|
91
|
+
CODEX_LAUNCH_ARGS+=("$@")
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
string_contains_any() {
|
|
71
95
|
local haystack="$1"
|
|
72
96
|
shift
|
|
@@ -735,9 +759,14 @@ print_takeover_prompt() {
|
|
|
735
759
|
|
|
736
760
|
finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
|
|
737
761
|
|
|
738
|
-
echo "[codex-agent]
|
|
739
|
-
echo "
|
|
740
|
-
echo "
|
|
762
|
+
echo "[codex-agent] Resume this sandbox:"
|
|
763
|
+
echo " change: ${change_slug}"
|
|
764
|
+
echo " branch: ${branch}"
|
|
765
|
+
echo " worktree: ${wt}"
|
|
766
|
+
echo " spec: ${change_artifact}"
|
|
767
|
+
echo " routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
|
|
768
|
+
echo " rule: continue current state; do not create a new sandbox"
|
|
769
|
+
echo " finish: ${finish_cmd}"
|
|
741
770
|
}
|
|
742
771
|
|
|
743
772
|
sync_worktree_with_base() {
|
|
@@ -1034,7 +1063,8 @@ run_finish_flow() {
|
|
|
1034
1063
|
(
|
|
1035
1064
|
cd "$wt"
|
|
1036
1065
|
set +e
|
|
1037
|
-
|
|
1066
|
+
build_codex_launch_args "$review_prompt"
|
|
1067
|
+
"$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
|
|
1038
1068
|
review_exit="$?"
|
|
1039
1069
|
set -e
|
|
1040
1070
|
if [[ "$review_exit" -ne 0 ]]; then
|
|
@@ -1092,10 +1122,11 @@ trap cleanup_active_session_state_on_exit EXIT INT TERM
|
|
|
1092
1122
|
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
|
|
1093
1123
|
cd "$worktree_path"
|
|
1094
1124
|
set +e
|
|
1125
|
+
build_codex_launch_args "$@"
|
|
1095
1126
|
GUARDEX_TASK_MODE="$TASK_MODE" \
|
|
1096
1127
|
GUARDEX_OPENSPEC_TIER="$OPENSPEC_TIER" \
|
|
1097
1128
|
GUARDEX_TASK_ROUTING_REASON="$TASK_ROUTING_REASON" \
|
|
1098
|
-
"$CODEX_BIN" "
|
|
1129
|
+
"$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
|
|
1099
1130
|
codex_exit="$?"
|
|
1100
1131
|
set -e
|
|
1101
1132
|
|
|
@@ -1161,7 +1192,11 @@ else
|
|
|
1161
1192
|
echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
1162
1193
|
else
|
|
1163
1194
|
print_takeover_prompt "$worktree_path" "$worktree_branch"
|
|
1164
|
-
|
|
1195
|
+
finish_base_branch="$(resolve_worktree_base_branch "$worktree_path")"
|
|
1196
|
+
if [[ -z "$finish_base_branch" ]]; then
|
|
1197
|
+
finish_base_branch="dev"
|
|
1198
|
+
fi
|
|
1199
|
+
echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base ${finish_base_branch} --via-pr --wait-for-merge --cleanup"
|
|
1165
1200
|
echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
1166
1201
|
fi
|
|
1167
1202
|
fi
|
|
@@ -84,6 +84,7 @@ if [[ ! -f "$PLAN_DIR/README.md" ]]; then
|
|
|
84
84
|
echo
|
|
85
85
|
echo "Each role folder contains OpenSpec-style artifacts:"
|
|
86
86
|
echo "- \`.openspec.yaml\`"
|
|
87
|
+
echo "- \`prompt.md\` (copy/paste role prompt)"
|
|
87
88
|
echo "- \`proposal.md\`"
|
|
88
89
|
echo "- \`tasks.md\` (Spec / Tests / Implementation / Checkpoints checklists)"
|
|
89
90
|
echo "- \`specs/<role>/spec.md\`"
|
|
@@ -108,6 +109,7 @@ Drive this plan from draft to execution-ready status with strict checkpoint disc
|
|
|
108
109
|
- \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
|
|
109
110
|
- \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
|
|
110
111
|
- \`openspec/plan/${PLAN_SLUG}/planner/plan.md\`
|
|
112
|
+
- role \`prompt.md\` files for copy/paste helper startup
|
|
111
113
|
- role \`tasks.md\` files for planner/architect/critic/executor/writer/verifier
|
|
112
114
|
|
|
113
115
|
## Coordinator responsibilities
|
|
@@ -282,6 +284,7 @@ Role workspace for \`${role}\`.
|
|
|
282
284
|
|
|
283
285
|
Default artifacts:
|
|
284
286
|
- \`.openspec.yaml\`
|
|
287
|
+
- \`prompt.md\`
|
|
285
288
|
- \`proposal.md\`
|
|
286
289
|
- \`tasks.md\`
|
|
287
290
|
- \`specs/<role>/spec.md\`
|
|
@@ -302,12 +305,52 @@ plan: ${PLAN_SLUG}
|
|
|
302
305
|
role: ${role}
|
|
303
306
|
status: draft
|
|
304
307
|
artifacts:
|
|
308
|
+
prompt: prompt.md
|
|
305
309
|
proposal: proposal.md
|
|
306
310
|
tasks: tasks.md
|
|
307
311
|
spec: specs/${ROLE_SPEC_SLUG}/spec.md
|
|
308
312
|
ROLEYAMLEOF
|
|
309
313
|
fi
|
|
310
314
|
|
|
315
|
+
if [[ ! -f "$ROLE_DIR/prompt.md" ]]; then
|
|
316
|
+
cat > "$ROLE_DIR/prompt.md" <<ROLEPROMPTEOF
|
|
317
|
+
# ${role} Prompt
|
|
318
|
+
|
|
319
|
+
You are the \`${role}\` role for OpenSpec plan \`${PLAN_SLUG}\`.
|
|
320
|
+
|
|
321
|
+
## Objective
|
|
322
|
+
|
|
323
|
+
Complete only this role's assigned checklist and leave compact evidence for the coordinator.
|
|
324
|
+
|
|
325
|
+
## Source of truth
|
|
326
|
+
|
|
327
|
+
- \`openspec/plan/${PLAN_SLUG}/summary.md\`
|
|
328
|
+
- \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
|
|
329
|
+
- \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
|
|
330
|
+
- \`openspec/plan/${PLAN_SLUG}/${role}/tasks.md\`
|
|
331
|
+
- \`openspec/plan/${PLAN_SLUG}/${role}/proposal.md\`
|
|
332
|
+
|
|
333
|
+
## Before edits
|
|
334
|
+
|
|
335
|
+
1. Confirm branch/worktree with \`git status --short --branch\`.
|
|
336
|
+
2. Claim every touched file before editing:
|
|
337
|
+
- Prefer Colony \`task_claim_file\` when an active task exists.
|
|
338
|
+
- Otherwise run \`gx locks claim --branch <agent-branch> <file...>\`.
|
|
339
|
+
3. Stay inside assigned files/modules; coordinate before touching shared paths.
|
|
340
|
+
|
|
341
|
+
## Working rules
|
|
342
|
+
|
|
343
|
+
- Update \`${role}/tasks.md\` as each item completes.
|
|
344
|
+
- Record durable unresolved questions in \`open-questions.md\`.
|
|
345
|
+
- Keep handoffs short: files changed, behavior touched, verification, risks.
|
|
346
|
+
- Do not revert another agent's edits.
|
|
347
|
+
|
|
348
|
+
## Cleanup
|
|
349
|
+
|
|
350
|
+
Only the owner/finalizer lane runs \`gx branch finish --branch <agent-branch> --base dev --via-pr --wait-for-merge --cleanup\`. If blocked, append \`BLOCKED:\` with branch, task, blocker, next, evidence.
|
|
351
|
+
ROLEPROMPTEOF
|
|
352
|
+
fi
|
|
353
|
+
|
|
311
354
|
if [[ ! -f "$ROLE_DIR/proposal.md" ]]; then
|
|
312
355
|
cat > "$ROLE_DIR/proposal.md" <<ROLEPROPOSALEOF
|
|
313
356
|
# Proposal: ${role} (${PLAN_SLUG})
|
|
@@ -64,6 +64,36 @@ normalize_bool() {
|
|
|
64
64
|
esac
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
gh_api_probe_looks_like_network_failure() {
|
|
68
|
+
local probe_output="$1"
|
|
69
|
+
[[ "$probe_output" =~ error[[:space:]]connecting[[:space:]]to[[:space:]]api\.github\.com|Could[[:space:]]not[[:space:]]resolve[[:space:]]host|could[[:space:]]not[[:space:]]resolve[[:space:]]host|Failed[[:space:]]to[[:space:]]connect|failed[[:space:]]to[[:space:]]connect|Network[[:space:]]is[[:space:]]unreachable|network[[:space:]]is[[:space:]]unreachable|Connection[[:space:]]timed[[:space:]]out|connection[[:space:]]timed[[:space:]]out|Temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution|temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution ]]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ensure_gh_auth_or_explain() {
|
|
73
|
+
local auth_output probe_output
|
|
74
|
+
if auth_output="$(gh auth status 2>&1)"; then
|
|
75
|
+
return 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if probe_output="$(gh api user --jq .login 2>&1)"; then
|
|
79
|
+
echo "[review-bot-watch] gh auth status failed, but gh api user succeeded; continuing with usable GitHub auth." >&2
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if gh_api_probe_looks_like_network_failure "$probe_output"; then
|
|
84
|
+
echo "[review-bot-watch] GitHub API is unreachable, so gh cannot validate the stored token." >&2
|
|
85
|
+
echo "[review-bot-watch] This is a network or Codex sandbox connectivity problem, not proof that the token is invalid." >&2
|
|
86
|
+
echo "$probe_output" >&2
|
|
87
|
+
return 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
|
|
91
|
+
if [[ -n "$auth_output" ]]; then
|
|
92
|
+
echo "$auth_output" >&2
|
|
93
|
+
fi
|
|
94
|
+
return 1
|
|
95
|
+
}
|
|
96
|
+
|
|
67
97
|
ONCE=0
|
|
68
98
|
|
|
69
99
|
while [[ $# -gt 0 ]]; do
|
|
@@ -153,8 +183,7 @@ if ! command -v codex >/dev/null 2>&1; then
|
|
|
153
183
|
exit 127
|
|
154
184
|
fi
|
|
155
185
|
|
|
156
|
-
if !
|
|
157
|
-
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
|
|
186
|
+
if ! ensure_gh_auth_or_explain; then
|
|
158
187
|
exit 1
|
|
159
188
|
fi
|
|
160
189
|
|