@imdeadpool/guardex 7.0.15 → 7.0.18

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.
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ function resolveSessionSchemaModule() {
7
+ const candidates = [
8
+ path.resolve(__dirname, '..', 'vscode', 'guardex-active-agents', 'session-schema.js'),
9
+ path.resolve(__dirname, '..', 'templates', 'vscode', 'guardex-active-agents', 'session-schema.js'),
10
+ ];
11
+
12
+ for (const candidate of candidates) {
13
+ if (fs.existsSync(candidate)) {
14
+ return require(candidate);
15
+ }
16
+ }
17
+
18
+ throw new Error('Could not resolve Guardex active-agent session schema module.');
19
+ }
20
+
21
+ const sessionSchema = resolveSessionSchemaModule();
22
+
23
+ function usage() {
24
+ return (
25
+ 'Usage:\n' +
26
+ ' node scripts/agent-session-state.js start --repo <path> --branch <name> --task <task> --agent <agent> --worktree <path> --pid <pid> --cli <name>\n' +
27
+ ' node scripts/agent-session-state.js stop --repo <path> --branch <name>\n'
28
+ );
29
+ }
30
+
31
+ function parseOptions(argv) {
32
+ const options = {};
33
+ for (let index = 0; index < argv.length; index += 1) {
34
+ const token = argv[index];
35
+ if (!token.startsWith('--')) {
36
+ throw new Error(`Unexpected argument: ${token}`);
37
+ }
38
+ const key = token.slice(2);
39
+ const value = argv[index + 1];
40
+ if (!value || value.startsWith('--')) {
41
+ throw new Error(`Missing value for --${key}`);
42
+ }
43
+ options[key] = value;
44
+ index += 1;
45
+ }
46
+ return options;
47
+ }
48
+
49
+ function requireOption(options, key) {
50
+ const value = options[key];
51
+ if (!value) {
52
+ throw new Error(`Missing required option --${key}`);
53
+ }
54
+ return value;
55
+ }
56
+
57
+ function writeSessionRecord(options) {
58
+ const repoRoot = requireOption(options, 'repo');
59
+ const branch = requireOption(options, 'branch');
60
+ const record = sessionSchema.buildSessionRecord({
61
+ repoRoot,
62
+ branch,
63
+ taskName: requireOption(options, 'task'),
64
+ agentName: requireOption(options, 'agent'),
65
+ worktreePath: requireOption(options, 'worktree'),
66
+ pid: requireOption(options, 'pid'),
67
+ cliName: requireOption(options, 'cli'),
68
+ });
69
+
70
+ const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
71
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
72
+ fs.writeFileSync(targetPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
73
+ }
74
+
75
+ function removeSessionRecord(options) {
76
+ const repoRoot = requireOption(options, 'repo');
77
+ const branch = requireOption(options, 'branch');
78
+ const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
79
+ if (fs.existsSync(targetPath)) {
80
+ fs.unlinkSync(targetPath);
81
+ }
82
+ }
83
+
84
+ function main() {
85
+ const [command, ...rest] = process.argv.slice(2);
86
+ if (!command || ['-h', '--help', 'help'].includes(command)) {
87
+ process.stdout.write(usage());
88
+ return;
89
+ }
90
+
91
+ const options = parseOptions(rest);
92
+ if (command === 'start') {
93
+ writeSessionRecord(options);
94
+ return;
95
+ }
96
+ if (command === 'stop') {
97
+ removeSessionRecord(options);
98
+ return;
99
+ }
100
+
101
+ throw new Error(`Unknown subcommand: ${command}`);
102
+ }
103
+
104
+ try {
105
+ main();
106
+ } catch (error) {
107
+ process.stderr.write(`[guardex-active-session] ${error.message}\n`);
108
+ process.stderr.write(usage());
109
+ process.exitCode = 1;
110
+ }
@@ -6,6 +6,7 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
6
6
  BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
7
7
  BASE_BRANCH_EXPLICIT=0
8
8
  CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
9
+ NODE_BIN="${GUARDEX_NODE_BIN:-node}"
9
10
  AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
10
11
  AUTO_REVIEW_ON_CONFLICT_RAW="${GUARDEX_CODEX_AUTO_REVIEW_ON_CONFLICT:-true}"
11
12
  AUTO_CLEANUP_RAW="${GUARDEX_CODEX_AUTO_CLEANUP:-true}"
@@ -14,6 +15,7 @@ OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-true}"
14
15
  OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
15
16
  OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
16
17
  OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
18
+ OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
17
19
 
18
20
  normalize_bool() {
19
21
  local raw="${1:-}"
@@ -34,6 +36,19 @@ AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "1")"
34
36
  AUTO_WAIT_FOR_MERGE="$(normalize_bool "$AUTO_WAIT_FOR_MERGE_RAW" "1")"
