@imdeadpool/guardex 6.1.0 → 7.0.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.
@@ -6,17 +6,10 @@ AGENT_NAME="agent"
6
6
  BASE_BRANCH=""
7
7
  BASE_BRANCH_EXPLICIT=0
8
8
  WORKTREE_ROOT_REL=".omx/agent-worktrees"
9
- OPENSPEC_AUTO_INIT_RAW="${GX_OPENSPEC_AUTO_INIT:-${GUARDEX_OPENSPEC_AUTO_INIT:-true}}"
10
- TIER_LEVEL_RAW="${GUARDEX_TIER:-}"
11
- TIER_LEVEL_EXPLICIT=0
12
- GH_SYNC_ON_START_RAW="${GUARDEX_GH_SYNC_ON_START:-true}"
13
- MIGRATE_PROTECTED_CHANGES_RAW="${GUARDEX_MIGRATE_PROTECTED_CHANGES:-true}"
9
+ OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-false}"
14
10
  OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
15
11
  OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
16
12
  OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
17
- PR_REF="${GUARDEX_GH_PR_REF:-}"
18
- GH_REPO_REF="${GUARDEX_GH_REPO:-}"
19
- PRINT_NAME_ONLY=0
20
13
  POSITIONAL_ARGS=()
21
14
 
22
15
  while [[ $# -gt 0 ]]; do
@@ -43,31 +36,6 @@ while [[ $# -gt 0 ]]; do
43
36
  WORKTREE_ROOT_REL="${2:-.omx/agent-worktrees}"
44
37
  shift 2
45
38
  ;;
46
- --pr)
47
- PR_REF="${2:-}"
48
- shift 2
49
- ;;
50
- --repo)
51
- GH_REPO_REF="${2:-}"
52
- shift 2
53
- ;;
54
- --gh-sync)
55
- GH_SYNC_ON_START_RAW="true"
56
- shift
57
- ;;
58
- --no-gh-sync)
59
- GH_SYNC_ON_START_RAW="false"
60
- shift
61
- ;;
62
- --print-name-only)
63
- PRINT_NAME_ONLY=1
64
- shift
65
- ;;
66
- --tier)
67
- TIER_LEVEL_RAW="${2:-}"
68
- TIER_LEVEL_EXPLICIT=1
69
- shift 2
70
- ;;
71
39
  --)
72
40
  shift
