@imdeadpool/guardex 7.0.41 → 7.1.0
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 +94 -13
- package/package.json +3 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +202 -0
- package/src/agents/launch.js +249 -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 +146 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +344 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +357 -3
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +85 -3613
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- 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/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -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 +304 -43
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +45 -15
- package/src/finish/index.js +186 -7
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +511 -4
- package/src/hooks/index.js +0 -64
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +68 -2
- package/src/pr-review.js +264 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +127 -10
- package/src/speckit/index.js +226 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +45 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +125 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +63 -323
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +44 -20
- 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 +519 -23
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +176 -24
- package/templates/scripts/agent-preflight.sh +115 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -97
- 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
|
@@ -281,7 +281,10 @@ if [[ -z "$TARGET_BRANCH" ]]; then
|
|
|
281
281
|
start_output=""
|
|
282
282
|
if ! start_output="$(
|
|
283
283
|
cd "$repo_root"
|
|
284
|
-
|
|
284
|
+
# An integration lane is inherently cross-cutting, so pin it to T3 (full
|
|
285
|
+
# change + plan workspace) regardless of the branch-start default (now T1).
|
|
286
|
+
GUARDEX_OPENSPEC_AUTO_INIT=1 GUARDEX_OPENSPEC_TIER="${GUARDEX_OPENSPEC_TIER:-T3}" \
|
|
287
|
+
run_guardex_cli branch start "$TASK_NAME" "$AGENT_NAME" "$BASE_BRANCH" 2>&1
|
|
285
288
|
)"; then
|
|
286
289
|
printf '%s\n' "$start_output" >&2
|
|
287
290
|
exit 1
|
|
@@ -9,13 +9,19 @@ 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
|
+
# Default tier is T1 (notes.md only, minimal scaffold): most tasks are small,
|
|
18
|
+
# and a full T3 plan workspace costs thousands of tokens an agent never reads.
|
|
19
|
+
# Escalate explicitly with --tier T2 (behavior change) or T3 (plan-driven).
|
|
20
|
+
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T1}"
|
|
18
21
|
REUSE_EXISTING_RAW="${GUARDEX_BRANCH_START_REUSE_EXISTING:-true}"
|
|
22
|
+
AUTO_TRANSFER_ENABLED_RAW="${GUARDEX_AUTO_TRANSFER:-true}"
|
|
23
|
+
AUTO_TRANSFER_EXCLUDE_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.codex/settings.local.json:.claude/settings.local.json:.codex/state/**:.claude/state/**'
|
|
24
|
+
AUTO_TRANSFER_EXCLUDE_RAW="${GUARDEX_AUTO_TRANSFER_EXCLUDE-$AUTO_TRANSFER_EXCLUDE_DEFAULT}"
|
|
19
25
|
PRINT_NAME_ONLY=0
|
|
20
26
|
POSITIONAL_ARGS=()
|
|
21
27
|
|
|
@@ -36,8 +42,40 @@ run_guardex_cli() {
|
|
|
36
42
|
return 127
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
print_usage() {
|
|
46
|
+
cat <<'USAGE'
|
|
47
|
+
Usage: agent-branch-start [task] [agent] [base] [options]
|
|
48
|
+
|
|
49
|
+
Start an isolated agent/* branch + worktree for a task.
|
|
50
|
+
|
|
51
|
+
Positional:
|
|
52
|
+
task Task name/slug (default: "task")
|
|
53
|
+
agent Agent name (default: "agent")
|
|
54
|
+
base Base branch to fork from (default: repo default)
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--task <name> Task name/slug
|
|
58
|
+
--agent <name> Agent name
|
|
59
|
+
--base <branch> Base branch to fork from
|
|
60
|
+
--worktree-root <p> Worktree root dir (default: .omx/agent-worktrees)
|
|
61
|
+
--reuse-existing Reuse an existing matching worktree (default)
|
|
62
|
+
--new Force a fresh worktree instead of reusing
|
|
63
|
+
--tier <T1|T2|T3> OpenSpec tier for scaffolding (default T1; T2 for a
|
|
64
|
+
behavior change, T3 for plan-driven work)
|
|
65
|
+
--transfer Auto-transfer uncommitted changes into the worktree (default)
|
|
66
|
+
--no-transfer Do not auto-transfer uncommitted changes
|
|
67
|
+
--transfer-exclude <globs> Colon-separated globs to exclude from transfer
|
|
68
|
+
--print-name-only Print the computed branch name and exit
|
|
69
|
+
-h, --help Show this help and exit
|
|
70
|
+
USAGE
|
|
71
|
+
}
|
|
72
|
+
|
|
39
73
|
while [[ $# -gt 0 ]]; do
|
|
40
74
|
case "$1" in
|
|
75
|
+
-h|--help)
|
|
76
|
+
print_usage
|
|
77
|
+
exit 0
|
|
78
|
+
;;
|
|
41
79
|
--task)
|
|
42
80
|
TASK_NAME="${2:-task}"
|
|
43
81
|
shift 2
|
|
@@ -67,6 +105,18 @@ while [[ $# -gt 0 ]]; do
|
|
|
67
105
|
REUSE_EXISTING_RAW="false"
|
|
68
106
|
shift
|
|
69
107
|
;;
|
|
108
|
+
--no-transfer)
|
|
109
|
+
AUTO_TRANSFER_ENABLED_RAW="false"
|
|
110
|
+
shift
|
|
111
|
+
;;
|
|
112
|
+
--transfer)
|
|
113
|
+
AUTO_TRANSFER_ENABLED_RAW="true"
|
|
114
|
+
shift
|
|
115
|
+
;;
|
|
116
|
+
--transfer-exclude)
|
|
117
|
+
AUTO_TRANSFER_EXCLUDE_RAW="${2:-}"
|
|
118
|
+
shift 2
|
|
119
|
+
;;
|
|
70
120
|
--in-place|--allow-in-place)
|
|
71
121
|
echo "[agent-branch-start] In-place branch mode is disabled." >&2
|
|
72
122
|
echo "[agent-branch-start] This command always creates an isolated worktree to keep your active checkout unchanged." >&2
|
|
@@ -277,7 +327,7 @@ normalize_tier() {
|
|
|
277
327
|
esac
|
|
278
328
|
}
|
|
279
329
|
|
|
280
|
-
if ! OPENSPEC_TIER="$(normalize_tier "$OPENSPEC_TIER_RAW" "
|
|
330
|
+
if ! OPENSPEC_TIER="$(normalize_tier "$OPENSPEC_TIER_RAW" "T1")"; then
|
|
281
331
|
echo "[agent-branch-start] Unsupported OpenSpec tier: ${OPENSPEC_TIER_RAW}" >&2
|
|
282
332
|
exit 1
|
|
283
333
|
fi
|
|
@@ -389,11 +439,41 @@ print_reused_agent_worktree() {
|
|
|
389
439
|
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
390
440
|
echo "[agent-branch-start] OpenSpec change: existing worktree"
|
|
391
441
|
echo "[agent-branch-start] OpenSpec plan: existing worktree"
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
442
|
+
print_agent_next_steps "$branch_name" "$worktree_path" "continue work in this existing sandbox" "$BASE_BRANCH"
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
print_agent_next_steps() {
|
|
446
|
+
local branch_name="$1"
|
|
447
|
+
local worktree_path="$2"
|
|
448
|
+
local work_step="$3"
|
|
449
|
+
local base_branch="${4:-main}"
|
|
450
|
+
|
|
451
|
+
if [[ -z "$base_branch" ]]; then
|
|
452
|
+
base_branch="main"
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
# Pre-`Ready:` post-condition check. `git worktree add` has been observed
|
|
456
|
+
# to return 0 with the worktree dir still missing on disk (race condition
|
|
457
|
+
# during the OpenSpec / dependency-symlink phase between `worktree add`
|
|
458
|
+
# and now). If we print `Ready:` in that state, callers cd into a
|
|
459
|
+
# vanished directory and lose subsequent edits silently. Surface it as
|
|
460
|
+
# an exit-1 error instead — operator can retry + `git worktree prune`.
|
|
461
|
+
if [[ ! -d "$worktree_path" || ! -e "$worktree_path/.git" ]]; then
|
|
462
|
+
printf '[agent-branch-start] ERROR: worktree did not materialize on disk before Ready:\n' >&2
|
|
463
|
+
printf '[agent-branch-start] branch: %s\n' "$branch_name" >&2
|
|
464
|
+
printf '[agent-branch-start] expected: %s\n' "$worktree_path" >&2
|
|
465
|
+
printf '[agent-branch-start] Run `git worktree prune` to clear ghost entries, then retry.\n' >&2
|
|
466
|
+
exit 1
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
echo "[agent-branch-start] Ready:"
|
|
470
|
+
echo " branch: ${branch_name}"
|
|
471
|
+
echo " worktree: ${worktree_path}"
|
|
472
|
+
echo " next:"
|
|
473
|
+
echo " cd \"${worktree_path}\""
|
|
474
|
+
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
475
|
+
echo " # ${work_step}"
|
|
476
|
+
echo " gx branch finish --branch \"${branch_name}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
|
|
397
477
|
}
|
|
398
478
|
|
|
399
479
|
has_local_changes() {
|
|
@@ -419,7 +499,7 @@ meaningful_slug_tokens() {
|
|
|
419
499
|
| awk '
|
|
420
500
|
length($0) < 4 { next }
|
|
421
501
|
$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 }
|
|
502
|
+
$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
503
|
!seen[$0]++ { print }
|
|
424
504
|
'
|
|
425
505
|
}
|
|
@@ -463,6 +543,25 @@ managed_worktree_roots() {
|
|
|
463
543
|
done
|
|
464
544
|
}
|
|
465
545
|
|
|
546
|
+
branch_published_then_remote_pruned() {
|
|
547
|
+
# Detect the post-`gx branch finish --via-pr --cleanup` state: the agent
|
|
548
|
+
# branch was published (so upstream config is set), but the remote-tracking
|
|
549
|
+
# ref no longer exists (because `push origin --delete` ran during cleanup).
|
|
550
|
+
# That combination only arises after a finish that successfully merged the
|
|
551
|
+
# PR and pruned the remote branch — a freshly-created agent branch never
|
|
552
|
+
# has an upstream until publish, so this never false-positives on the
|
|
553
|
+
# "started, dirty, no commits yet" case that we want to keep reusable.
|
|
554
|
+
local repo="$1"
|
|
555
|
+
local branch="$2"
|
|
556
|
+
local upstream
|
|
557
|
+
upstream="$(git -C "$repo" config --get "branch.${branch}.remote" 2>/dev/null || true)"
|
|
558
|
+
[[ -n "$upstream" ]] || return 1
|
|
559
|
+
if git -C "$repo" show-ref --verify --quiet "refs/remotes/${upstream}/${branch}"; then
|
|
560
|
+
return 1
|
|
561
|
+
fi
|
|
562
|
+
return 0
|
|
563
|
+
}
|
|
564
|
+
|
|
466
565
|
find_matching_dirty_agent_worktree() {
|
|
467
566
|
local repo="$1"
|
|
468
567
|
local worktree_root_rel="$2"
|
|
@@ -483,6 +582,13 @@ find_matching_dirty_agent_worktree() {
|
|
|
483
582
|
fi
|
|
484
583
|
[[ "$branch" == "agent/${agent_slug}/"* ]] || continue
|
|
485
584
|
has_local_changes "$entry" || continue
|
|
585
|
+
# Skip merged-and-cleaned worktrees that happen to still be on disk
|
|
586
|
+
# (e.g. operator's shell is cwd'd inside; cleanup deferred). Reusing
|
|
587
|
+
# such a worktree would silently hand the next agent a stale HEAD.
|
|
588
|
+
if branch_published_then_remote_pruned "$repo" "$branch"; then
|
|
589
|
+
echo "[agent-branch-start] Skipping merged-and-cleaned worktree: ${entry} (branch ${branch} has no remote tracking ref)" >&2
|
|
590
|
+
continue
|
|
591
|
+
fi
|
|
486
592
|
|
|
487
593
|
descriptor="${branch#agent/${agent_slug}/}"
|
|
488
594
|
score="$(token_match_score "$task_slug" "$descriptor")"
|
|
@@ -774,18 +880,54 @@ restore_auto_transfer_stash_on_failure() {
|
|
|
774
880
|
|
|
775
881
|
trap 'restore_auto_transfer_stash_on_failure "$?"' EXIT
|
|
776
882
|
|
|
883
|
+
auto_transfer_enabled_lc="$(printf '%s' "$AUTO_TRANSFER_ENABLED_RAW" | tr '[:upper:]' '[:lower:]')"
|
|
884
|
+
case "$auto_transfer_enabled_lc" in
|
|
885
|
+
0|false|no|off) AUTO_TRANSFER_ENABLED=0 ;;
|
|
886
|
+
*) AUTO_TRANSFER_ENABLED=1 ;;
|
|
887
|
+
esac
|
|
888
|
+
|
|
889
|
+
build_auto_transfer_stash_argv() {
|
|
890
|
+
# Emit NUL-separated argv: --include-untracked --message <msg> [-- :/ :(exclude,glob)PAT ...]
|
|
891
|
+
local msg="$1"
|
|
892
|
+
printf '%s\0' --include-untracked --message "$msg"
|
|
893
|
+
if [[ -z "${AUTO_TRANSFER_EXCLUDE_RAW:-}" ]]; then
|
|
894
|
+
return 0
|
|
895
|
+
fi
|
|
896
|
+
printf '%s\0' '--' ':/'
|
|
897
|
+
local -a patterns=()
|
|
898
|
+
IFS=':' read -ra patterns <<< "$AUTO_TRANSFER_EXCLUDE_RAW"
|
|
899
|
+
local pattern
|
|
900
|
+
for pattern in "${patterns[@]}"; do
|
|
901
|
+
[[ -z "$pattern" ]] && continue
|
|
902
|
+
printf '%s\0' ":(exclude,glob)${pattern}"
|
|
903
|
+
done
|
|
904
|
+
}
|
|
905
|
+
|
|
777
906
|
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
778
907
|
protected_branches_raw="$(resolve_protected_branches "$repo_root")"
|
|
779
908
|
if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
|
|
780
909
|
if has_local_changes "$repo_root"; then
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
910
|
+
if [[ "$AUTO_TRANSFER_ENABLED" -eq 0 ]]; then
|
|
911
|
+
echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'; --no-transfer set, leaving them in place." >&2
|
|
912
|
+
echo "[agent-branch-start] If you intended those changes for '${branch_name}', stash them manually and apply inside the worktree." >&2
|
|
913
|
+
else
|
|
914
|
+
auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
|
|
915
|
+
stash_argv=()
|
|
916
|
+
while IFS= read -r -d '' arg; do
|
|
917
|
+
stash_argv+=("$arg")
|
|
918
|
+
done < <(build_auto_transfer_stash_argv "$auto_transfer_message")
|
|
919
|
+
if git -C "$repo_root" stash push "${stash_argv[@]}" >/dev/null 2>&1; then
|
|
920
|
+
auto_transfer_stash_ref="$(resolve_stash_ref_by_message "$repo_root" "$auto_transfer_message")"
|
|
921
|
+
if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
922
|
+
auto_transfer_source_branch="$current_branch"
|
|
923
|
+
echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}' (state-file globs excluded)..."
|
|
924
|
+
if ! maybe_fail_after_auto_transfer_stash; then
|
|
925
|
+
exit 1
|
|
926
|
+
fi
|
|
927
|
+
else
|
|
928
|
+
if has_local_changes "$repo_root"; then
|
|
929
|
+
echo "[agent-branch-start] Local changes on '${current_branch}' all match the auto-transfer exclude list; leaving them in place on '${current_branch}'." >&2
|
|
930
|
+
fi
|
|
789
931
|
fi
|
|
790
932
|
fi
|
|
791
933
|
fi
|
|
@@ -797,6 +939,13 @@ if ! worktree_add_output="$(git -C "$repo_root" worktree add -b "$branch_name" "
|
|
|
797
939
|
printf '%s\n' "$worktree_add_output" >&2
|
|
798
940
|
exit 1
|
|
799
941
|
fi
|
|
942
|
+
# git worktree add has been observed to exit 0 while the target dir is
|
|
943
|
+
# missing — verify before downstream init runs against a phantom path.
|
|
944
|
+
if [[ ! -d "$worktree_path" || ! -e "$worktree_path/.git" ]]; then
|
|
945
|
+
printf '[agent-branch-start] ERROR: git worktree add reported success but %s is not a valid worktree.\n' "$worktree_path" >&2
|
|
946
|
+
printf '%s\n' "$worktree_add_output" >&2
|
|
947
|
+
exit 1
|
|
948
|
+
fi
|
|
800
949
|
git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
801
950
|
git -C "$repo_root" config "branch.${branch_name}.guardexWorktreeRoot" "$WORKTREE_ROOT_REL" >/dev/null 2>&1 || true
|
|
802
951
|
# Fresh agent branches should start unpublished; clear any inherited base-branch tracking.
|
|
@@ -832,18 +981,21 @@ fi
|
|
|
832
981
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
833
982
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
834
983
|
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
835
|
-
if [[ "$
|
|
984
|
+
if [[ "$OPENSPEC_TIER" == "T1" ]]; then
|
|
985
|
+
echo "[agent-branch-start] T1 minimal scaffold (notes.md). Escalate: --tier T2 for a behavior change, T3 for plan-driven work."
|
|
986
|
+
fi
|
|
987
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
988
|
+
echo "[agent-branch-start] OpenSpec change: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
|
|
989
|
+
elif [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
836
990
|
echo "[agent-branch-start] OpenSpec change: skipped by tier ${OPENSPEC_TIER}"
|
|
837
991
|
else
|
|
838
992
|
echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
|
|
839
993
|
fi
|
|
840
|
-
if [[ "$
|
|
994
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
995
|
+
echo "[agent-branch-start] OpenSpec plan: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
|
|
996
|
+
elif [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
|
|
841
997
|
echo "[agent-branch-start] OpenSpec plan: skipped by tier ${OPENSPEC_TIER}"
|
|
842
998
|
else
|
|
843
999
|
echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
|
|
844
1000
|
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"
|
|
1001
|
+
print_agent_next_steps "$branch_name" "$worktree_path" "implement + commit" "$BASE_BRANCH"
|
|
@@ -0,0 +1,115 @@
|
|
|
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 # steps that PASSED
|
|
26
|
+
attempted=0 # steps that RAN (pass or fail) — drives stack detection
|
|
27
|
+
fail=0
|
|
28
|
+
# Quiet by default: a green `npm test` run can be hundreds of lines of TAP that
|
|
29
|
+
# floods the agent's context on every `gx branch finish`. Capture each step's
|
|
30
|
+
# output, print a one-line summary on success, and surface only the tail on
|
|
31
|
+
# failure (where it is actually useful). Stream full output live with
|
|
32
|
+
# GUARDEX_PREFLIGHT_VERBOSE=1.
|
|
33
|
+
GUARDEX_PREFLIGHT_FAIL_TAIL="${GUARDEX_PREFLIGHT_FAIL_TAIL:-40}"
|
|
34
|
+
run_step() {
|
|
35
|
+
local label="$1"
|
|
36
|
+
shift
|
|
37
|
+
echo "[agent-preflight] -> $label"
|
|
38
|
+
attempted=$((attempted + 1))
|
|
39
|
+
if [[ "${GUARDEX_PREFLIGHT_VERBOSE:-0}" == "1" ]]; then
|
|
40
|
+
if "$@"; then
|
|
41
|
+
ran=$((ran + 1))
|
|
42
|
+
echo "[agent-preflight] ok"
|
|
43
|
+
else
|
|
44
|
+
echo "[agent-preflight] FAIL: $label" >&2
|
|
45
|
+
fail=1
|
|
46
|
+
fi
|
|
47
|
+
return 0
|
|
48
|
+
fi
|
|
49
|
+
local out rc
|
|
50
|
+
# `if` keeps `set -e` from aborting on a failing step before we capture rc.
|
|
51
|
+
if out="$("$@" 2>&1)"; then
|
|
52
|
+
rc=0
|
|
53
|
+
else
|
|
54
|
+
rc=$?
|
|
55
|
+
fi
|
|
56
|
+
if [[ "$rc" -eq 0 ]]; then
|
|
57
|
+
ran=$((ran + 1))
|
|
58
|
+
echo "[agent-preflight] ok ($(printf '%s\n' "$out" | wc -l | tr -d ' ') lines suppressed; GUARDEX_PREFLIGHT_VERBOSE=1 to show)"
|
|
59
|
+
else
|
|
60
|
+
echo "[agent-preflight] FAIL: $label (exit $rc) — last ${GUARDEX_PREFLIGHT_FAIL_TAIL} lines:" >&2
|
|
61
|
+
printf '%s\n' "$out" | tail -n "$GUARDEX_PREFLIGHT_FAIL_TAIL" >&2
|
|
62
|
+
fail=1
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
has_package_script() {
|
|
67
|
+
local script_name="$1"
|
|
68
|
+
[[ -f package.json ]] || return 1
|
|
69
|
+
grep -E "\"${script_name}\"\\s*:" package.json >/dev/null 2>&1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Node detection
|
|
73
|
+
if [[ -f package.json ]]; then
|
|
74
|
+
pkg_manager=""
|
|
75
|
+
if command -v pnpm >/dev/null 2>&1 && [[ -f pnpm-lock.yaml ]]; then
|
|
76
|
+
pkg_manager="pnpm"
|
|
77
|
+
elif command -v npm >/dev/null 2>&1 && [[ -f package-lock.json ]]; then
|
|
78
|
+
pkg_manager="npm"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
case "$pkg_manager" in
|
|
82
|
+
pnpm)
|
|
83
|
+
has_package_script typecheck && run_step "pnpm typecheck" pnpm typecheck
|
|
84
|
+
has_package_script lint && run_step "pnpm lint" pnpm lint
|
|
85
|
+
has_package_script test && run_step "pnpm test" pnpm test
|
|
86
|
+
;;
|
|
87
|
+
npm)
|
|
88
|
+
has_package_script test && run_step "npm test" npm test
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Rust detection
|
|
94
|
+
if [[ -f Cargo.toml ]] && command -v cargo >/dev/null 2>&1; then
|
|
95
|
+
run_step "cargo check" cargo check --quiet
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Python detection (ruff if available; pytest is too project-specific to default)
|
|
99
|
+
if [[ -f pyproject.toml ]] && command -v ruff >/dev/null 2>&1; then
|
|
100
|
+
run_step "ruff check" ruff check .
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [[ "$attempted" -eq 0 ]]; then
|
|
104
|
+
echo "[agent-preflight] No recognized project stack detected; skipping checks." >&2
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
if [[ "$fail" -ne 0 ]]; then
|
|
109
|
+
echo "[agent-preflight] Verification failed; refusing push." >&2
|
|
110
|
+
echo "[agent-preflight] Fix the issues, or re-run with: gx branch finish --no-preflight ..." >&2
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
echo "[agent-preflight] ${ran} step(s) passed."
|
|
115
|
+
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/**:.codex/settings.local.json:.claude/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
|
|