35
37
  OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
36
38
 
39
+ resolve_openspec_masterplan_label() {
40
+ local raw="${OPENSPEC_MASTERPLAN_LABEL_RAW:-}"
41
+ local label
42
+
43
+ if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ -z "$raw" ]]; then
44
+ printf ''
45
+ return 0
46
+ fi
47
+
48
+ label="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')"
49
+ printf '%s' "$label"
50
+ }
51
+
37
52
  if [[ -n "$BASE_BRANCH" ]]; then
38
53
  BASE_BRANCH_EXPLICIT=1
39
54
  fi
@@ -129,6 +144,7 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
129
144
  exit 1
130
145
  fi
131
146
  repo_root="$(git rev-parse --show-toplevel)"
147
+ active_session_state_script="${repo_root}/scripts/agent-session-state.js"
132
148
 
133
149
  guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
134
150
  if [[ -f "$guardex_env_helper" ]]; then
@@ -161,11 +177,21 @@ sanitize_slug() {
161
177
  resolve_openspec_plan_slug() {
162
178
  local branch_name="$1"
163
179
  local task_slug
180
+ local masterplan_label=""
181
+ local branch_role=""
182
+ local branch_leaf=""
164
183
  task_slug="$(sanitize_slug "$TASK_NAME" "task")"
165
184
  if [[ -n "$OPENSPEC_PLAN_SLUG_OVERRIDE" ]]; then
166
185
  sanitize_slug "$OPENSPEC_PLAN_SLUG_OVERRIDE" "$task_slug"
167
186
  return 0
168
187
  fi
188
+ masterplan_label="$(resolve_openspec_masterplan_label)"
189
+ if [[ -n "$masterplan_label" ]] && [[ "$branch_name" =~ ^agent/([^/]+)/(.+)$ ]]; then
190
+ branch_role="${BASH_REMATCH[1]}"
191
+ branch_leaf="${BASH_REMATCH[2]}"
192
+ sanitize_slug "agent-${branch_role}-${masterplan_label}-${branch_leaf}" "$task_slug"
193
+ return 0
194
+ fi
169
195
  sanitize_slug "${branch_name//\//-}" "$task_slug"
170
196
  }
171
197
 
@@ -190,6 +216,23 @@ resolve_openspec_capability_slug() {
190
216
  sanitize_slug "$task_slug" "general-behavior"
191
217
  }
192
218
 
219
+ resolve_worktree_leaf() {
220
+ local branch_name="$1"
221
+ local masterplan_label=""
222
+ local branch_role=""
223
+ local branch_leaf=""
224
+
225
+ masterplan_label="$(resolve_openspec_masterplan_label)"
226
+ if [[ -n "$masterplan_label" ]] && [[ "$branch_name" =~ ^agent/([^/]+)/(.+)$ ]]; then
227
+ branch_role="${BASH_REMATCH[1]}"
228
+ branch_leaf="${BASH_REMATCH[2]}"
229
+ printf 'agent__%s__%s__%s' "$branch_role" "$masterplan_label" "$branch_leaf"
230
+ return 0
231
+ fi
232
+
233
+ printf '%s' "${branch_name//\//__}"
234
+ }
235
+
193
236
  hydrate_local_helper_in_worktree() {
194
237
  local worktree="$1"
195
238
  local relative_path="$2"
@@ -314,7 +357,7 @@ start_sandbox_fallback() {
314
357
 
315
358
  worktree_root="${repo_root}/.omx/agent-worktrees"
316
359
  mkdir -p "$worktree_root"
317
- worktree_path="${worktree_root}/${branch_name//\//__}"
360
+ worktree_path="${worktree_root}/$(resolve_worktree_leaf "$branch_name")"
318
361
  if [[ -e "$worktree_path" ]]; then
319
362
  echo "[codex-agent] Fallback worktree path already exists: $worktree_path" >&2
320
363
  return 1
@@ -346,7 +389,11 @@ initial_repo_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/nu
346
389
  start_output=""
347
390
  start_status=0
348
391
  set +e
349
- start_output="$(GUARDEX_OPENSPEC_AUTO_INIT=0 bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}" 2>&1)"
392
+ start_output="$(
393
+ GUARDEX_OPENSPEC_AUTO_INIT="$OPENSPEC_AUTO_INIT" \
394
+ GUARDEX_OPENSPEC_MASTERPLAN_LABEL="$OPENSPEC_MASTERPLAN_LABEL_RAW" \
395
+ bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}" 2>&1
396
+ )"
350
397
  start_status=$?
351
398
  set -e
352
399
 
@@ -401,6 +448,40 @@ has_origin_remote() {
401
448
  git -C "$repo_root" remote get-url origin >/dev/null 2>&1
402
449
  }
403
450
 
