@imdeadpool/guardex 7.0.16 → 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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.16",
4
- "description": "GitGuardex: hardened multi-agent git guardrails for parallel agent work.",
3
+ "version": "7.0.18",
4
+ "description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,
7
7
  "bin": {
@@ -7,22 +7,22 @@
7
7
  `GUARDEX_ON=0` disables Guardex for that repo.
8
8
  `GUARDEX_ON=1` explicitly enables Guardex for that repo again.
9
9
 
10
- **Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `scripts/agent-branch-start.sh "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. Never `git checkout <branch>` on a primary working tree (including nested repos); use `git worktree add` instead. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions - bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
10
+ **Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `gx branch start "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. Never `git checkout <branch>` on a primary working tree (including nested repos); use `git worktree add` instead. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions - bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
11
11
  For every new task, including follow-up work in the same chat/session, if an assigned agent sub-branch/worktree is already open, continue in that sub-branch instead of creating a fresh lane unless the user explicitly redirects scope.
12
12
  Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
13
13
 
14
- **Ownership.** Before editing, claim files: `scripts/agent-file-locks.py claim --branch "<agent-branch>" <file...>`. Before deleting, confirm the path is in your claim. Don't edit outside your scope unless reassigned.
14
+ **Ownership.** Before editing, claim files: `gx locks claim --branch "<agent-branch>" <file...>`. Before deleting, confirm the path is in your claim. Don't edit outside your scope unless reassigned.
15
15
 
16
16
  **Handoff gate.** Post a one-line handoff note (plan/change, owned scope, intended action) before editing. Re-read the latest handoffs before replacing others' code.
17
17
 
18
- **Completion.** Finish with `scripts/agent-branch-finish.sh --branch "<agent-branch>" --via-pr --wait-for-merge --cleanup` (or `gx finish --all`). Task is only complete when: commit pushed, PR URL recorded, state = `MERGED`, sandbox worktree pruned. If anything blocks, append a `BLOCKED:` note and stop - don't half-finish.
18
+ **Completion.** Finish with `gx branch finish --branch "<agent-branch>" --via-pr --wait-for-merge --cleanup` (or `gx finish --all`). Task is only complete when: commit pushed, PR URL recorded, state = `MERGED`, sandbox worktree pruned. If anything blocks, append a `BLOCKED:` note and stop - don't half-finish.
19
19
  OMX completion policy: when a task is done, the agent must commit the task changes, push the agent branch, and create/update a PR before considering the branch complete.
20
20
 
21
21
  **Parallel safety.** Assume other agents edit nearby. Never revert unrelated changes. Report conflicts in the handoff.
22
22
 
23
23
  **Reporting.** Every completion handoff includes: files changed, behavior touched, verification commands + results, risks/follow-ups.
24
24
 
25
- **OpenSpec (when change-driven).** Keep `openspec/changes/<slug>/tasks.md` checkboxes current during work, not batched at the end. Task scaffolds and manual task edits must include an explicit final completion/cleanup section that ends with PR merge + sandbox cleanup (`gx finish --via-pr --wait-for-merge --cleanup` or `scripts/agent-branch-finish.sh ... --cleanup`) and records PR URL + final `MERGED` evidence. Verify specs with `openspec validate --specs` before archive. Don't archive unverified.
25
+ **OpenSpec (when change-driven).** Keep `openspec/changes/<slug>/tasks.md` checkboxes current during work, not batched at the end. Task scaffolds and manual task edits must include an explicit final completion/cleanup section that ends with PR merge + sandbox cleanup (`gx finish --via-pr --wait-for-merge --cleanup` or `gx branch finish ... --cleanup`) and records PR URL + final `MERGED` evidence. Verify specs with `openspec validate --specs` before archive. Don't archive unverified.
26
26
 
27
27
  **Version bumps.** If a change bumps a published version, the same PR updates release notes/changelog.
28
28
  <!-- multiagent-safety:END -->
@@ -64,7 +64,7 @@ echo "[agent-primary-branch-guard] Primary checkout switched branches." >&2
64
64
  echo "[agent-primary-branch-guard] from: $prev_branch (protected)" >&2
65
65
  echo "[agent-primary-branch-guard] to: $new_branch" >&2
66
66
  echo "[agent-primary-branch-guard] The primary working tree must stay on its base/protected branch." >&2
67
- echo "[agent-primary-branch-guard] Use 'git worktree add' (or scripts/agent-branch-start.sh) for feature work." >&2
67
+ echo "[agent-primary-branch-guard] Use 'git worktree add' (or gx branch start) for feature work." >&2
68
68
 
69
69
  if [[ "$is_agent" == "1" ]]; then
70
70
  echo "[agent-primary-branch-guard] Agent session detected — reverting to '$prev_branch'." >&2
@@ -32,17 +32,30 @@ if [[ "$branch" != "$base_branch" ]]; then
32
32
  exit 0
33
33
  fi
34
34
 
35
- cli_path="$repo_root/bin/multiagent-safety.js"
36
- if [[ ! -f "$cli_path" ]]; then
35
+ if [[ -n "${GUARDEX_CLI_ENTRY:-}" ]]; then
36
+ node_bin="${GUARDEX_NODE_BIN:-node}"
37
+ if command -v "$node_bin" >/dev/null 2>&1; then
38
+ "$node_bin" "$GUARDEX_CLI_ENTRY" cleanup \
39
+ --target "$repo_root" \
40
+ --base "$base_branch" \
41
+ --include-pr-merged \
42
+ --keep-clean-worktrees >/dev/null 2>&1 || true
43
+ fi
37
44
  exit 0
38
45
  fi
39
46
 
40
- node_bin="${GUARDEX_NODE_BIN:-node}"
41
- if ! command -v "$node_bin" >/dev/null 2>&1; then
42
- exit 0
47
+ cli_bin="${GUARDEX_CLI_BIN:-}"
48
+ if [[ -z "$cli_bin" ]]; then
49
+ if command -v gx >/dev/null 2>&1; then
50
+ cli_bin="gx"
51
+ elif command -v gitguardex >/dev/null 2>&1; then
52
+ cli_bin="gitguardex"
53
+ else
54
+ exit 0
55
+ fi
43
56
  fi
44
57
 
45
- "$node_bin" "$cli_path" cleanup \
58
+ "$cli_bin" cleanup \
46
59
  --target "$repo_root" \
47
60
  --base "$base_branch" \
48
61
  --include-pr-merged \
@@ -116,11 +116,11 @@ if [[ "$should_require_codex_agent_branch" == "1" && "${GUARDEX_ALLOW_CODEX_ON_N
116
116
 
117
117
  cat >&2 <<'MSG'
118
118
  [guardex-preedit-guard] Codex edit/commit detected on a protected branch.
119
- GuardeX requires Codex work to run from an isolated agent/* branch.
119
+ GitGuardex requires Codex work to run from an isolated agent/* branch.
120
120
  Start the sub-branch/worktree with:
121
- bash scripts/codex-agent.sh "<task-or-plan>" "<agent-name>"
121
+ gx branch start "<task-or-plan>" "<agent-name>"
122
122
  Or manually:
123
- bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
123
+ gx branch start "<task-or-plan>" "<agent-name>"
124
124
  Then commit from the created agent/* branch.
125
125
 
126
126
  Temporary bypass (not recommended):
@@ -132,7 +132,7 @@ MSG
132
132
  cat >&2 <<'MSG'
133
133
  [codex-branch-guard] Codex agent commit blocked on non-agent branch.
134
134
  Use isolated branch/worktree first:
135
- bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
135
+ gx branch start "<task-or-plan>" "<agent-name>"
136
136
  Then commit from the created agent/* branch.
137
137
 
138
138
  Temporary bypass (not recommended):
@@ -163,9 +163,9 @@ if [[ "$is_protected_branch" == "1" ]]; then
163
163
  cat >&2 <<'MSG'
164
164
  [agent-branch-guard] Direct commits on protected branches are blocked.
165
165
  Use an agent branch first:
166
- bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
166
+ gx branch start "<task-or-plan>" "<agent-name>"
167
167
  After finishing work:
168
- bash scripts/agent-branch-finish.sh
168
+ gx branch finish
169
169
 
170
170
  Temporary bypass (not recommended):
171
171
  ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
@@ -177,7 +177,7 @@ if [[ "$is_agent_session" == "1" && "$branch" != agent/* ]]; then
177
177
  cat >&2 <<'MSG'
178
178
  [agent-branch-guard] Agent commits must run on dedicated agent/* branches.
179
179
  Start an agent branch first:
180
- bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
180
+ gx branch start "<task-or-plan>" "<agent-name>"
181
181
  Then commit on that branch.
182
182
 
183
183
  Temporary bypass (not recommended):
@@ -199,7 +199,7 @@ if [[ "$branch" == agent/* ]]; then
199
199
  cat >&2 <<'MSG'
200
200
  [agent-branch-guard] Agent branch commits require file ownership locks.
201
201
  Claim files first:
202
- python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
202
+ gx locks claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
203
203
  MSG
204
204
  exit 1
205
205
  fi
@@ -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}"
@@ -143,6 +144,7 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
143
144
  exit 1
144
145
  fi
145
146
  repo_root="$(git rev-parse --show-toplevel)"
147
+ active_session_state_script="${repo_root}/scripts/agent-session-state.js"
146
148
 
147
149
  guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
148
150
  if [[ -f "$guardex_env_helper" ]]; then
@@ -446,6 +448,40 @@ has_origin_remote() {
446
448
  git -C "$repo_root" remote get-url origin >/dev/null 2>&1
447
449
  }
448
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
+
449
485
  origin_remote_supports_pr_finish() {
450
486
  local origin_url
451
487
  origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
@@ -474,6 +510,31 @@ resolve_worktree_base_branch() {
474
510
  printf 'dev'
475
511
  }
476
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
+
477
538
  sync_worktree_with_base() {
478
539
  local wt="$1"
479
540
  if ! has_origin_remote; then
@@ -833,6 +894,19 @@ if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
833
894
  exit 1
834
895
  fi
835
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
+
836
910
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
837
911
  cd "$worktree_path"
838
912
  set +e
@@ -841,6 +915,8 @@ codex_exit="$?"
841
915
  set -e
842
916
 
843
917
  cd "$repo_root"
918
+ cleanup_active_session_state_on_exit
919
+ trap - EXIT INT TERM
844
920
  final_exit="$codex_exit"
845
921
  auto_finish_completed=0
846
922
 
@@ -901,6 +977,7 @@ else
901
977
  if [[ "$auto_finish_completed" -eq 1 ]]; then
902
978
  echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
903
979
  else
980
+ print_takeover_prompt "$worktree_path" "$worktree_branch"
904
981
  echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
905
982
  echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
906
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