@imdeadpool/guardex 5.0.12 → 5.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -4
- package/bin/multiagent-safety.js +547 -62
- package/package.json +1 -1
- package/templates/AGENTS.multiagent-safety.md +18 -6
- package/templates/githooks/post-merge +43 -0
- package/templates/githooks/pre-commit +24 -15
- package/templates/githooks/pre-push +3 -3
- package/templates/scripts/agent-branch-finish.sh +0 -22
- package/templates/scripts/agent-branch-start.sh +66 -1
- package/templates/scripts/codex-agent.sh +82 -27
- package/templates/scripts/openspec/init-change-workspace.sh +87 -0
package/package.json
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
- OMX completion policy: when a task is done, the agent must commit the task changes, push the agent branch, and create/update a PR for those changes (via `codex-agent` or `agent-branch-finish`).
|
|
17
17
|
- Auto-finish now waits for required checks/merge and then cleans merged sandbox branch/worktree by default.
|
|
18
18
|
- Use `--no-cleanup` only when you explicitly need to keep a merged sandbox for audit/debug follow-up.
|
|
19
|
-
- If codex-agent auto-finish cannot complete, immediately run `scripts/agent-branch-finish.sh --branch "<agent-branch>" --via-pr --wait-for-merge` and keep the branch open until checks/review pass.
|
|
20
|
-
- If merge/rebase conflicts block auto-finish, run a conflict-resolution review pass in that sandbox branch, then rerun `agent-branch-finish.sh --via-pr` until merged.
|
|
19
|
+
- If codex-agent auto-finish cannot complete, immediately run `scripts/agent-branch-finish.sh --branch "<agent-branch>" --base dev --via-pr --wait-for-merge` and keep the branch open until checks/review pass.
|
|
20
|
+
- If merge/rebase conflicts block auto-finish, run a conflict-resolution review pass in that sandbox branch, then rerun `agent-branch-finish.sh --base dev --via-pr --wait-for-merge` until merged.
|
|
21
21
|
- Completion is not valid until these are true: commit exists on the agent branch, branch is pushed to `origin`, and PR/merge status is produced by `agent-branch-finish.sh` or `codex-agent`.
|
|
22
22
|
- 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; otherwise create a fresh one from the current local base snapshot with `scripts/agent-branch-start.sh`.
|
|
23
23
|
- Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
@@ -46,12 +46,13 @@
|
|
|
46
46
|
- Verification commands + results
|
|
47
47
|
- Risks / follow-ups
|
|
48
48
|
|
|
49
|
-
## OpenSpec
|
|
49
|
+
## OpenSpec Workspaces (required for agent sub-branch changes)
|
|
50
50
|
|
|
51
|
-
OMX Codex execution flows must use OpenSpec. `scripts/codex-agent.sh` bootstraps
|
|
52
|
-
per-branch
|
|
51
|
+
OMX Codex execution flows must use OpenSpec. `scripts/codex-agent.sh` bootstraps
|
|
52
|
+
per-branch OpenSpec workspaces automatically:
|
|
53
53
|
|
|
54
54
|
```text
|
|
55
|
+
openspec/changes/<agent-branch-slug>/
|
|
55
56
|
openspec/plan/<agent-branch-slug>/
|
|
56
57
|
```
|
|
57
58
|
|
|
@@ -59,10 +60,21 @@ For manual `scripts/agent-branch-start.sh` usage, enable auto-bootstrap with
|
|
|
59
60
|
`MUSAFETY_OPENSPEC_AUTO_INIT=true` or scaffold manually before implementation:
|
|
60
61
|
|
|
61
62
|
```bash
|
|
63
|
+
bash scripts/openspec/init-change-workspace.sh "<change-slug>" "<capability-slug>"
|
|
62
64
|
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
Expected shape:
|
|
67
|
+
Expected change shape:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
openspec/changes/<change-slug>/
|
|
71
|
+
.openspec.yaml
|
|
72
|
+
proposal.md
|
|
73
|
+
tasks.md
|
|
74
|
+
specs/<capability-slug>/spec.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Expected plan shape:
|
|
66
78
|
|
|
67
79
|
```text
|
|
68
80
|
openspec/plan/<plan-slug>/
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [[ "${MUSAFETY_DISABLE_POST_MERGE_CLEANUP:-0}" == "1" ]]; then
|
|
5
|
+
exit 0
|
|
6
|
+
fi
|
|
7
|
+
|
|
8
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
9
|
+
if [[ -z "$repo_root" ]]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
14
|
+
if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
base_branch="${MUSAFETY_BASE_BRANCH:-$(git -C "$repo_root" config --get multiagent.baseBranch || true)}"
|
|
19
|
+
if [[ -z "$base_branch" ]]; then
|
|
20
|
+
base_branch="dev"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [[ "$branch" != "$base_branch" ]]; then
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
cli_path="$repo_root/bin/multiagent-safety.js"
|
|
28
|
+
if [[ ! -f "$cli_path" ]]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
node_bin="${MUSAFETY_NODE_BIN:-node}"
|
|
33
|
+
if ! command -v "$node_bin" >/dev/null 2>&1; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
"$node_bin" "$cli_path" cleanup \
|
|
38
|
+
--target "$repo_root" \
|
|
39
|
+
--base "$base_branch" \
|
|
40
|
+
--include-pr-merged \
|
|
41
|
+
--keep-clean-worktrees >/dev/null 2>&1 || true
|
|
42
|
+
|
|
43
|
+
exit 0
|
|
@@ -30,7 +30,7 @@ fi
|
|
|
30
30
|
|
|
31
31
|
allow_vscode_protected_raw="${MUSAFETY_ALLOW_VSCODE_PROTECTED_BRANCH_WRITES:-$(git config --get multiagent.allowVscodeProtectedBranchWrites || true)}"
|
|
32
32
|
if [[ -z "$allow_vscode_protected_raw" ]]; then
|
|
33
|
-
allow_vscode_protected_raw="
|
|
33
|
+
allow_vscode_protected_raw="false"
|
|
34
34
|
fi
|
|
35
35
|
allow_vscode_protected="$(printf '%s' "$allow_vscode_protected_raw" | tr '[:upper:]' '[:lower:]')"
|
|
36
36
|
|
|
@@ -55,15 +55,6 @@ for protected_branch in $protected_branches_raw; do
|
|
|
55
55
|
fi
|
|
56
56
|
done
|
|
57
57
|
|
|
58
|
-
is_local_only_branch=0
|
|
59
|
-
if [[ "$is_protected_branch" == "1" ]]; then
|
|
60
|
-
upstream_ref="$(git for-each-ref --format='%(upstream:short)' "refs/heads/${branch}" | head -n 1)"
|
|
61
|
-
remote_branch_ref="$(git for-each-ref --format='%(refname:short)' "refs/remotes/*/${branch}" | head -n 1)"
|
|
62
|
-
if [[ -z "$upstream_ref" && -z "$remote_branch_ref" ]]; then
|
|
63
|
-
is_local_only_branch=1
|
|
64
|
-
fi
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
58
|
codex_require_agent_branch_raw="${MUSAFETY_CODEX_REQUIRE_AGENT_BRANCH:-$(git config --get multiagent.codexRequireAgentBranch || true)}"
|
|
68
59
|
if [[ -z "$codex_require_agent_branch_raw" ]]; then
|
|
69
60
|
codex_require_agent_branch_raw="true"
|
|
@@ -134,7 +125,7 @@ fi
|
|
|
134
125
|
|
|
135
126
|
if [[ "$is_protected_branch" == "1" ]]; then
|
|
136
127
|
if [[ "$is_codex_session" != "1" && "$is_vscode_git_context" == "1" ]]; then
|
|
137
|
-
if [[ "$allow_vscode_protected_branch_writes" == "1"
|
|
128
|
+
if [[ "$allow_vscode_protected_branch_writes" == "1" ]]; then
|
|
138
129
|
exit 0
|
|
139
130
|
fi
|
|
140
131
|
fi
|
|
@@ -155,11 +146,21 @@ Use an agent branch first:
|
|
|
155
146
|
After finishing work:
|
|
156
147
|
bash scripts/agent-branch-finish.sh
|
|
157
148
|
|
|
158
|
-
Optional repo
|
|
159
|
-
git config multiagent.allowVscodeProtectedBranchWrites
|
|
149
|
+
Optional repo opt-in for VS Code protected-branch commits:
|
|
150
|
+
git config multiagent.allowVscodeProtectedBranchWrites true
|
|
151
|
+
|
|
152
|
+
Temporary bypass (not recommended):
|
|
153
|
+
ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
|
|
154
|
+
MSG
|
|
155
|
+
exit 1
|
|
156
|
+
fi
|
|
160
157
|
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
if [[ "$is_agent_context" == "1" && "$branch" != agent/* ]]; then
|
|
159
|
+
cat >&2 <<'MSG'
|
|
160
|
+
[agent-branch-guard] Agent commits must run on dedicated agent/* branches.
|
|
161
|
+
Start an agent branch first:
|
|
162
|
+
bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
|
|
163
|
+
Then commit on that branch.
|
|
163
164
|
|
|
164
165
|
Temporary bypass (not recommended):
|
|
165
166
|
ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
|
|
@@ -168,6 +169,14 @@ MSG
|
|
|
168
169
|
fi
|
|
169
170
|
|
|
170
171
|
if [[ "$branch" == agent/* ]]; then
|
|
172
|
+
if [[ "${MUSAFETY_AUTOCLAIM_STAGED_LOCKS:-1}" == "1" ]]; then
|
|
173
|
+
while IFS= read -r staged_file; do
|
|
174
|
+
[[ -z "$staged_file" ]] && continue
|
|
175
|
+
[[ "$staged_file" == ".omx/state/agent-file-locks.json" ]] && continue
|
|
176
|
+
python3 scripts/agent-file-locks.py claim --branch "$branch" "$staged_file" >/dev/null 2>&1 || true
|
|
177
|
+
done < <(git diff --cached --name-only --diff-filter=ACMRDTUXB)
|
|
178
|
+
fi
|
|
179
|
+
|
|
171
180
|
if ! python3 scripts/agent-file-locks.py validate --branch "$branch" --staged; then
|
|
172
181
|
cat >&2 <<'MSG'
|
|
173
182
|
[agent-branch-guard] Agent branch commits require file ownership locks.
|
|
@@ -12,7 +12,7 @@ fi
|
|
|
12
12
|
|
|
13
13
|
allow_vscode_protected_raw="${MUSAFETY_ALLOW_VSCODE_PROTECTED_BRANCH_WRITES:-$(git config --get multiagent.allowVscodeProtectedBranchWrites || true)}"
|
|
14
14
|
if [[ -z "$allow_vscode_protected_raw" ]]; then
|
|
15
|
-
allow_vscode_protected_raw="
|
|
15
|
+
allow_vscode_protected_raw="false"
|
|
16
16
|
fi
|
|
17
17
|
allow_vscode_protected="$(printf '%s' "$allow_vscode_protected_raw" | tr '[:upper:]' '[:lower:]')"
|
|
18
18
|
|
|
@@ -77,8 +77,8 @@ if [[ "${#blocked_refs[@]}" -gt 0 ]]; then
|
|
|
77
77
|
echo "[agent-branch-guard] Push to protected branch blocked."
|
|
78
78
|
echo "[agent-branch-guard] Protected target(s): ${blocked_refs[*]}"
|
|
79
79
|
echo "[agent-branch-guard] Use an agent branch and merge via PR."
|
|
80
|
-
echo "[agent-branch-guard] Optional repo
|
|
81
|
-
echo " git config multiagent.allowVscodeProtectedBranchWrites
|
|
80
|
+
echo "[agent-branch-guard] Optional repo opt-in for VS Code protected-branch push:"
|
|
81
|
+
echo " git config multiagent.allowVscodeProtectedBranchWrites true"
|
|
82
82
|
echo
|
|
83
83
|
echo "Temporary bypass (not recommended):"
|
|
84
84
|
echo " ALLOW_PUSH_ON_PROTECTED_BRANCH=1 git push ..."
|
|
@@ -162,28 +162,6 @@ if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
|
|
|
162
162
|
fi
|
|
163
163
|
fi
|
|
164
164
|
|
|
165
|
-
if [[ -z "$BASE_BRANCH" ]]; then
|
|
166
|
-
branch_stored_base="$(git -C "$repo_root" config --get "branch.${SOURCE_BRANCH}.musafetyBase" || true)"
|
|
167
|
-
if [[ -n "$branch_stored_base" ]]; then
|
|
168
|
-
BASE_BRANCH="$branch_stored_base"
|
|
169
|
-
fi
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
if [[ -z "$BASE_BRANCH" ]]; then
|
|
173
|
-
source_upstream="$(git -C "$repo_root" for-each-ref --format='%(upstream:short)' "refs/heads/${SOURCE_BRANCH}" | head -n 1)"
|
|
174
|
-
source_upstream="${source_upstream:-}"
|
|
175
|
-
if [[ "$source_upstream" == */* ]]; then
|
|
176
|
-
BASE_BRANCH="${source_upstream#*/}"
|
|
177
|
-
fi
|
|
178
|
-
fi
|
|
179
|
-
|
|
180
|
-
if [[ -z "$BASE_BRANCH" ]]; then
|
|
181
|
-
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
182
|
-
if [[ -n "$current_branch" && "$current_branch" != "HEAD" && "$current_branch" != "$SOURCE_BRANCH" ]]; then
|
|
183
|
-
BASE_BRANCH="$current_branch"
|
|
184
|
-
fi
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
165
|
if [[ -z "$BASE_BRANCH" ]]; then
|
|
188
166
|
BASE_BRANCH="dev"
|
|
189
167
|
fi
|
|
@@ -8,6 +8,8 @@ BASE_BRANCH_EXPLICIT=0
|
|
|
8
8
|
WORKTREE_ROOT_REL=".omx/agent-worktrees"
|
|
9
9
|
OPENSPEC_AUTO_INIT_RAW="${MUSAFETY_OPENSPEC_AUTO_INIT:-false}"
|
|
10
10
|
OPENSPEC_PLAN_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_PLAN_SLUG:-}"
|
|
11
|
+
OPENSPEC_CHANGE_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CHANGE_SLUG:-}"
|
|
12
|
+
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
11
13
|
POSITIONAL_ARGS=()
|
|
12
14
|
|
|
13
15
|
while [[ $# -gt 0 ]]; do
|
|
@@ -109,6 +111,25 @@ resolve_openspec_plan_slug() {
|
|
|
109
111
|
sanitize_slug "${branch_name//\//-}" "$task_slug"
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
resolve_openspec_change_slug() {
|
|
115
|
+
local branch_name="$1"
|
|
116
|
+
local task_slug="$2"
|
|
117
|
+
if [[ -n "$OPENSPEC_CHANGE_SLUG_OVERRIDE" ]]; then
|
|
118
|
+
sanitize_slug "$OPENSPEC_CHANGE_SLUG_OVERRIDE" "$task_slug"
|
|
119
|
+
return 0
|
|
120
|
+
fi
|
|
121
|
+
sanitize_slug "${branch_name//\//-}" "$task_slug"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
resolve_openspec_capability_slug() {
|
|
125
|
+
local task_slug="$1"
|
|
126
|
+
if [[ -n "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" ]]; then
|
|
127
|
+
sanitize_slug "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" "$task_slug"
|
|
128
|
+
return 0
|
|
129
|
+
fi
|
|
130
|
+
sanitize_slug "$task_slug" "general-behavior"
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
resolve_active_codex_snapshot_name() {
|
|
113
134
|
local override="${MUSAFETY_CODEX_AUTH_SNAPSHOT:-}"
|
|
114
135
|
if [[ -n "$override" ]]; then
|
|
@@ -250,6 +271,44 @@ initialize_openspec_plan_workspace() {
|
|
|
250
271
|
echo "[agent-branch-start] OpenSpec plan workspace: ${worktree}/openspec/plan/${plan_slug}"
|
|
251
272
|
}
|
|
252
273
|
|
|
274
|
+
initialize_openspec_change_workspace() {
|
|
275
|
+
local repo="$1"
|
|
276
|
+
local worktree="$2"
|
|
277
|
+
local change_slug="$3"
|
|
278
|
+
local capability_slug="$4"
|
|
279
|
+
|
|
280
|
+
hydrate_local_helper_in_worktree "$repo" "$worktree" "scripts/openspec/init-change-workspace.sh"
|
|
281
|
+
|
|
282
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
283
|
+
return 0
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
local openspec_script="${worktree}/scripts/openspec/init-change-workspace.sh"
|
|
287
|
+
if [[ ! -f "$openspec_script" ]]; then
|
|
288
|
+
echo "[agent-branch-start] OpenSpec change init script is missing in sandbox worktree." >&2
|
|
289
|
+
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
|
|
290
|
+
return 1
|
|
291
|
+
fi
|
|
292
|
+
if [[ ! -x "$openspec_script" ]]; then
|
|
293
|
+
chmod +x "$openspec_script" 2>/dev/null || true
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
local init_output=""
|
|
297
|
+
if ! init_output="$(
|
|
298
|
+
cd "$worktree"
|
|
299
|
+
bash "scripts/openspec/init-change-workspace.sh" "$change_slug" "$capability_slug" 2>&1
|
|
300
|
+
)"; then
|
|
301
|
+
printf '%s\n' "$init_output" >&2
|
|
302
|
+
echo "[agent-branch-start] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
303
|
+
return 1
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
if [[ -n "$init_output" ]]; then
|
|
307
|
+
printf '%s\n' "$init_output"
|
|
308
|
+
fi
|
|
309
|
+
echo "[agent-branch-start] OpenSpec change workspace: ${worktree}/openspec/changes/${change_slug}"
|
|
310
|
+
}
|
|
311
|
+
|
|
253
312
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
254
313
|
echo "[agent-branch-start] Not inside a git repository." >&2
|
|
255
314
|
exit 1
|
|
@@ -312,6 +371,8 @@ worktree_root="${repo_root}/${WORKTREE_ROOT_REL}"
|
|
|
312
371
|
mkdir -p "$worktree_root"
|
|
313
372
|
worktree_path="${worktree_root}/${branch_name//\//__}"
|
|
314
373
|
openspec_plan_slug="$(resolve_openspec_plan_slug "$branch_name" "$task_slug")"
|
|
374
|
+
openspec_change_slug="$(resolve_openspec_change_slug "$branch_name" "$task_slug")"
|
|
375
|
+
openspec_capability_slug="$(resolve_openspec_capability_slug "$task_slug")"
|
|
315
376
|
|
|
316
377
|
if [[ -e "$worktree_path" ]]; then
|
|
317
378
|
echo "[agent-branch-start] Worktree path already exists: ${worktree_path}" >&2
|
|
@@ -364,15 +425,19 @@ hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-ag
|
|
|
364
425
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
|
|
365
426
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
|
|
366
427
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
|
|
428
|
+
if ! initialize_openspec_change_workspace "$repo_root" "$worktree_path" "$openspec_change_slug" "$openspec_capability_slug"; then
|
|
429
|
+
exit 1
|
|
430
|
+
fi
|
|
367
431
|
if ! initialize_openspec_plan_workspace "$repo_root" "$worktree_path" "$openspec_plan_slug"; then
|
|
368
432
|
exit 1
|
|
369
433
|
fi
|
|
370
434
|
|
|
371
435
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
372
436
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
437
|
+
echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
|
|
373
438
|
echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
|
|
374
439
|
echo "[agent-branch-start] Next steps:"
|
|
375
440
|
echo " cd \"${worktree_path}\""
|
|
376
441
|
echo " python3 scripts/agent-file-locks.py claim --branch \"${branch_name}\" <file...>"
|
|
377
442
|
echo " # implement + commit"
|
|
378
|
-
echo " bash scripts/agent-branch-finish.sh --branch \"${branch_name}\""
|
|
443
|
+
echo " bash scripts/agent-branch-finish.sh --branch \"${branch_name}\" --base dev --via-pr --wait-for-merge"
|
|
@@ -12,6 +12,8 @@ AUTO_CLEANUP_RAW="${MUSAFETY_CODEX_AUTO_CLEANUP:-true}"
|
|
|
12
12
|
AUTO_WAIT_FOR_MERGE_RAW="${MUSAFETY_CODEX_WAIT_FOR_MERGE:-true}"
|
|
13
13
|
OPENSPEC_AUTO_INIT_RAW="${MUSAFETY_OPENSPEC_AUTO_INIT:-true}"
|
|
14
14
|
OPENSPEC_PLAN_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_PLAN_SLUG:-}"
|
|
15
|
+
OPENSPEC_CHANGE_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CHANGE_SLUG:-}"
|
|
16
|
+
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
15
17
|
|
|
16
18
|
normalize_bool() {
|
|
17
19
|
local raw="${1:-}"
|
|
@@ -150,6 +152,27 @@ resolve_openspec_plan_slug() {
|
|
|
150
152
|
sanitize_slug "${branch_name//\//-}" "$task_slug"
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
resolve_openspec_change_slug() {
|
|
156
|
+
local branch_name="$1"
|
|
157
|
+
local task_slug
|
|
158
|
+
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
159
|
+
if [[ -n "$OPENSPEC_CHANGE_SLUG_OVERRIDE" ]]; then
|
|
160
|
+
sanitize_slug "$OPENSPEC_CHANGE_SLUG_OVERRIDE" "$task_slug"
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
sanitize_slug "${branch_name//\//-}" "$task_slug"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
resolve_openspec_capability_slug() {
|
|
167
|
+
local task_slug
|
|
168
|
+
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
169
|
+
if [[ -n "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" ]]; then
|
|
170
|
+
sanitize_slug "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" "$task_slug"
|
|
171
|
+
return 0
|
|
172
|
+
fi
|
|
173
|
+
sanitize_slug "$task_slug" "general-behavior"
|
|
174
|
+
}
|
|
175
|
+
|
|
153
176
|
hydrate_local_helper_in_worktree() {
|
|
154
177
|
local worktree="$1"
|
|
155
178
|
local relative_path="$2"
|
|
@@ -192,13 +215,6 @@ resolve_start_base_branch() {
|
|
|
192
215
|
return 0
|
|
193
216
|
fi
|
|
194
217
|
|
|
195
|
-
local current_branch
|
|
196
|
-
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
197
|
-
if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]]; then
|
|
198
|
-
printf '%s' "$current_branch"
|
|
199
|
-
return 0
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
218
|
printf 'dev'
|
|
203
219
|
}
|
|
204
220
|
|
|
@@ -338,30 +354,20 @@ has_origin_remote() {
|
|
|
338
354
|
}
|
|
339
355
|
|
|
340
356
|
resolve_worktree_base_branch() {
|
|
341
|
-
local
|
|
357
|
+
local _wt="$1"
|
|
342
358
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
|
|
343
359
|
printf '%s' "$BASE_BRANCH"
|
|
344
360
|
return 0
|
|
345
361
|
fi
|
|
346
362
|
|
|
347
|
-
local branch
|
|
348
|
-
branch="$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
349
|
-
if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
|
|
350
|
-
return 0
|
|
351
|
-
fi
|
|
352
|
-
|
|
353
|
-
local stored_base
|
|
354
|
-
stored_base="$(git -C "$repo_root" config --get "branch.${branch}.musafetyBase" || true)"
|
|
355
|
-
if [[ -n "$stored_base" ]]; then
|
|
356
|
-
printf '%s' "$stored_base"
|
|
357
|
-
return 0
|
|
358
|
-
fi
|
|
359
|
-
|
|
360
363
|
local configured_base
|
|
361
364
|
configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
|
|
362
365
|
if [[ -n "$configured_base" ]]; then
|
|
363
366
|
printf '%s' "$configured_base"
|
|
367
|
+
return 0
|
|
364
368
|
fi
|
|
369
|
+
|
|
370
|
+
printf 'dev'
|
|
365
371
|
}
|
|
366
372
|
|
|
367
373
|
sync_worktree_with_base() {
|
|
@@ -443,6 +449,43 @@ ensure_openspec_plan_workspace() {
|
|
|
443
449
|
echo "[codex-agent] OpenSpec plan workspace: ${wt}/openspec/plan/${plan_slug}"
|
|
444
450
|
}
|
|
445
451
|
|
|
452
|
+
ensure_openspec_change_workspace() {
|
|
453
|
+
local wt="$1"
|
|
454
|
+
local branch="$2"
|
|
455
|
+
|
|
456
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
457
|
+
return 0
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-change-workspace.sh"
|
|
461
|
+
|
|
462
|
+
local openspec_script="${wt}/scripts/openspec/init-change-workspace.sh"
|
|
463
|
+
if [[ ! -f "$openspec_script" ]]; then
|
|
464
|
+
echo "[codex-agent] Missing OpenSpec change init script in sandbox: ${openspec_script}" >&2
|
|
465
|
+
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
|
|
466
|
+
return 1
|
|
467
|
+
fi
|
|
468
|
+
if [[ ! -x "$openspec_script" ]]; then
|
|
469
|
+
chmod +x "$openspec_script" 2>/dev/null || true
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
local change_slug capability_slug init_output=""
|
|
473
|
+
change_slug="$(resolve_openspec_change_slug "$branch")"
|
|
474
|
+
capability_slug="$(resolve_openspec_capability_slug)"
|
|
475
|
+
if ! init_output="$(
|
|
476
|
+
cd "$wt"
|
|
477
|
+
bash "scripts/openspec/init-change-workspace.sh" "$change_slug" "$capability_slug" 2>&1
|
|
478
|
+
)"; then
|
|
479
|
+
printf '%s\n' "$init_output" >&2
|
|
480
|
+
echo "[codex-agent] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
481
|
+
return 1
|
|
482
|
+
fi
|
|
483
|
+
if [[ -n "$init_output" ]]; then
|
|
484
|
+
printf '%s\n' "$init_output"
|
|
485
|
+
fi
|
|
486
|
+
echo "[codex-agent] OpenSpec change workspace: ${wt}/openspec/changes/${change_slug}"
|
|
487
|
+
}
|
|
488
|
+
|
|
446
489
|
worktree_has_changes() {
|
|
447
490
|
local wt="$1"
|
|
448
491
|
if ! git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
|
|
@@ -598,12 +641,18 @@ looks_like_conflict_failure() {
|
|
|
598
641
|
run_finish_flow() {
|
|
599
642
|
local wt="$1"
|
|
600
643
|
local branch="$2"
|
|
644
|
+
local finish_base_branch=""
|
|
601
645
|
local finish_output=""
|
|
602
646
|
local -a finish_args
|
|
603
647
|
|
|
604
648
|
finish_args=(--branch "$branch")
|
|
605
|
-
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
606
|
-
|
|
649
|
+
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
|
|
650
|
+
finish_base_branch="$BASE_BRANCH"
|
|
651
|
+
else
|
|
652
|
+
finish_base_branch="$(resolve_worktree_base_branch "$wt")"
|
|
653
|
+
fi
|
|
654
|
+
if [[ -n "$finish_base_branch" ]]; then
|
|
655
|
+
finish_args+=(--base "$finish_base_branch")
|
|
607
656
|
fi
|
|
608
657
|
if [[ "$AUTO_CLEANUP" -eq 1 ]]; then
|
|
609
658
|
finish_args+=(--cleanup)
|
|
@@ -613,9 +662,11 @@ run_finish_flow() {
|
|
|
613
662
|
fi
|
|
614
663
|
|
|
615
664
|
if has_origin_remote; then
|
|
616
|
-
if command -v gh >/dev/null 2>&1
|
|
617
|
-
|
|
665
|
+
if ! command -v "${MUSAFETY_GH_BIN:-gh}" >/dev/null 2>&1 && ! command -v gh >/dev/null 2>&1; then
|
|
666
|
+
echo "[codex-agent] Auto-finish requires GitHub CLI for PR flow; command not found: ${MUSAFETY_GH_BIN:-gh}" >&2
|
|
667
|
+
return 2
|
|
618
668
|
fi
|
|
669
|
+
finish_args+=(--via-pr)
|
|
619
670
|
else
|
|
620
671
|
echo "[codex-agent] No origin remote detected; skipping auto-finish merge/PR pipeline." >&2
|
|
621
672
|
return 2
|
|
@@ -631,7 +682,7 @@ run_finish_flow() {
|
|
|
631
682
|
if [[ "$AUTO_REVIEW_ON_CONFLICT" -eq 1 ]] && looks_like_conflict_failure "$finish_output"; then
|
|
632
683
|
echo "[codex-agent] Auto-finish hit conflicts. Launching Codex conflict-review pass in sandbox..." >&2
|
|
633
684
|
local review_prompt
|
|
634
|
-
review_prompt="Resolve git conflicts for branch ${branch} against ${
|
|
685
|
+
review_prompt="Resolve git conflicts for branch ${branch} against ${finish_base_branch:-dev}, then commit the resolution in this sandbox worktree and exit."
|
|
635
686
|
|
|
636
687
|
(
|
|
637
688
|
cd "$wt"
|
|
@@ -665,6 +716,10 @@ if [[ -z "$worktree_branch" || "$worktree_branch" == "HEAD" ]]; then
|
|
|
665
716
|
exit 1
|
|
666
717
|
fi
|
|
667
718
|
|
|
719
|
+
if ! ensure_openspec_change_workspace "$worktree_path" "$worktree_branch"; then
|
|
720
|
+
exit 1
|
|
721
|
+
fi
|
|
722
|
+
|
|
668
723
|
if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
|
|
669
724
|
exit 1
|
|
670
725
|
fi
|
|
@@ -735,7 +790,7 @@ else
|
|
|
735
790
|
if [[ "$auto_finish_completed" -eq 1 ]]; then
|
|
736
791
|
echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
737
792
|
else
|
|
738
|
-
echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --via-pr"
|
|
793
|
+
echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
|
|
739
794
|
echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
|
|
740
795
|
fi
|
|
741
796
|
fi
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
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"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
CHANGE_SLUG="$1"
|
|
11
|
+
CAPABILITY_SLUG="${2:-$CHANGE_SLUG}"
|
|
12
|
+
|
|
13
|
+
if [[ "$CHANGE_SLUG" =~ [^a-z0-9-] ]]; then
|
|
14
|
+
echo "Error: change slug must be kebab-case (lowercase letters, numbers, hyphens)."
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [[ "$CAPABILITY_SLUG" =~ [^a-z0-9-] ]]; then
|
|
19
|
+
echo "Error: capability slug must be kebab-case (lowercase letters, numbers, hyphens)."
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CHANGE_DIR="openspec/changes/${CHANGE_SLUG}"
|
|
24
|
+
SPEC_DIR="${CHANGE_DIR}/specs/${CAPABILITY_SLUG}"
|
|
25
|
+
TODAY="$(date -u +%Y-%m-%d)"
|
|
26
|
+
|
|
27
|
+
mkdir -p "$SPEC_DIR"
|
|
28
|
+
|
|
29
|
+
if [[ ! -f "${CHANGE_DIR}/.openspec.yaml" ]]; then
|
|
30
|
+
cat > "${CHANGE_DIR}/.openspec.yaml" <<YAMLEOF
|
|
31
|
+
schema: spec-driven
|
|
32
|
+
created: ${TODAY}
|
|
33
|
+
YAMLEOF
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [[ ! -f "${CHANGE_DIR}/proposal.md" ]]; then
|
|
37
|
+
cat > "${CHANGE_DIR}/proposal.md" <<PROPOSALEOF
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
- TODO: describe the user/problem outcome this change addresses.
|
|
41
|
+
|
|
42
|
+
## What Changes
|
|
43
|
+
|
|
44
|
+
- TODO: summarize the intended behavior and scope.
|
|
45
|
+
|
|
46
|
+
## Impact
|
|
47
|
+
|
|
48
|
+
- TODO: call out risks, rollout notes, and affected surfaces.
|
|
49
|
+
PROPOSALEOF
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [[ ! -f "${CHANGE_DIR}/tasks.md" ]]; then
|
|
53
|
+
cat > "${CHANGE_DIR}/tasks.md" <<TASKSEOF
|
|
54
|
+
## 1. Specification
|
|
55
|
+
|
|
56
|
+
- [ ] 1.1 Finalize proposal scope and acceptance criteria for \`${CHANGE_SLUG}\`.
|
|
57
|
+
- [ ] 1.2 Define normative requirements in \`specs/${CAPABILITY_SLUG}/spec.md\`.
|
|
58
|
+
|
|
59
|
+
## 2. Implementation
|
|
60
|
+
|
|
61
|
+
- [ ] 2.1 Implement scoped behavior changes.
|
|
62
|
+
- [ ] 2.2 Add/update focused regression coverage.
|
|
63
|
+
|
|
64
|
+
## 3. Verification
|
|
65
|
+
|
|
66
|
+
- [ ] 3.1 Run targeted project verification commands.
|
|
67
|
+
- [ ] 3.2 Run \`openspec validate ${CHANGE_SLUG} --type change --strict\`.
|
|
68
|
+
- [ ] 3.3 Run \`openspec validate --specs\`.
|
|
69
|
+
TASKSEOF
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [[ ! -f "${SPEC_DIR}/spec.md" ]]; then
|
|
73
|
+
cat > "${SPEC_DIR}/spec.md" <<SPECEOF
|
|
74
|
+
## ADDED Requirements
|
|
75
|
+
|
|
76
|
+
### Requirement: ${CAPABILITY_SLUG} behavior
|
|
77
|
+
The system SHALL enforce ${CAPABILITY_SLUG} behavior as defined by this change.
|
|
78
|
+
|
|
79
|
+
#### Scenario: Baseline acceptance
|
|
80
|
+
- **WHEN** ${CAPABILITY_SLUG} behavior is exercised
|
|
81
|
+
- **THEN** the expected outcome is produced
|
|
82
|
+
- **AND** regressions are covered by tests.
|
|
83
|
+
SPECEOF
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
echo "[guardex] OpenSpec change workspace ready: ${CHANGE_DIR}"
|
|
87
|
+
echo "[guardex] OpenSpec change spec scaffold: ${SPEC_DIR}/spec.md"
|