73
41
  while [[ $# -gt 0 ]]; do
@@ -78,7 +46,7 @@ while [[ $# -gt 0 ]]; do
78
46
  ;;
79
47
  -*)
80
48
  echo "[agent-branch-start] Unknown option: $1" >&2
81
- echo "Usage: $0 [task] [agent] [base] [--tier T0|T1|T2|T3] [--worktree-root <path>] [--pr <ref>] [--repo <owner/name>] [--gh-sync|--no-gh-sync]" >&2
49
+ echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>]" >&2
82
50
  exit 1
83
51
  ;;
84
52
  *)
@@ -90,7 +58,7 @@ done
90
58
 
91
59
  if [[ "${#POSITIONAL_ARGS[@]}" -gt 3 ]]; then
92
60
  echo "[agent-branch-start] Too many positional arguments." >&2
93
- echo "Usage: $0 [task] [agent] [base] [--tier T0|T1|T2|T3] [--worktree-root <path>] [--pr <ref>] [--repo <owner/name>] [--gh-sync|--no-gh-sync]" >&2
61
+ echo "Usage: $0 [task] [agent] [base] [--worktree-root <path>]" >&2
94
62
  exit 1
95
63
  fi
96
64
 
@@ -163,122 +131,21 @@ checksum_slug_suffix() {
163
131
  }
164
132
 
165
133
  compose_branch_descriptor() {
166
- local task_slug="$1"
167
- local task_max task_part checksum_part
134
+ local snapshot_slug="$1"
135
+ local task_slug="$2"
136
+ local snapshot_max task_max task_part snapshot_part checksum_input checksum_part
137
+ snapshot_max="$(normalize_positive_int "${GUARDEX_BRANCH_SNAPSHOT_SLUG_MAX:-18}" "18")"
168
138
  task_max="$(normalize_positive_int "${GUARDEX_BRANCH_TASK_SLUG_MAX:-36}" "36")"
169
139
  task_part="$(shorten_slug "$task_slug" "$task_max")"
170
- checksum_part="$(checksum_slug_suffix "$task_slug")"
171
- printf '%s-%s' "$task_part" "$checksum_part"
172
- }
173
-
174
- normalize_agent_type() {
175
- local raw="${1:-}"
176
- local lowered
177
- if [[ -n "${GUARDEX_AGENT_TYPE:-}" ]]; then
178
- printf '%s' "${GUARDEX_AGENT_TYPE}"
179
- return 0
180
- fi
181
- lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
182
- if [[ "$lowered" == *codex* ]]; then
183
- printf 'codex'
184
- return 0
185
- fi
186
- if [[ "$lowered" == *claude* ]]; then
187
- printf 'claude'
188
- return 0
189
- fi
190
- if [[ -n "${CLAUDECODE:-}" || -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
191
- printf 'claude'
192
- return 0
193
- fi
194
- if [[ -n "${CODEX_CLI:-}" ]]; then
195
- printf 'codex'
196
- return 0
197
- fi
198
- printf 'codex'
199
- }
200
-
201
- lookup_nickname_map() {
202
- local key="$1"
203
- local map_env="${GUARDEX_EMAIL_NICKNAME_MAP:-}"
204
- local map_file="${repo_root:-$(pwd)}/.agents/nickname-map.json"
205
- local value=""
206
- if ! command -v python3 >/dev/null 2>&1; then
207
- printf ''
208
- return 0
209
- fi
210
- if [[ -n "$map_env" ]]; then
211
- value="$(KEY="$key" MAP="$map_env" python3 -c '
212
- import json, os
213
- try:
214
- data = json.loads(os.environ.get("MAP") or "{}")
215
- print(data.get(os.environ.get("KEY", ""), ""))
216
- except Exception:
217
- pass
218
- ' 2>/dev/null || true)"
219
- fi
220
- if [[ -z "$value" && -f "$map_file" ]]; then
221
- value="$(KEY="$key" FILE="$map_file" python3 -c '
222
- import json, os
223
- try:
224
- with open(os.environ["FILE"]) as f:
225
- data = json.load(f)
226
- print(data.get(os.environ.get("KEY", ""), ""))
227
- except Exception:
228
- pass
229
- ' 2>/dev/null || true)"
230
- fi
231
- printf '%s' "$value"
232
- }
233
-
234
- time_fallback_nickname() {
235
- # Current wall-clock HH-MM used when no codex-auth nickname is resolvable
236
- # (typical for Claude sessions). Produces visually distinct, sortable
237
- # branch slugs instead of the generic "none" marker. Tests override via
238
- # GUARDEX_NICKNAME_TIME_FALLBACK.
239
- if [[ -n "${GUARDEX_NICKNAME_TIME_FALLBACK:-}" ]]; then
240
- printf '%s' "${GUARDEX_NICKNAME_TIME_FALLBACK}"
241
- return 0
242
- fi
243
- printf '%s' "$(date +%H-%M)"
244
- }
245
-
246
- extract_nickname() {
247
- local raw="${1:-}"
248
- local lowered candidate mapped fallback use_account_raw use_account
249
- fallback="$(time_fallback_nickname)"
250
- # Default: always use the HH-MM time fallback so agent branches are
251
- # distinguishable by creation time (e.g. `agent/claude-00-38/…`) instead of
252
- # reflecting whichever codex-auth account happens to be active. Opt back in
253
- # to the old account-nickname behavior with GUARDEX_USE_ACCOUNT_NICKNAME=1.
254
- use_account_raw="${GUARDEX_USE_ACCOUNT_NICKNAME:-0}"
255
- use_account="$(printf '%s' "$use_account_raw" | tr '[:upper:]' '[:lower:]')"
256
- case "$use_account" in
257
- 1|true|yes|on) use_account=1 ;;
258
- *) use_account=0 ;;
259
- esac
260
- if [[ "$use_account" -ne 1 || -z "$raw" ]]; then
261
- printf '%s' "$fallback"
262
- return 0
263
- fi
264
- lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
265
- mapped="$(lookup_nickname_map "$lowered")"
266
- if [[ -n "$mapped" ]]; then
267
- sanitize_slug "$mapped" "$fallback"
140
+ if [[ -n "$snapshot_slug" ]]; then
141
+ snapshot_part="$(shorten_slug "$snapshot_slug" "$snapshot_max")"
142
+ checksum_input="${snapshot_slug}--${task_slug}"
143
+ checksum_part="$(checksum_slug_suffix "$checksum_input")"
144
+ printf '%s-%s-%s' "$snapshot_part" "$task_part" "$checksum_part"
268
145
  return 0
269
146
  fi
270
- if [[ "$lowered" == *"@"* ]]; then
271
- candidate="${lowered%%@*}"
272
- elif [[ "$lowered" == *-* ]]; then
273
- candidate="${lowered%%-*}"
274
- else
275
- candidate="$lowered"
276
- fi
277
- candidate="$(sanitize_slug "$candidate" "$fallback")"
278
- if [[ -z "$candidate" || "$candidate" == "none" ]]; then
279
- candidate="$fallback"
280
- fi
281
- printf '%s' "$candidate"
147
+ checksum_part="$(checksum_slug_suffix "$task_slug")"
148
+ printf '%s-%s' "$task_part" "$checksum_part"
282
149
  }
283
150
 
284
151
  normalize_bool() {
@@ -295,63 +162,6 @@ normalize_bool() {
295
162
  }
296
163
 
297
164
  OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
298
- GH_SYNC_ON_START="$(normalize_bool "$GH_SYNC_ON_START_RAW" "1")"
299
- MIGRATE_PROTECTED_CHANGES="$(normalize_bool "$MIGRATE_PROTECTED_CHANGES_RAW" "1")"
300
-
301
- normalize_tier() {
302
- local raw="${1:-}"
303
- local upper
304
- upper="$(printf '%s' "$raw" | tr '[:lower:]' '[:upper:]')"
305
- case "$upper" in
306
- T0|T1|T2|T3) printf '%s' "$upper" ;;
307
- 0) printf 'T0' ;;
308
- 1) printf 'T1' ;;
309
- 2) printf 'T2' ;;
310
- 3) printf 'T3' ;;
311
- '') printf 'T3' ;;
312
- *)
313
- echo "[agent-branch-start] Unknown tier: ${raw} (expected T0|T1|T2|T3)" >&2
314
- return 1
315
- ;;
316
- esac
317
- }
318
-
319
- if ! TIER_LEVEL="$(normalize_tier "$TIER_LEVEL_RAW")"; then
320
- exit 1
321
- fi
322
-
323
- case "$TIER_LEVEL" in
324
- T0)
325
- OPENSPEC_CHANGE_INIT=0
326
- OPENSPEC_PLAN_INIT=0
327
- OPENSPEC_CHANGE_MINIMAL=0
328
- ;;
329
- T1)
330
- OPENSPEC_CHANGE_INIT=1
331
- OPENSPEC_PLAN_INIT=0
332
- OPENSPEC_CHANGE_MINIMAL=1
333
- ;;
334
- T2)
335
- OPENSPEC_CHANGE_INIT=1
336
- OPENSPEC_PLAN_INIT=0
337
- OPENSPEC_CHANGE_MINIMAL=0
338
- ;;
339
- T3)
340
- OPENSPEC_CHANGE_INIT=1
341
- OPENSPEC_PLAN_INIT=1
342
- OPENSPEC_CHANGE_MINIMAL=0
343
- ;;
344
- esac
345
-
346
- # Explicit tier overrides the legacy OPENSPEC_AUTO_INIT env var; otherwise keep legacy behavior.
347
- if [[ "$TIER_LEVEL_EXPLICIT" -eq 1 ]]; then
348
- OPENSPEC_AUTO_INIT="$OPENSPEC_CHANGE_INIT"
349
- fi
350
-
351
- is_helper_agent_base_branch() {
352
- local base_branch="$1"
353
- [[ "$base_branch" == agent/* ]]
354
- }
355
165
 
356
166
  resolve_openspec_plan_slug() {
357
167
  local branch_name="$1"
@@ -414,54 +224,6 @@ has_local_changes() {
414
224
  return 1
415
225
  }
416
226
 
417
- migrate_local_changes_to_worktree() {
418
- local root="$1"
419
- local source_branch="$2"
420
- local target_branch="$3"
421
- local target_worktree="$4"
422
- local stash_label stash_output stash_status apply_output apply_status
423
- local stash_ref="stash@{0}"
424
-
425
- if ! has_local_changes "$root"; then
426
- return 0
427
- fi
428
-
429
- stash_label="guardex-migrate-${source_branch//\//-}-to-${target_branch//\//-}-$(date -u +%Y%m%dT%H%M%SZ)"
430
-
431
- set +e
432
- stash_output="$(git -C "$root" stash push --include-untracked --message "$stash_label" 2>&1)"
433
- stash_status=$?
434
- set -e
435
- if [[ "$stash_status" -ne 0 ]]; then
436
- echo "[agent-branch-start] Failed to stash protected-branch changes before migration." >&2
437
- printf '%s\n' "$stash_output" >&2
438
- return 1
439
- fi
440
-
441
- if printf '%s\n' "$stash_output" | grep -qi "No local changes to save"; then
442
- return 0
443
- fi
444
-
445
- set +e
446
- apply_output="$(git -C "$target_worktree" stash apply --index "$stash_ref" 2>&1)"
447
- apply_status=$?
448
- set -e
449
- if [[ "$apply_status" -ne 0 ]]; then
450
- echo "[agent-branch-start] Created stash ${stash_ref} but could not auto-apply into worktree '${target_worktree}'." >&2
451
- printf '%s\n' "$apply_output" >&2
452
- echo "[agent-branch-start] Manual recovery options:" >&2
453
- echo " # apply in sandbox worktree" >&2
454
- echo " git -C \"$target_worktree\" stash apply --index ${stash_ref}" >&2
455
- echo " # or restore on primary checkout if needed" >&2
456
- echo " git -C \"$root\" stash apply --index ${stash_ref}" >&2
457
- return 1
458
- fi
459
-
460
- git -C "$root" stash drop "$stash_ref" >/dev/null 2>&1 || true
461
- echo "[agent-branch-start] Migrated local changes from '${source_branch}' into '${target_branch}' (${target_worktree})."
462
- return 0
463
- }
464
-
465
227
  resolve_protected_branches() {
466
228
  local root="$1"
467
229
  local raw
@@ -484,51 +246,6 @@ is_protected_branch_name() {
484
246
  return 1
485
247
  }
486
248
 
487
- branch_exists_locally_or_on_origin() {
488
- local root="$1"
489
- local branch="$2"
490
- if git -C "$root" show-ref --verify --quiet "refs/heads/${branch}"; then
491
- return 0
492
- fi
493
- if git -C "$root" show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
494
- return 0
495
- fi
496
- return 1
497
- }
498
-
499
- resolve_default_base_branch_for_agent_subbranch() {
500
- local root="$1"
501
- local protected_raw="$2"
502
- local configured_base candidate
503
-
504
- configured_base="$(git -C "$root" config --get multiagent.baseBranch || true)"
505
- if [[ -n "$configured_base" ]] && branch_exists_locally_or_on_origin "$root" "$configured_base"; then
506
- printf '%s' "$configured_base"
507
- return 0
508
- fi
509
-
510
- for candidate in $protected_raw; do
511
- if branch_exists_locally_or_on_origin "$root" "$candidate"; then
512
- printf '%s' "$candidate"
513
- return 0
514
- fi
515
- done
516
-
517
- if branch_exists_locally_or_on_origin "$root" "dev"; then
518
- printf 'dev'
519
- return 0
520
- fi
521
- if branch_exists_locally_or_on_origin "$root" "main"; then
522
- printf 'main'
523
- return 0
524
- fi
525
- if branch_exists_locally_or_on_origin "$root" "master"; then
526
- printf 'master'
527
- return 0
528
- fi
529
- return 1
530
- }
531
-
532
249
  hydrate_local_helper_in_worktree() {
533
250
  local repo="$1"
534
251
  local worktree="$2"
@@ -586,7 +303,7 @@ initialize_openspec_plan_workspace() {
586
303
 
587
304
  hydrate_local_helper_in_worktree "$repo" "$worktree" "scripts/openspec/init-plan-workspace.sh"
588
305
 
589
- if [[ "${OPENSPEC_PLAN_INIT:-$OPENSPEC_AUTO_INIT}" -ne 1 ]]; then
306
+ if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
590
307
  return 0
591
308
  fi
592
309
 
@@ -621,7 +338,6 @@ initialize_openspec_change_workspace() {
621
338
  local worktree="$2"
622
339
  local change_slug="$3"
623
340
  local capability_slug="$4"
624
- local branch_name="${5:-}"
625
341
 
626
342
  hydrate_local_helper_in_worktree "$repo" "$worktree" "scripts/openspec/init-change-workspace.sh"
627
343
 
@@ -642,8 +358,7 @@ initialize_openspec_change_workspace() {
642
358
  local init_output=""
643
359
  if ! init_output="$(
644
360
  cd "$worktree"
645
- GUARDEX_OPENSPEC_MINIMAL="${OPENSPEC_CHANGE_MINIMAL:-0}" \
646
- bash "scripts/openspec/init-change-workspace.sh" "$change_slug" "$capability_slug" "$branch_name" 2>&1
361
+ bash "scripts/openspec/init-change-workspace.sh" "$change_slug" "$capability_slug" 2>&1
647
362
  )"; then
648
363
  printf '%s\n' "$init_output" >&2
649
364
  echo "[agent-branch-start] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
@@ -656,459 +371,6 @@ initialize_openspec_change_workspace() {
656
371
  echo "[agent-branch-start] OpenSpec change workspace: ${worktree}/openspec/changes/${change_slug}"
657
372
  }
658
373
 
659
- filtered_status_output() {
660
- local wt="$1"
661
- git -C "$wt" status --porcelain --untracked-files=normal -- \
662
- . \
663
- ":(exclude).omx/state/agent-file-locks.json" \
664
- ":(exclude).dev-ports.json" \
665
- ":(exclude)apps/logs/*.log"
666
- }
667
-
668
- resolve_worktree_git_dir() {
669
- local wt="$1"
670
- local git_dir=""
671
- git_dir="$(git -C "$wt" rev-parse --git-dir 2>/dev/null || true)"
672
- if [[ -z "$git_dir" ]]; then
673
- return 1
674
- fi
675
- if [[ "$git_dir" == /* ]]; then
676
- git_dir="$(cd "$git_dir" 2>/dev/null && pwd -P || true)"
677
- else
678
- git_dir="$(cd "$wt/$git_dir" 2>/dev/null && pwd -P || true)"
679
- fi
680
- if [[ -z "$git_dir" ]]; then
681
- return 1
682
- fi
683
- printf '%s' "$git_dir"
684
- }
685
-
686
- bootstrap_manifest_path_for_worktree() {
687
- local wt="$1"
688
- local git_dir=""
689
- git_dir="$(resolve_worktree_git_dir "$wt" || true)"
690
- if [[ -z "$git_dir" ]]; then
691
- return 1
692
- fi
693
- printf '%s/guardex-bootstrap-manifest.json' "$git_dir"
694
- }
695
-
696
- record_worktree_bootstrap_manifest() {
697
- local worktree="$1"
698
- local branch="$2"
699
- local base_branch="$3"
700
- local change_slug="$4"
701
- local plan_slug="$5"
702
- local tier="${6:-T3}"
703
- local manifest_path=""
704
- local status_output=""
705
-
706
- manifest_path="$(bootstrap_manifest_path_for_worktree "$worktree" || true)"
707
- if [[ -z "$manifest_path" ]]; then
708
- return 0
709
- fi
710
-
711
- status_output="$(filtered_status_output "$worktree")"
712
- STATUS_OUTPUT="$status_output" python3 - "$worktree" "$manifest_path" "$branch" "$base_branch" "$change_slug" "$plan_slug" "$tier" <<'PY'
713
- from __future__ import annotations
714
-
715
- import hashlib
716
- import json
717
- import os
718
- import sys
719
- from datetime import datetime, timezone
720
- from pathlib import Path
721
-
722
-
723
- def parse_status_paths(raw: str) -> list[str]:
724
- paths: list[str] = []
725
- for line in raw.splitlines():
726
- if len(line) < 4:
727
- continue
728
- path_part = line[3:]
729
- if " -> " in path_part:
730
- path_part = path_part.split(" -> ", 1)[1]
731
- path_part = path_part.strip()
732
- if path_part:
733
- paths.append(path_part)
734
- return sorted(set(paths))
735
-
736
-
737
- def sha256_for_file(path: Path) -> str | None:
738
- if not path.exists() or not path.is_file():
739
- return None
740
- digest = hashlib.sha256()
741
- with path.open("rb") as handle:
742
- for chunk in iter(lambda: handle.read(65536), b""):
743
- digest.update(chunk)
744
- return digest.hexdigest()
745
-
746
-
747
- if len(sys.argv) != 8:
748
- sys.exit(1)
749
-
750
- worktree_root = Path(sys.argv[1])
751
- manifest_path = Path(sys.argv[2])
752
- branch_name = sys.argv[3]
753
- base_branch = sys.argv[4]
754
- change_slug = sys.argv[5]
755
- plan_slug = sys.argv[6]
756
- tier = sys.argv[7] or "T3"
757
-
758
- status_paths = parse_status_paths(os.environ.get("STATUS_OUTPUT", ""))
759
- entries: list[dict[str, object]] = []
760
- for rel_path in status_paths:
761
- file_path = worktree_root / rel_path
762
- entries.append(
763
- {
764
- "path": rel_path,
765
- "sha256": sha256_for_file(file_path),
766
- }
767
- )
768
-
769
- payload = {
770
- "version": 1,
771
- "generatedAt": datetime.now(timezone.utc).isoformat(),
772
- "branch": branch_name,
773
- "baseBranch": base_branch,
774
- "changeSlug": change_slug,
775
- "planSlug": plan_slug,
776
- "tier": tier,
777
- "files": entries,
778
- }
779
-
780
- manifest_path.parent.mkdir(parents=True, exist_ok=True)
781
- manifest_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
782
- PY
783
- echo "[agent-branch-start] Bootstrap manifest: ${manifest_path}"
784
- }
785
-
786
- worktree_matches_bootstrap_manifest() {
787
- local worktree="$1"
788
- local manifest_path=""
789
- local status_output=""
790
-
791
- manifest_path="$(bootstrap_manifest_path_for_worktree "$worktree" || true)"
792
- if [[ -z "$manifest_path" || ! -f "$manifest_path" ]]; then
793
- return 1
794
- fi
795
-
796
- status_output="$(filtered_status_output "$worktree")"
797
- if [[ -z "$status_output" ]]; then
798
- return 1
799
- fi
800
-
801
- STATUS_OUTPUT="$status_output" python3 - "$worktree" "$manifest_path" <<'PY'
802
- from __future__ import annotations
803
-
804
- import hashlib
805
- import json
806
- import os
807
- import sys
808
- from pathlib import Path
809
-
810
-
811
- def parse_status_paths(raw: str) -> list[str]:
812
- paths: list[str] = []
813
- for line in raw.splitlines():
814
- if len(line) < 4:
815
- continue
816
- path_part = line[3:]
817
- if " -> " in path_part:
818
- path_part = path_part.split(" -> ", 1)[1]
819
- path_part = path_part.strip()
820
- if path_part:
821
- paths.append(path_part)
822
- return sorted(set(paths))
823
-
824
-
825
- def sha256_for_file(path: Path) -> str | None:
826
- if not path.exists() or not path.is_file():
827
- return None
828
- digest = hashlib.sha256()
829
- with path.open("rb") as handle:
830
- for chunk in iter(lambda: handle.read(65536), b""):
831
- digest.update(chunk)
832
- return digest.hexdigest()
833
-
834
-
835
- if len(sys.argv) != 3:
836
- sys.exit(1)
837
-
838
- worktree_root = Path(sys.argv[1])
839
- manifest_path = Path(sys.argv[2])
840
-
841
- status_paths = parse_status_paths(os.environ.get("STATUS_OUTPUT", ""))
842
- if not status_paths:
843
- sys.exit(1)
844
-
845
- try:
846
- payload = json.loads(manifest_path.read_text(encoding="utf-8"))
847
- except Exception:
848
- sys.exit(1)
849
-
850
- entries = payload.get("files")
851
- if not isinstance(entries, list):
852
- sys.exit(1)
853
-
854
- manifest_by_path: dict[str, str | None] = {}
855
- for entry in entries:
856
- if not isinstance(entry, dict):
857
- continue
858
- path_value = entry.get("path")
859
- if not isinstance(path_value, str) or not path_value:
860
- continue
861
- sha_value = entry.get("sha256")
862
- if sha_value is not None and not isinstance(sha_value, str):
863
- continue
864
- manifest_by_path[path_value] = sha_value
865
-
866
- if not manifest_by_path:
867
- sys.exit(1)
868
-
869
- for rel_path in status_paths:
870
- if rel_path not in manifest_by_path:
871
- sys.exit(1)
872
- current_sha = sha256_for_file(worktree_root / rel_path)
873
- if current_sha != manifest_by_path.get(rel_path):
874
- sys.exit(1)
875
-
876
- sys.exit(0)
877
- PY
878
- }
879
-
880
- get_worktree_for_branch() {
881
- local branch="$1"
882
- git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" '
883
- $1 == "worktree" { wt = $2 }
884
- $1 == "branch" && $2 == target { print wt; exit }
885
- '
886
- }
887
-
888
- is_clean_worktree() {
889
- local wt="$1"
890
- git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
891
- && git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
892
- && [[ -z "$(git -C "$wt" ls-files --others --exclude-standard)" ]]
893
- }
894
-
895
- json_escape() {
896
- local raw="$1"
897
- raw="${raw//\\/\\\\}"
898
- raw="${raw//\"/\\\"}"
899
- raw="${raw//$'\n'/\\n}"
900
- printf '%s' "$raw"
901
- }
902
-
903
- link_worktree_mem0_compat_file() {
904
- local mem0_file="$1"
905
- local compat_file="$2"
906
- local compat_target="$3"
907
-
908
- mkdir -p "$(dirname "$compat_file")"
909
- if [[ -e "$compat_file" ]]; then
910
- return 0
911
- fi
912
-
913
- if ln -s "$compat_target" "$compat_file" >/dev/null 2>&1; then
914
- return 0
915
- fi
916
-
917
- cp "$mem0_file" "$compat_file"
918
- }
919
-
920
- initialize_worktree_mem0_layer() {
921
- local worktree="$1"
922
- local branch="$2"
923
- local base_branch="$3"
924
- local task_slug="$4"
925
- local agent_slug="$5"
926
-
927
- local omx_dir="${worktree}/.omx"
928
- local mem0_dir="${omx_dir}/mem0"
929
- local notepad_path="${mem0_dir}/notepad.md"
930
- local project_memory_path="${mem0_dir}/project-memory.json"
931
- local scope_path="${mem0_dir}/worktree-scope.json"
932
- local created_at
933
- local now
934
-
935
- mkdir -p "$mem0_dir"
936
-
937
- if [[ ! -f "$notepad_path" ]]; then
938
- cat >"$notepad_path" <<EOF
939
- # Mem0 Worktree Memory
940
-
941
- - Scope: worktree
942
- - Branch: ${branch}
943
- - Base: ${base_branch}
944
- - Task: ${task_slug}
945
- - Agent: ${agent_slug}
946
-
947
- ## WORKING MEMORY
948
- EOF
949
- fi
950
-
951
- if [[ ! -f "$project_memory_path" ]]; then
952
- created_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
953
- cat >"$project_memory_path" <<EOF
954
- {
955
- "version": 1,
956
- "scope": "worktree",
957
- "branch": "$(json_escape "$branch")",
958
- "baseBranch": "$(json_escape "$base_branch")",
959
- "taskSlug": "$(json_escape "$task_slug")",
960
- "agentSlug": "$(json_escape "$agent_slug")",
961
- "createdAt": "$(json_escape "$created_at")",
962
- "notes": [],
963
- "directives": []
964
- }
965
- EOF
966
- fi
967
-
968
- now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
969
- cat >"$scope_path" <<EOF
970
- {
971
- "version": 1,
972
- "scope": "worktree",
973
- "branch": "$(json_escape "$branch")",
974
- "baseBranch": "$(json_escape "$base_branch")",
975
- "taskSlug": "$(json_escape "$task_slug")",
976
- "agentSlug": "$(json_escape "$agent_slug")",
977
- "worktreePath": "$(json_escape "$worktree")",
978
- "updatedAt": "$(json_escape "$now")",
979
- "notepadPath": ".omx/mem0/notepad.md",
980
- "projectMemoryPath": ".omx/mem0/project-memory.json"
981
- }
982
- EOF
983
-
984
- link_worktree_mem0_compat_file "$notepad_path" "${omx_dir}/notepad.md" "mem0/notepad.md"
985
- link_worktree_mem0_compat_file "$project_memory_path" "${omx_dir}/project-memory.json" "mem0/project-memory.json"
986
- echo "[agent-branch-start] Mem0 worktree memory: ${mem0_dir}"
987
- }
988
-
989
- run_startup_context_artifacts() {
990
- local repo="$1"
991
- local worktree="$2"
992
- local branch="$3"
993
- local base_branch="$4"
994
- local pr_ref="$5"
995
- local repo_ref="$6"
996
- local branch_slug
997
- local github_context_dir="${worktree}/.omx/context/github"
998
- local merge_gate_dir="${worktree}/.omx/state/merge-gates"
999
- local context_pack_dir="${worktree}/.omx/context/packs"
1000
- local context_json=""
1001
- local conflict_json=""
1002
- local context_pack_json=""
1003
- local conflict_passed=1
1004
-
1005
- mkdir -p "$github_context_dir" "$merge_gate_dir" "$context_pack_dir"
1006
- branch_slug="$(sanitize_slug "${branch//\//-}" "context-pack")"
1007
-
1008
- if [[ "$GH_SYNC_ON_START" -eq 1 ]]; then
1009
- if [[ -x "${repo}/scripts/omx-gh-sync.sh" ]]; then
1010
- local sync_args=(--branch "$branch" --output-dir "$github_context_dir")
1011
- if [[ -n "$pr_ref" ]]; then
1012
- sync_args+=(--pr "$pr_ref")
1013
- fi
1014
- if [[ -n "$repo_ref" ]]; then
1015
- sync_args+=(--repo "$repo_ref")
1016
- fi
1017
- local sync_output=""
1018
- if sync_output="$(bash "${repo}/scripts/omx-gh-sync.sh" "${sync_args[@]}" 2>&1)"; then
1019
- printf '%s\n' "$sync_output"
1020
- context_json="$(printf '%s\n' "$sync_output" | sed -n 's/^Context JSON: //p' | tail -n1)"
1021
- else
1022
- echo "[agent-branch-start] Warning: GitHub context sync failed; continuing with local-only startup context." >&2
1023
- printf '%s\n' "$sync_output" >&2
1024
- fi
1025
- else
1026
- echo "[agent-branch-start] Warning: scripts/omx-gh-sync.sh is missing; skipping GitHub context sync." >&2
1027
- fi
1028
- else
1029
- echo "[agent-branch-start] GitHub context sync disabled (--no-gh-sync)."
1030
- fi
1031
-
1032
- if [[ -x "${repo}/scripts/agent-conflict-predict.sh" ]]; then
1033
- local conflict_output=""
1034
- local conflict_args=(--branch "$branch" --base "$base_branch" --output-dir "$merge_gate_dir")
1035
- if conflict_output="$(bash "${repo}/scripts/agent-conflict-predict.sh" "${conflict_args[@]}" 2>&1)"; then
1036
- printf '%s\n' "$conflict_output"
1037
- conflict_json="$(printf '%s\n' "$conflict_output" | sed -n 's/^Conflict JSON: //p' | tail -n1)"
1038
- else
1039
- conflict_passed=0
1040
- echo "[agent-branch-start] Warning: conflict predictor reported overlaps/locks before coding begins." >&2
1041
- printf '%s\n' "$conflict_output" >&2
1042
- fi
1043
- fi
1044
-
1045
- if [[ -x "${repo}/scripts/omx-context-pack.sh" ]]; then
1046
- local pack_args=(
1047
- --slug "$branch_slug"
1048
- --branch "$branch"
1049
- --base "$base_branch"
1050
- --output-dir "$context_pack_dir"
1051
- )
1052
- if [[ -n "$context_json" ]]; then
1053
- pack_args+=(--context-file "$context_json")
1054
- fi
1055
- if [[ -n "$conflict_json" ]]; then
1056
- pack_args+=(--conflict-file "$conflict_json")
1057
- fi
1058
- local pack_output=""
1059
- if pack_output="$(bash "${repo}/scripts/omx-context-pack.sh" "${pack_args[@]}" 2>&1)"; then
1060
- printf '%s\n' "$pack_output"
1061
- context_pack_json="$(printf '%s\n' "$pack_output" | sed -n 's/^Context pack JSON: //p' | tail -n1)"
1062
- else
1063
- echo "[agent-branch-start] Warning: context pack assembly failed; continuing without startup pack." >&2
1064
- printf '%s\n' "$pack_output" >&2
1065
- fi
1066
- fi
1067
-
1068
- python3 - "${github_context_dir}/sandbox-startup-latest.json" "$branch" "$base_branch" "$pr_ref" "$repo_ref" "$GH_SYNC_ON_START" "$conflict_passed" "$context_json" "$conflict_json" "$context_pack_json" <<'PY'
1069
- from __future__ import annotations
1070
-
1071
- import json
1072
- import sys
1073
- from datetime import datetime, timezone
1074
- from pathlib import Path
1075
-
1076
- (
1077
- _,
1078
- output_path,
1079
- branch_name,
1080
- base_name,
1081
- pr_value,
1082
- repo_value,
1083
- gh_sync_value,
1084
- conflict_value,
1085
- context_json_path,
1086
- conflict_json_path,
1087
- context_pack_path,
1088
- ) = sys.argv
1089
-
1090
- payload = {
1091
- "version": 1,
1092
- "generated_at": datetime.now(timezone.utc).isoformat(),
1093
- "branch": branch_name,
1094
- "base_branch": base_name,
1095
- "pr": pr_value,
1096
- "repo": repo_value,
1097
- "gh_sync_enabled": int(gh_sync_value),
1098
- "conflict_passed": int(conflict_value),
1099
- "context_json": context_json_path,
1100
- "conflict_json": conflict_json_path,
1101
- "context_pack_json": context_pack_path,
1102
- }
1103
-
1104
- target = Path(output_path)
1105
- target.parent.mkdir(parents=True, exist_ok=True)
1106
- target.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
1107
- PY
1108
-
1109
- echo "[agent-branch-start] Startup metadata: ${github_context_dir}/sandbox-startup-latest.json"
1110
- }
1111
-
1112
374
  if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
1113
375
  echo "[agent-branch-start] Not inside a git repository." >&2
1114
376
  exit 1
@@ -1124,39 +386,20 @@ fi
1124
386
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
1125
387
  current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
1126
388
  protected_branches_raw="$(resolve_protected_branches "$repo_root")"
1127
- configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
1128
389
  if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
1129
390
  BASE_BRANCH="$current_branch"
1130
- elif [[ -n "$current_branch" && "$current_branch" == agent/* ]]; then
1131
- BASE_BRANCH="$current_branch"
1132
- echo "[agent-branch-start] Using current agent branch '${BASE_BRANCH}' as helper base."
1133
- elif [[ -n "$configured_base" ]]; then
1134
- BASE_BRANCH="$configured_base"
1135
391
  else
1136
- if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]]; then
392
+ configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
393
+ if [[ -n "$configured_base" ]]; then
394
+ BASE_BRANCH="$configured_base"
395
+ elif [[ -n "$current_branch" && "$current_branch" != "HEAD" ]]; then
1137
396
  BASE_BRANCH="$current_branch"
1138
397
  else
1139
- BASE_BRANCH="$(resolve_default_base_branch_for_agent_subbranch "$repo_root" "$protected_branches_raw" || printf 'dev')"
398
+ BASE_BRANCH="dev"
1140
399
  fi
1141
400
  fi
1142
401
  fi
1143
402
 
1144
- helper_branch_assist_mode=0
1145
- if is_helper_agent_base_branch "$BASE_BRANCH"; then
1146
- helper_branch_assist_mode=1
1147
- OPENSPEC_AUTO_INIT=0
1148
- OPENSPEC_CHANGE_INIT=0
1149
- OPENSPEC_PLAN_INIT=0
1150
- OPENSPEC_CHANGE_MINIMAL=0
1151
- echo "[agent-branch-start] Helper branch base '${BASE_BRANCH}' detected; skipping OpenSpec auto-init for joined-agent assist."
1152
- elif [[ "$TIER_LEVEL_EXPLICIT" -eq 1 && "$TIER_LEVEL" == "T0" ]]; then
1153
- echo "[agent-branch-start] Tier T0: skipping OpenSpec change + plan workspace scaffolding."
1154
- elif [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
1155
- echo "[agent-branch-start] OpenSpec auto-init is mandatory for non-helper agent branches; ignoring disabled override." >&2
1156
- OPENSPEC_AUTO_INIT=1
1157
- OPENSPEC_CHANGE_INIT=1
1158
- fi
1159
-
1160
403
  if git show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
1161
404
  git fetch origin "${BASE_BRANCH}" --quiet
1162
405
  start_ref="origin/${BASE_BRANCH}"
@@ -1169,74 +412,72 @@ else
1169
412
  fi
1170
413
 
1171
414
  task_slug="$(sanitize_slug "$TASK_NAME" "task")"
1172
- agent_type="$(normalize_agent_type "$AGENT_NAME")"
415
+ agent_slug_raw="$(sanitize_slug "$AGENT_NAME" "agent")"
416
+ agent_slug="$(shorten_slug "$agent_slug_raw" "${GUARDEX_BRANCH_AGENT_SLUG_MAX:-24}")"
1173
417
  snapshot_name="$(resolve_active_codex_snapshot_name)"
1174
- nickname="$(extract_nickname "$snapshot_name")"
1175
- agent_slug="${agent_type}-${nickname}"
1176
- branch_descriptor="$(compose_branch_descriptor "$task_slug")"
418
+ snapshot_slug="$(sanitize_optional_slug "$snapshot_name" "snapshot")"
419
+ branch_descriptor="$(compose_branch_descriptor "$snapshot_slug" "$task_slug")"
1177
420
  timestamp="$(date +%Y%m%d-%H%M%S)"
1178
421
  branch_name_base="agent/${agent_slug}/${branch_descriptor}"
1179
422
 
1180
- if [[ "$PRINT_NAME_ONLY" -eq 1 ]]; then
1181
- printf '%s\n' "$branch_name_base"
1182
- exit 0
1183
- fi
1184
-
1185
423
  branch_name="$branch_name_base"
424
+ branch_suffix=2
425
+ while git show-ref --verify --quiet "refs/heads/${branch_name}"; do
426
+ branch_name="${branch_name_base}-${branch_suffix}"
427
+ branch_suffix=$((branch_suffix + 1))
428
+ done
429
+
1186
430
  worktree_root="${repo_root}/${WORKTREE_ROOT_REL}"
1187
431
  mkdir -p "$worktree_root"
1188
- worktree_path=""
1189
- reused_existing_worktree=0
1190
-
1191
- if git show-ref --verify --quiet "refs/heads/${branch_name_base}"; then
1192
- existing_worktree_path="$(get_worktree_for_branch "$branch_name_base" || true)"
1193
- if [[ -n "$existing_worktree_path" && -d "$existing_worktree_path" ]] \
1194
- && git -C "$repo_root" merge-base --is-ancestor "$branch_name_base" "$start_ref" >/dev/null 2>&1; then
1195
- if is_clean_worktree "$existing_worktree_path" || worktree_matches_bootstrap_manifest "$existing_worktree_path"; then
1196
- worktree_path="$existing_worktree_path"
1197
- reused_existing_worktree=1
1198
- echo "[agent-branch-start] Reusing untouched sandbox branch/worktree: ${branch_name_base} (${worktree_path})"
1199
- fi
1200
- fi
1201
- fi
1202
-
1203
- if [[ "$reused_existing_worktree" -eq 0 ]]; then
1204
- branch_suffix=2
1205
- while git show-ref --verify --quiet "refs/heads/${branch_name}"; do
1206
- branch_name="${branch_name_base}-${branch_suffix}"
1207
- branch_suffix=$((branch_suffix + 1))
1208
- done
1209
- worktree_path="${worktree_root}/${branch_name//\//__}"
1210
- if [[ -e "$worktree_path" ]]; then
1211
- echo "[agent-branch-start] Worktree path already exists: ${worktree_path}" >&2
1212
- exit 1
1213
- fi
1214
- fi
1215
-
432
+ worktree_path="${worktree_root}/${branch_name//\//__}"
1216
433
  openspec_plan_slug="$(resolve_openspec_plan_slug "$branch_name" "$task_slug")"
1217
434
  openspec_change_slug="$(resolve_openspec_change_slug "$branch_name" "$task_slug")"
1218
435
  openspec_capability_slug="$(resolve_openspec_capability_slug "$task_slug")"
1219
436
 
1220
- primary_branch_before="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
437
+ if [[ -e "$worktree_path" ]]; then
438
+ echo "[agent-branch-start] Worktree path already exists: ${worktree_path}" >&2
439
+ exit 1
440
+ fi
441
+
442
+ auto_transfer_stash_ref=""
443
+ auto_transfer_message=""
444
+ auto_transfer_source_branch=""
445
+ current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
1221
446
  protected_branches_raw="$(resolve_protected_branches "$repo_root")"
1222
- should_migrate_protected_changes=0
1223
- if [[ -n "$primary_branch_before" && "$primary_branch_before" != "HEAD" ]] && is_protected_branch_name "$primary_branch_before" "$protected_branches_raw"; then
447
+ if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
1224
448
  if has_local_changes "$repo_root"; then
1225
- if [[ "$MIGRATE_PROTECTED_CHANGES" -eq 1 ]]; then
1226
- should_migrate_protected_changes=1
1227
- echo "[agent-branch-start] Detected local changes on protected branch '${primary_branch_before}'. Scheduling migration into sandbox worktree."
1228
- else
1229
- echo "[agent-branch-start] Detected local changes on protected branch '${primary_branch_before}'. Leaving them in place (GUARDEX_MIGRATE_PROTECTED_CHANGES=0)."
449
+ auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
450
+ if git -C "$repo_root" stash push --include-untracked --message "$auto_transfer_message" >/dev/null 2>&1; then
451
+ auto_transfer_stash_ref="$(
452
+ git -C "$repo_root" stash list \
453
+ | awk -v msg="$auto_transfer_message" '$0 ~ msg { ref=$1; sub(/:$/, "", ref); print ref; exit }'
454
+ )"
455
+ if [[ -n "$auto_transfer_stash_ref" ]]; then
456
+ auto_transfer_source_branch="$current_branch"
457
+ echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}'..."
458
+ fi
1230
459
  fi
1231
460
  fi
1232
461
  fi
1233
462
 
1234
- if [[ "$reused_existing_worktree" -eq 0 ]]; then
1235
- git -C "$repo_root" worktree add -b "$branch_name" "$worktree_path" "$start_ref"
1236
- git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$BASE_BRANCH" >/dev/null 2>&1 || true
463
+ git -C "$repo_root" worktree add -b "$branch_name" "$worktree_path" "$start_ref"
464
+ git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$BASE_BRANCH" >/dev/null 2>&1 || true
1237
465
 
1238
- if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
1239
- git -C "$worktree_path" branch --set-upstream-to="origin/${BASE_BRANCH}" "$branch_name" >/dev/null 2>&1 || true
466
+ if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
467
+ git -C "$worktree_path" branch --set-upstream-to="origin/${BASE_BRANCH}" "$branch_name" >/dev/null 2>&1 || true
468
+ fi
469
+
470
+ if [[ -n "$auto_transfer_stash_ref" ]]; then
471
+ if git -C "$worktree_path" stash apply "$auto_transfer_stash_ref" >/dev/null 2>&1; then
472
+ git -C "$repo_root" stash drop "$auto_transfer_stash_ref" >/dev/null 2>&1 || true
473
+ transfer_label="${auto_transfer_source_branch:-$BASE_BRANCH}"
474
+ echo "[agent-branch-start] Moved local changes from '${transfer_label}' into '${branch_name}'."
475
+ else
476
+ echo "[agent-branch-start] Failed to auto-apply moved changes in new worktree." >&2
477
+ transfer_label="${auto_transfer_source_branch:-$BASE_BRANCH}"
478
+ echo "[agent-branch-start] Changes are preserved in ${auto_transfer_stash_ref} on ${transfer_label}." >&2
479
+ echo "[agent-branch-start] Apply manually with: git -C \"$worktree_path\" stash apply \"${auto_transfer_stash_ref}\"" >&2
480
+ exit 1
1240
481
  fi
1241
482
  fi
1242
483
 
@@ -1244,61 +485,19 @@ hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-ag
1244
485
  hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
1245
486
  hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
1246
487
  hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
1247
- hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" ".venv"
1248
- if [[ "$reused_existing_worktree" -eq 0 ]]; then
1249
- if ! initialize_openspec_change_workspace "$repo_root" "$worktree_path" "$openspec_change_slug" "$openspec_capability_slug" "$branch_name"; then
1250
- exit 1
1251
- fi
1252
- if ! initialize_openspec_plan_workspace "$repo_root" "$worktree_path" "$openspec_plan_slug"; then
1253
- exit 1
1254
- fi
1255
- fi
1256
- initialize_worktree_mem0_layer "$worktree_path" "$branch_name" "$BASE_BRANCH" "$task_slug" "$agent_slug"
1257
-
1258
- if [[ "$should_migrate_protected_changes" -eq 1 ]]; then
1259
- if ! migrate_local_changes_to_worktree "$repo_root" "$primary_branch_before" "$branch_name" "$worktree_path"; then
1260
- echo "[agent-branch-start] Warning: automatic protected-branch change migration did not fully complete." >&2
1261
- fi
488
+ if ! initialize_openspec_change_workspace "$repo_root" "$worktree_path" "$openspec_change_slug" "$openspec_capability_slug"; then
489
+ exit 1
1262
490
  fi
1263
-
1264
- run_startup_context_artifacts "$repo_root" "$worktree_path" "$branch_name" "$BASE_BRANCH" "$PR_REF" "$GH_REPO_REF"
1265
- record_worktree_bootstrap_manifest "$worktree_path" "$branch_name" "$BASE_BRANCH" "$openspec_change_slug" "$openspec_plan_slug" "$TIER_LEVEL"
1266
-
1267
- primary_branch_after="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
1268
- if [[ -n "$primary_branch_before" && "$primary_branch_before" != "HEAD" && "$primary_branch_after" != "$primary_branch_before" ]]; then
1269
- echo "[agent-branch-start] Warning: primary checkout moved from '${primary_branch_before}' to '${primary_branch_after}'. Restoring '${primary_branch_before}'."
1270
- git -C "$repo_root" checkout -q "$primary_branch_before"
1271
- primary_branch_after="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
1272
- if [[ "$primary_branch_after" != "$primary_branch_before" ]]; then
1273
- echo "[agent-branch-start] Failed to restore primary checkout branch '${primary_branch_before}'." >&2
1274
- exit 1
1275
- fi
491
+ if ! initialize_openspec_plan_workspace "$repo_root" "$worktree_path" "$openspec_plan_slug"; then
492
+ exit 1
1276
493
  fi
1277
494
 
1278
495
  echo "[agent-branch-start] Created branch: ${branch_name}"
1279
496
  echo "[agent-branch-start] Worktree: ${worktree_path}"
1280
- echo "[agent-branch-start] Tier: ${TIER_LEVEL}"
1281
- if [[ "$helper_branch_assist_mode" -eq 1 ]]; then
1282
- echo "[agent-branch-start] OpenSpec change: skipped (helper branch assisting ${BASE_BRANCH})"
1283
- echo "[agent-branch-start] OpenSpec plan: skipped (helper branch assisting ${BASE_BRANCH})"
1284
- else
1285
- if [[ "$OPENSPEC_CHANGE_INIT" -eq 1 ]]; then
1286
- if [[ "$OPENSPEC_CHANGE_MINIMAL" -eq 1 ]]; then
1287
- echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug} (minimal / notes-only)"
1288
- else
1289
- echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
1290
- fi
1291
- else
1292
- echo "[agent-branch-start] OpenSpec change: skipped (tier ${TIER_LEVEL})"
1293
- fi
1294
- if [[ "$OPENSPEC_PLAN_INIT" -eq 1 ]]; then
1295
- echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
1296
- else
1297
- echo "[agent-branch-start] OpenSpec plan: skipped (tier ${TIER_LEVEL})"
1298
- fi
1299
- fi
497
+ echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
498
+ echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
1300
499
  echo "[agent-branch-start] Next steps:"
1301
500
  echo " cd \"${worktree_path}\""
1302
501
  echo " python3 scripts/agent-file-locks.py claim --branch \"${branch_name}\" <file...>"
1303
502
  echo " # implement + commit"
1304
- echo " bash scripts/agent-branch-finish.sh --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup"
503
+ echo " bash scripts/agent-branch-finish.sh --branch \"${branch_name}\" --base dev --via-pr --wait-for-merge"