451
+ run_active_session_state() {
452
+ local action="$1"
453
+ shift
454
+
455
+ if [[ ! -f "$active_session_state_script" ]]; then
456
+ return 0
457
+ fi
458
+ if ! command -v "$NODE_BIN" >/dev/null 2>&1; then
459
+ return 0
460
+ fi
461
+
462
+ "$NODE_BIN" "$active_session_state_script" "$action" "$@" >/dev/null 2>&1 || true
463
+ }
464
+
465
+ record_active_session_state() {
466
+ local wt="$1"
467
+ local branch="$2"
468
+
469
+ run_active_session_state \
470
+ start \
471
+ --repo "$repo_root" \
472
+ --branch "$branch" \
473
+ --task "$TASK_NAME" \
474
+ --agent "$AGENT_NAME" \
475
+ --worktree "$wt" \
476
+ --pid "$$" \
477
+ --cli "$CODEX_BIN"
478
+ }
479
+
480
+ clear_active_session_state() {
481
+ local branch="$1"
482
+ run_active_session_state stop --repo "$repo_root" --branch "$branch"
483
+ }
484
+
404
485
  origin_remote_supports_pr_finish() {
405
486
  local origin_url
406
487
  origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
@@ -429,6 +510,31 @@ resolve_worktree_base_branch() {
429
510
  printf 'dev'
430
511
  }
431
512
 
513
+ print_takeover_prompt() {
514
+ local wt="$1"
515
+ local branch="$2"
516
+ local base_branch change_slug change_artifact finish_cmd
517
+
518
+ base_branch="$(resolve_worktree_base_branch "$wt")"
519
+ if [[ -z "$base_branch" ]]; then
520
+ base_branch="dev"
521
+ fi
522
+
523
+ change_slug="$(resolve_openspec_change_slug "$branch")"
524
+ change_artifact="openspec/changes/${change_slug}/tasks.md"
525
+ if [[ ! -f "${wt}/${change_artifact}" ]]; then
526
+ change_artifact="openspec/changes/${change_slug}/notes.md"
527
+ fi
528
+ if [[ ! -f "${wt}/${change_artifact}" ]]; then
529
+ change_artifact="openspec/changes/${change_slug}/"
530
+ fi
531
+
532
+ finish_cmd="bash scripts/agent-branch-finish.sh --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
533
+
534
+ echo "[codex-agent] Takeover sandbox: ${wt}"
535
+ 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}\`."
536
+ }
537
+
432
538
  sync_worktree_with_base() {
433
539
  local wt="$1"
434
540
  if ! has_origin_remote; then
@@ -788,6 +894,19 @@ if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
788
894
  exit 1
789
895
  fi
790
896
 
897
+ active_session_recorded=0
898
+ cleanup_active_session_state_on_exit() {
899
+ set +e
900
+ if [[ "${active_session_recorded:-0}" -eq 1 && -n "${worktree_branch:-}" && "${worktree_branch:-}" != "HEAD" ]]; then
901
+ clear_active_session_state "$worktree_branch"
902
+ active_session_recorded=0
903
+ fi
904
+ }
905
+
906
+ record_active_session_state "$worktree_path" "$worktree_branch"
907
+ active_session_recorded=1
908
+ trap cleanup_active_session_state_on_exit EXIT INT TERM
909
+
791
910
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
792
911
  cd "$worktree_path"
793
912
  set +e
@@ -796,6 +915,8 @@ codex_exit="$?"
796
915
  set -e
797
916
 
798
917
  cd "$repo_root"
918
+ cleanup_active_session_state_on_exit
919
+ trap - EXIT INT TERM
799
920
  final_exit="$codex_exit"
800
921
  auto_finish_completed=0
801
922
 
@@ -856,6 +977,7 @@ else
856
977
  if [[ "$auto_finish_completed" -eq 1 ]]; then
857
978
  echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
858
979
  else
980
+ print_takeover_prompt "$worktree_path" "$worktree_branch"
859
981
  echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
860
982
  echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
861
983
  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 2 ]]; then
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
- mkdir -p "$SPEC_DIR"
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. Completion
138
+ ## 4. Cleanup (mandatory; run before claiming completion)
71
139
 
72
- - [ ] 4.1 Finish the agent branch via PR merge + cleanup (\`gx finish --via-pr --wait-for-merge --cleanup\` or \`bash scripts/agent-branch-finish.sh --branch <agent-branch> --base <base-branch> --via-pr --wait-for-merge --cleanup\`).
73
- - [ ] 4.2 Record PR URL + final \`MERGED\` state in the completion handoff.
74
- - [ ] 4.3 Confirm sandbox cleanup (\`git worktree list\`, \`git branch -a\`) or capture a \`BLOCKED:\` handoff if merge/cleanup is pending.
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