@imdeadpool/guardex 7.0.16 → 7.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +1 -1
- package/README.md +187 -53
- package/bin/multiagent-safety.js +863 -220
- package/package.json +2 -2
- package/templates/AGENTS.multiagent-safety.md +7 -4
- package/templates/codex/skills/gitguardex/SKILL.md +1 -1
- package/templates/codex/skills/guardex-merge-skills-to-dev/SKILL.md +3 -3
- package/templates/githooks/post-checkout +1 -1
- package/templates/githooks/post-merge +19 -6
- package/templates/githooks/pre-commit +29 -10
- package/templates/scripts/agent-branch-finish.sh +32 -19
- package/templates/scripts/agent-branch-merge.sh +24 -5
- package/templates/scripts/agent-branch-start.sh +23 -29
- package/templates/scripts/agent-file-locks.py +11 -11
- package/templates/scripts/agent-session-state.js +110 -0
- package/templates/scripts/codex-agent.sh +113 -54
- package/templates/scripts/install-vscode-active-agents-extension.js +92 -0
- package/templates/scripts/openspec/init-change-workspace.sh +77 -9
- package/templates/scripts/openspec/init-plan-workspace.sh +576 -74
- package/templates/scripts/review-bot-watch.sh +30 -7
- package/templates/vscode/guardex-active-agents/README.md +22 -0
- package/templates/vscode/guardex-active-agents/extension.js +357 -0
- package/templates/vscode/guardex-active-agents/package.json +57 -0
- package/templates/vscode/guardex-active-agents/session-schema.js +407 -0
|
@@ -6,6 +6,8 @@ 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
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
10
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
9
11
|
AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
|
|
10
12
|
AUTO_REVIEW_ON_CONFLICT_RAW="${GUARDEX_CODEX_AUTO_REVIEW_ON_CONFLICT:-true}"
|
|
11
13
|
AUTO_CLEANUP_RAW="${GUARDEX_CODEX_AUTO_CLEANUP:-true}"
|
|
@@ -16,6 +18,23 @@ OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
|
16
18
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
17
19
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
18
20
|
|
|
21
|
+
run_guardex_cli() {
|
|
22
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
23
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
24
|
+
return $?
|
|
25
|
+
fi
|
|
26
|
+
if command -v gx >/dev/null 2>&1; then
|
|
27
|
+
gx "$@"
|
|
28
|
+
return $?
|
|
29
|
+
fi
|
|
30
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
31
|
+
gitguardex "$@"
|
|
32
|
+
return $?
|
|
33
|
+
fi
|
|
34
|
+
echo "[codex-agent] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
35
|
+
return 127
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
normalize_bool() {
|
|
20
39
|
local raw="${1:-}"
|
|
21
40
|
local fallback="${2:-0}"
|
|
@@ -143,6 +162,7 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
143
162
|
exit 1
|
|
144
163
|
fi
|
|
145
164
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
165
|
+
active_session_state_script="${repo_root}/scripts/agent-session-state.js"
|
|
146
166
|
|
|
147
167
|
guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
|
|
148
168
|
if [[ -f "$guardex_env_helper" ]]; then
|
|
@@ -373,11 +393,6 @@ start_sandbox_fallback() {
|
|
|
373
393
|
printf '[agent-branch-start] Worktree: %s\n' "$worktree_path"
|
|
374
394
|
}
|
|
375
395
|
|
|
376
|
-
if [[ ! -x "${repo_root}/scripts/agent-branch-start.sh" ]]; then
|
|
377
|
-
echo "[codex-agent] Missing scripts/agent-branch-start.sh. Run: gx setup" >&2
|
|
378
|
-
exit 1
|
|
379
|
-
fi
|
|
380
|
-
|
|
381
396
|
start_args=("$TASK_NAME" "$AGENT_NAME")
|
|
382
397
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
383
398
|
start_args+=("$BASE_BRANCH")
|
|
@@ -390,7 +405,7 @@ set +e
|
|
|
390
405
|
start_output="$(
|
|
391
406
|
GUARDEX_OPENSPEC_AUTO_INIT="$OPENSPEC_AUTO_INIT" \
|
|
392
407
|
GUARDEX_OPENSPEC_MASTERPLAN_LABEL="$OPENSPEC_MASTERPLAN_LABEL_RAW" \
|
|
393
|
-
|
|
408
|
+
run_guardex_cli branch start "${start_args[@]}" 2>&1
|
|
394
409
|
)"
|
|
395
410
|
start_status=$?
|
|
396
411
|
set -e
|
|
@@ -446,6 +461,40 @@ has_origin_remote() {
|
|
|
446
461
|
git -C "$repo_root" remote get-url origin >/dev/null 2>&1
|
|
447
462
|
}
|
|
448
463
|
|
|
464
|
+
run_active_session_state() {
|
|
465
|
+
local action="$1"
|
|
466
|
+
shift
|
|
467
|
+
|
|
468
|
+
if [[ ! -f "$active_session_state_script" ]]; then
|
|
469
|
+
return 0
|
|
470
|
+
fi
|
|
471
|
+
if ! command -v "$NODE_BIN" >/dev/null 2>&1; then
|
|
472
|
+
return 0
|
|
473
|
+
fi
|
|
474
|
+
|
|
475
|
+
"$NODE_BIN" "$active_session_state_script" "$action" "$@" >/dev/null 2>&1 || true
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
record_active_session_state() {
|
|
479
|
+
local wt="$1"
|
|
480
|
+
local branch="$2"
|
|
481
|
+
|
|
482
|
+
run_active_session_state \
|
|
483
|
+
start \
|
|
484
|
+
--repo "$repo_root" \
|
|
485
|
+
--branch "$branch" \
|
|
486
|
+
--task "$TASK_NAME" \
|
|
487
|
+
--agent "$AGENT_NAME" \
|
|
488
|
+
--worktree "$wt" \
|
|
489
|
+
--pid "$$" \
|
|
490
|
+
--cli "$CODEX_BIN"
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
clear_active_session_state() {
|
|
494
|
+
local branch="$1"
|
|
495
|
+
run_active_session_state stop --repo "$repo_root" --branch "$branch"
|
|
496
|
+
}
|
|
497
|
+
|
|
449
498
|
origin_remote_supports_pr_finish() {
|
|
450
499
|
local origin_url
|
|
451
500
|
origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
|
|
@@ -474,6 +523,31 @@ resolve_worktree_base_branch() {
|
|
|
474
523
|
printf 'dev'
|
|
475
524
|
}
|
|
476
525
|
|
|
526
|
+
print_takeover_prompt() {
|
|
527
|
+
local wt="$1"
|
|
528
|
+
local branch="$2"
|
|
529
|
+
local base_branch change_slug change_artifact finish_cmd
|
|
530
|
+
|
|
531
|
+
base_branch="$(resolve_worktree_base_branch "$wt")"
|
|
532
|
+
if [[ -z "$base_branch" ]]; then
|
|
533
|
+
base_branch="dev"
|
|
534
|
+
fi
|
|
535
|
+
|
|
536
|
+
change_slug="$(resolve_openspec_change_slug "$branch")"
|
|
537
|
+
change_artifact="openspec/changes/${change_slug}/tasks.md"
|
|
538
|
+
if [[ ! -f "${wt}/${change_artifact}" ]]; then
|
|
539
|
+
change_artifact="openspec/changes/${change_slug}/notes.md"
|
|
540
|
+
fi
|
|
541
|
+
if [[ ! -f "${wt}/${change_artifact}" ]]; then
|
|
542
|
+
change_artifact="openspec/changes/${change_slug}/"
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
|
|
546
|
+
|
|
547
|
+
echo "[codex-agent] Takeover sandbox: ${wt}"
|
|
548
|
+
echo "[codex-agent] Takeover prompt: Continue \`${change_slug}\` on branch \`${branch}\`. Work inside \`${wt}\`, review \`${change_artifact}\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`${finish_cmd}\`."
|
|
549
|
+
}
|
|
550
|
+
|
|
477
551
|
sync_worktree_with_base() {
|
|
478
552
|
local wt="$1"
|
|
479
553
|
if ! has_origin_remote; then
|
|
@@ -524,24 +598,12 @@ ensure_openspec_plan_workspace() {
|
|
|
524
598
|
return 0
|
|
525
599
|
fi
|
|
526
600
|
|
|
527
|
-
hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-plan-workspace.sh"
|
|
528
|
-
|
|
529
|
-
local openspec_script="${wt}/scripts/openspec/init-plan-workspace.sh"
|
|
530
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
531
|
-
echo "[codex-agent] Missing OpenSpec init script in sandbox: ${openspec_script}" >&2
|
|
532
|
-
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
|
|
533
|
-
return 1
|
|
534
|
-
fi
|
|
535
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
536
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
537
|
-
fi
|
|
538
|
-
|
|
539
601
|
local plan_slug
|
|
540
602
|
plan_slug="$(resolve_openspec_plan_slug "$branch")"
|
|
541
603
|
local init_output=""
|
|
542
604
|
if ! init_output="$(
|
|
543
605
|
cd "$wt"
|
|
544
|
-
|
|
606
|
+
run_guardex_cli internal run-shell planInit "$plan_slug" 2>&1
|
|
545
607
|
)"; then
|
|
546
608
|
printf '%s\n' "$init_output" >&2
|
|
547
609
|
echo "[codex-agent] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
|
|
@@ -561,24 +623,12 @@ ensure_openspec_change_workspace() {
|
|
|
561
623
|
return 0
|
|
562
624
|
fi
|
|
563
625
|
|
|
564
|
-
hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-change-workspace.sh"
|
|
565
|
-
|
|
566
|
-
local openspec_script="${wt}/scripts/openspec/init-change-workspace.sh"
|
|
567
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
568
|
-
echo "[codex-agent] Missing OpenSpec change init script in sandbox: ${openspec_script}" >&2
|
|
569
|
-
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
|
|
570
|
-
return 1
|
|
571
|
-
fi
|
|
572
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
573
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
574
|
-
fi
|
|
575
|
-
|
|
576
626
|
local change_slug capability_slug init_output=""
|
|
577
627
|
change_slug="$(resolve_openspec_change_slug "$branch")"
|
|
578
628
|
capability_slug="$(resolve_openspec_capability_slug)"
|
|
579
629
|
if ! init_output="$(
|
|
580
630
|
cd "$wt"
|
|
581
|
-
|
|
631
|
+
run_guardex_cli internal run-shell changeInit "$change_slug" "$capability_slug" 2>&1
|
|
582
632
|
)"; then
|
|
583
633
|
printf '%s\n' "$init_output" >&2
|
|
584
634
|
echo "[codex-agent] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
@@ -607,11 +657,6 @@ worktree_has_changes() {
|
|
|
607
657
|
claim_changed_files() {
|
|
608
658
|
local wt="$1"
|
|
609
659
|
local branch="$2"
|
|
610
|
-
local lock_script="${repo_root}/scripts/agent-file-locks.py"
|
|
611
|
-
|
|
612
|
-
if [[ ! -x "$lock_script" ]]; then
|
|
613
|
-
return 0
|
|
614
|
-
fi
|
|
615
660
|
|
|
616
661
|
local changed_raw deleted_raw
|
|
617
662
|
changed_raw="$({
|
|
@@ -622,7 +667,7 @@ claim_changed_files() {
|
|
|
622
667
|
|
|
623
668
|
if [[ -n "$changed_raw" ]]; then
|
|
624
669
|
mapfile -t changed_files < <(printf '%s\n' "$changed_raw")
|
|
625
|
-
|
|
670
|
+
run_guardex_cli locks claim --branch "$branch" "${changed_files[@]}" >/dev/null 2>&1 || true
|
|
626
671
|
fi
|
|
627
672
|
|
|
628
673
|
deleted_raw="$({
|
|
@@ -632,7 +677,7 @@ claim_changed_files() {
|
|
|
632
677
|
|
|
633
678
|
if [[ -n "$deleted_raw" ]]; then
|
|
634
679
|
mapfile -t deleted_files < <(printf '%s\n' "$deleted_raw")
|
|
635
|
-
|
|
680
|
+
run_guardex_cli locks allow-delete --branch "$branch" "${deleted_files[@]}" >/dev/null 2>&1 || true
|
|
636
681
|
fi
|
|
637
682
|
}
|
|
638
683
|
|
|
@@ -781,7 +826,7 @@ run_finish_flow() {
|
|
|
781
826
|
return 2
|
|
782
827
|
fi
|
|
783
828
|
|
|
784
|
-
if finish_output="$(
|
|
829
|
+
if finish_output="$(run_guardex_cli branch finish "${finish_args[@]}" 2>&1)"; then
|
|
785
830
|
printf '%s\n' "$finish_output"
|
|
786
831
|
return 0
|
|
787
832
|
fi
|
|
@@ -804,7 +849,7 @@ run_finish_flow() {
|
|
|
804
849
|
fi
|
|
805
850
|
)
|
|
806
851
|
|
|
807
|
-
if finish_output="$(
|
|
852
|
+
if finish_output="$(run_guardex_cli branch finish "${finish_args[@]}" 2>&1)"; then
|
|
808
853
|
printf '%s\n' "$finish_output"
|
|
809
854
|
return 0
|
|
810
855
|
fi
|
|
@@ -833,6 +878,19 @@ if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
|
|
|
833
878
|
exit 1
|
|
834
879
|
fi
|
|
835
880
|
|
|
881
|
+
active_session_recorded=0
|
|
882
|
+
cleanup_active_session_state_on_exit() {
|
|
883
|
+
set +e
|
|
884
|
+
if [[ "${active_session_recorded:-0}" -eq 1 && -n "${worktree_branch:-}" && "${worktree_branch:-}" != "HEAD" ]]; then
|
|
885
|
+
clear_active_session_state "$worktree_branch"
|
|
886
|
+
active_session_recorded=0
|
|
887
|
+
fi
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
record_active_session_state "$worktree_path" "$worktree_branch"
|
|
891
|
+
active_session_recorded=1
|
|
892
|
+
trap cleanup_active_session_state_on_exit EXIT INT TERM
|
|
893
|
+
|
|
836
894
|
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
|
|
837
895
|
cd "$worktree_path"
|
|
838
896
|
set +e
|
|
@@ -841,6 +899,8 @@ codex_exit="$?"
|
|
|
841
899
|
set -e
|
|
842
900
|
|
|
843
901
|
cd "$repo_root"
|
|
902
|
+
cleanup_active_session_state_on_exit
|
|
903
|
+
trap - EXIT INT TERM
|
|
844
904
|
final_exit="$codex_exit"
|
|
845
905
|
auto_finish_completed=0
|
|
846
906
|
|
|
@@ -878,18 +938,16 @@ if [[ "$AUTO_FINISH" -eq 1 && -n "$worktree_branch" && "$worktree_branch" != "HE
|
|
|
878
938
|
fi
|
|
879
939
|
fi
|
|
880
940
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
echo "[codex-agent] Warning: automatic worktree cleanup failed." >&2
|
|
892
|
-
fi
|
|
941
|
+
echo "[codex-agent] Session ended (exit=${codex_exit}). Running worktree cleanup..."
|
|
942
|
+
prune_args=()
|
|
943
|
+
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
944
|
+
prune_args+=(--base "$BASE_BRANCH")
|
|
945
|
+
fi
|
|
946
|
+
if [[ "$AUTO_CLEANUP" -eq 1 && "$auto_finish_completed" -eq 1 ]]; then
|
|
947
|
+
prune_args+=(--only-dirty-worktrees --delete-branches --delete-remote-branches)
|
|
948
|
+
fi
|
|
949
|
+
if ! run_guardex_cli worktree prune "${prune_args[@]}"; then
|
|
950
|
+
echo "[codex-agent] Warning: automatic worktree cleanup failed." >&2
|
|
893
951
|
fi
|
|
894
952
|
|
|
895
953
|
if [[ ! -d "$worktree_path" ]]; then
|
|
@@ -901,7 +959,8 @@ else
|
|
|
901
959
|
if [[ "$auto_finish_completed" -eq 1 ]]; then
|
|
902
960
|
echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
903
961
|
else
|
|
904
|
-
|
|
962
|
+
print_takeover_prompt "$worktree_path" "$worktree_branch"
|
|
963
|
+
echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
|
|
905
964
|
echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
906
965
|
fi
|
|
907
966
|
fi
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
function parseOptions(argv) {
|
|
8
|
+
const options = {};
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const token = argv[index];
|
|
11
|
+
if (!token.startsWith('--')) {
|
|
12
|
+
throw new Error(`Unexpected argument: ${token}`);
|
|
13
|
+
}
|
|
14
|
+
const key = token.slice(2);
|
|
15
|
+
const value = argv[index + 1];
|
|
16
|
+
if (!value || value.startsWith('--')) {
|
|
17
|
+
throw new Error(`Missing value for --${key}`);
|
|
18
|
+
}
|
|
19
|
+
options[key] = value;
|
|
20
|
+
index += 1;
|
|
21
|
+
}
|
|
22
|
+
return options;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveExtensionSource(repoRoot) {
|
|
26
|
+
const candidates = [
|
|
27
|
+
path.join(repoRoot, 'vscode', 'guardex-active-agents'),
|
|
28
|
+
path.join(repoRoot, 'templates', 'vscode', 'guardex-active-agents'),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new Error('Could not find the Guardex VS Code companion sources.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function removeIfExists(targetPath) {
|
|
41
|
+
if (fs.existsSync(targetPath)) {
|
|
42
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function main() {
|
|
47
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
48
|
+
const options = parseOptions(process.argv.slice(2));
|
|
49
|
+
const sourceDir = resolveExtensionSource(repoRoot);
|
|
50
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(sourceDir, 'package.json'), 'utf8'));
|
|
51
|
+
const extensionId = `${manifest.publisher}.${manifest.name}`;
|
|
52
|
+
const extensionsDir = path.resolve(
|
|
53
|
+
options['extensions-dir'] ||
|
|
54
|
+
process.env.GUARDEX_VSCODE_EXTENSIONS_DIR ||
|
|
55
|
+
process.env.VSCODE_EXTENSIONS_DIR ||
|
|
56
|
+
path.join(os.homedir(), '.vscode', 'extensions'),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
fs.mkdirSync(extensionsDir, { recursive: true });
|
|
60
|
+
const targetDir = path.join(extensionsDir, `${extensionId}-${manifest.version}`);
|
|
61
|
+
|
|
62
|
+
for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
|
63
|
+
if (!entry.isDirectory()) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (entry.name === path.basename(targetDir)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (entry.name.startsWith(`${extensionId}-`)) {
|
|
70
|
+
removeIfExists(path.join(extensionsDir, entry.name));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
removeIfExists(targetDir);
|
|
75
|
+
fs.cpSync(sourceDir, targetDir, {
|
|
76
|
+
recursive: true,
|
|
77
|
+
force: true,
|
|
78
|
+
preserveTimestamps: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
process.stdout.write(
|
|
82
|
+
`[guardex-active-agents] Installed ${extensionId}@${manifest.version} to ${targetDir}\n` +
|
|
83
|
+
'[guardex-active-agents] Reload the VS Code window to activate the Source Control companion.\n',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
main();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
process.stderr.write(`[guardex-active-agents] ${error.message}\n`);
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
if [[ $# -lt 1 || $# -gt
|
|
5
|
-
echo "Usage: $0 <change-slug> [capability-slug]"
|
|
6
|
-
echo "Example: $0 add-dashboard-live-usage runtime-migration"
|
|
4
|
+
if [[ $# -lt 1 || $# -gt 3 ]]; then
|
|
5
|
+
echo "Usage: $0 <change-slug> [capability-slug] [agent-branch]"
|
|
6
|
+
echo "Example: $0 add-dashboard-live-usage runtime-migration agent/claude-odin/add-dashboard-live-usage-123456"
|
|
7
7
|
exit 1
|
|
8
8
|
fi
|
|
9
9
|
|
|
10
10
|
CHANGE_SLUG="$1"
|
|
11
11
|
CAPABILITY_SLUG="${2:-$CHANGE_SLUG}"
|
|
12
|
+
AGENT_BRANCH="${3:-agent/<your-name>/<branch-slug>}"
|
|
12
13
|
|
|
13
14
|
if [[ "$CHANGE_SLUG" =~ [^a-z0-9-] ]]; then
|
|
14
15
|
echo "Error: change slug must be kebab-case (lowercase letters, numbers, hyphens)."
|
|
@@ -20,11 +21,39 @@ if [[ "$CAPABILITY_SLUG" =~ [^a-z0-9-] ]]; then
|
|
|
20
21
|
exit 1
|
|
21
22
|
fi
|
|
22
23
|
|
|
24
|
+
resolve_base_branch() {
|
|
25
|
+
local branch="$1"
|
|
26
|
+
local base_branch=""
|
|
27
|
+
|
|
28
|
+
if [[ -n "$branch" ]] && [[ "$branch" != "agent/<your-name>/<branch-slug>" ]]; then
|
|
29
|
+
base_branch="$(git config --get "branch.${branch}.guardexBase" || true)"
|
|
30
|
+
fi
|
|
31
|
+
if [[ -z "$base_branch" ]]; then
|
|
32
|
+
base_branch="$(git config --get multiagent.baseBranch || true)"
|
|
33
|
+
fi
|
|
34
|
+
if [[ -z "$base_branch" ]]; then
|
|
35
|
+
base_branch="dev"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
printf '%s' "$base_branch"
|
|
39
|
+
}
|
|
40
|
+
|
|
23
41
|
CHANGE_DIR="openspec/changes/${CHANGE_SLUG}"
|
|
24
42
|
SPEC_DIR="${CHANGE_DIR}/specs/${CAPABILITY_SLUG}"
|
|
25
43
|
TODAY="$(date -u +%Y-%m-%d)"
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
BASE_BRANCH="$(resolve_base_branch "$AGENT_BRANCH")"
|
|
45
|
+
|
|
46
|
+
MINIMAL_RAW="${GUARDEX_OPENSPEC_MINIMAL:-0}"
|
|
47
|
+
case "$(printf '%s' "$MINIMAL_RAW" | tr '[:upper:]' '[:lower:]')" in
|
|
48
|
+
1|true|yes|on) MINIMAL=1 ;;
|
|
49
|
+
*) MINIMAL=0 ;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
52
|
+
if [[ "$MINIMAL" -eq 1 ]]; then
|
|
53
|
+
mkdir -p "$CHANGE_DIR"
|
|
54
|
+
else
|
|
55
|
+
mkdir -p "$SPEC_DIR"
|
|
56
|
+
fi
|
|
28
57
|
|
|
29
58
|
if [[ ! -f "${CHANGE_DIR}/.openspec.yaml" ]]; then
|
|
30
59
|
cat > "${CHANGE_DIR}/.openspec.yaml" <<YAMLEOF
|
|
@@ -33,6 +62,32 @@ created: ${TODAY}
|
|
|
33
62
|
YAMLEOF
|
|
34
63
|
fi
|
|
35
64
|
|
|
65
|
+
if [[ "$MINIMAL" -eq 1 ]]; then
|
|
66
|
+
if [[ ! -f "${CHANGE_DIR}/notes.md" ]]; then
|
|
67
|
+
cat > "${CHANGE_DIR}/notes.md" <<NOTESEOF
|
|
68
|
+
# ${CHANGE_SLUG} (minimal / T1)
|
|
69
|
+
|
|
70
|
+
Branch: \`${AGENT_BRANCH}\`
|
|
71
|
+
|
|
72
|
+
Describe the change in a sentence or two. Commit message is the spec of record.
|
|
73
|
+
|
|
74
|
+
## Handoff
|
|
75
|
+
|
|
76
|
+
- Handoff: change=\`${CHANGE_SLUG}\`; branch=\`${AGENT_BRANCH}\`; scope=\`TODO\`; action=\`continue this sandbox or finish cleanup after a usage-limit/manual takeover\`.
|
|
77
|
+
- Copy prompt: Continue \`${CHANGE_SLUG}\` on branch \`${AGENT_BRANCH}\`. Work inside the existing sandbox, review \`openspec/changes/${CHANGE_SLUG}/notes.md\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`gx branch finish --branch ${AGENT_BRANCH} --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`.
|
|
78
|
+
|
|
79
|
+
## Cleanup
|
|
80
|
+
|
|
81
|
+
- [ ] Run: \`gx branch finish --branch ${AGENT_BRANCH} --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`
|
|
82
|
+
- [ ] Record PR URL + \`MERGED\` state in the completion handoff.
|
|
83
|
+
- [ ] Confirm sandbox worktree is gone (\`git worktree list\`, \`git branch -a\`).
|
|
84
|
+
NOTESEOF
|
|
85
|
+
fi
|
|
86
|
+
echo "[gitguardex] OpenSpec change workspace (minimal) ready: ${CHANGE_DIR}"
|
|
87
|
+
echo "[gitguardex] Notes-only scaffold: ${CHANGE_DIR}/notes.md"
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
36
91
|
if [[ ! -f "${CHANGE_DIR}/proposal.md" ]]; then
|
|
37
92
|
cat > "${CHANGE_DIR}/proposal.md" <<PROPOSALEOF
|
|
38
93
|
## Why
|
|
@@ -51,6 +106,19 @@ fi
|
|
|
51
106
|
|
|
52
107
|
if [[ ! -f "${CHANGE_DIR}/tasks.md" ]]; then
|
|
53
108
|
cat > "${CHANGE_DIR}/tasks.md" <<TASKSEOF
|
|
109
|
+
## Definition of Done
|
|
110
|
+
|
|
111
|
+
This change is complete only when **all** of the following are true:
|
|
112
|
+
|
|
113
|
+
- Every checkbox below is checked.
|
|
114
|
+
- The agent branch reaches \`MERGED\` state on \`origin\` and the PR URL + state are recorded in the completion handoff.
|
|
115
|
+
- If any step blocks (test failure, conflict, ambiguous result), append a \`BLOCKED:\` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.
|
|
116
|
+
|
|
117
|
+
## Handoff
|
|
118
|
+
|
|
119
|
+
- Handoff: change=\`${CHANGE_SLUG}\`; branch=\`${AGENT_BRANCH}\`; scope=\`TODO\`; action=\`continue this sandbox or finish cleanup after a usage-limit/manual takeover\`.
|
|
120
|
+
- Copy prompt: Continue \`${CHANGE_SLUG}\` on branch \`${AGENT_BRANCH}\`. Work inside the existing sandbox, review \`openspec/changes/${CHANGE_SLUG}/tasks.md\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`gx branch finish --branch ${AGENT_BRANCH} --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`.
|
|
121
|
+
|
|
54
122
|
## 1. Specification
|
|
55
123
|
|
|
56
124
|
- [ ] 1.1 Finalize proposal scope and acceptance criteria for \`${CHANGE_SLUG}\`.
|
|
@@ -67,11 +135,11 @@ if [[ ! -f "${CHANGE_DIR}/tasks.md" ]]; then
|
|
|
67
135
|
- [ ] 3.2 Run \`openspec validate ${CHANGE_SLUG} --type change --strict\`.
|
|
68
136
|
- [ ] 3.3 Run \`openspec validate --specs\`.
|
|
69
137
|
|
|
70
|
-
## 4.
|
|
138
|
+
## 4. Cleanup (mandatory; run before claiming completion)
|
|
71
139
|
|
|
72
|
-
- [ ] 4.1
|
|
73
|
-
- [ ] 4.2 Record PR URL
|
|
74
|
-
- [ ] 4.3 Confirm sandbox
|
|
140
|
+
- [ ] 4.1 Run the cleanup pipeline: \`gx branch finish --branch ${AGENT_BRANCH} --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
|
|
141
|
+
- [ ] 4.2 Record the PR URL and final merge state (\`MERGED\`) in the completion handoff.
|
|
142
|
+
- [ ] 4.3 Confirm the sandbox worktree is gone (\`git worktree list\` no longer shows the agent path; \`git branch -a\` shows no surviving local/remote refs for the branch).
|
|
75
143
|
TASKSEOF
|
|
76
144
|
fi
|
|
77
145
|
|