Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,10 @@ per-branch plan workspace automatically under:
openspec/plan/<agent-branch-slug>/
```

For manual `scripts/agent-branch-start.sh` usage, enable auto-bootstrap with
`MUSAFETY_OPENSPEC_AUTO_INIT=true` or scaffold manually before implementation:
For manual `scripts/agent-branch-start.sh` usage, OpenSpec auto-bootstrap is
enabled by default. Set `MUSAFETY_OPENSPEC_AUTO_INIT=false` only when you
intentionally need to skip scaffold generation, or scaffold manually before
implementation:

```bash
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,12 @@ openspec update

### OpenSpec in agent sub-branches

- `scripts/codex-agent.sh` enforces an OpenSpec workspace before it launches Codex in each sandbox branch/worktree.
- `scripts/agent-branch-start.sh` can also scaffold `openspec/plan/<agent-branch-slug>/` when you set `MUSAFETY_OPENSPEC_AUTO_INIT=true`.
- Set `MUSAFETY_OPENSPEC_AUTO_INIT=false` (default for `agent-branch-start`) to skip branch-start auto-bootstrap.
- `scripts/codex-agent.sh` enforces OpenSpec workspaces before it launches Codex in each sandbox branch/worktree.
- `scripts/agent-branch-start.sh` scaffolds both `openspec/changes/<agent-branch-slug>/` and `openspec/plan/<agent-branch-slug>/` by default.
- Set `MUSAFETY_OPENSPEC_AUTO_INIT=false` only when you intentionally need to skip branch-start auto-bootstrap.
- Set `MUSAFETY_OPENSPEC_PLAN_SLUG=<kebab-case-slug>` to force a specific plan workspace name.
- Set `MUSAFETY_OPENSPEC_CHANGE_SLUG=<kebab-case-slug>` to force a specific change workspace name.
- Set `MUSAFETY_OPENSPEC_CAPABILITY_SLUG=<kebab-case-slug>` to override the default capability folder used for `spec.md` scaffolding.

## Security and maintenance posture

Expand Down
99 changes: 90 additions & 9 deletions scripts/agent-branch-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ AGENT_NAME="agent"
BASE_BRANCH=""
BASE_BRANCH_EXPLICIT=0
WORKTREE_ROOT_REL=".omx/agent-worktrees"
OPENSPEC_AUTO_INIT_RAW="${MUSAFETY_OPENSPEC_AUTO_INIT:-false}"
OPENSPEC_AUTO_INIT_RAW="${MUSAFETY_OPENSPEC_AUTO_INIT:-true}"
OPENSPEC_PLAN_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_PLAN_SLUG:-}"
OPENSPEC_CHANGE_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CHANGE_SLUG:-}"
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CAPABILITY_SLUG:-}"
POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
Expand Down Expand Up @@ -109,6 +111,25 @@ resolve_openspec_plan_slug() {
sanitize_slug "${branch_name//\//-}" "$task_slug"
}

resolve_openspec_change_slug() {
local branch_name="$1"
local task_slug="$2"
if [[ -n "$OPENSPEC_CHANGE_SLUG_OVERRIDE" ]]; then
sanitize_slug "$OPENSPEC_CHANGE_SLUG_OVERRIDE" "$task_slug"
return 0
fi
sanitize_slug "${branch_name//\//-}" "$task_slug"
}

resolve_openspec_capability_slug() {
local task_slug="$1"
if [[ -n "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" ]]; then
sanitize_slug "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" "$task_slug"
return 0
fi
sanitize_slug "$task_slug" "general-behavior"
}

resolve_active_codex_snapshot_name() {
local override="${MUSAFETY_CODEX_AUTH_SNAPSHOT:-}"
if [[ -n "$override" ]]; then
Expand Down Expand Up @@ -193,6 +214,33 @@ hydrate_local_helper_in_worktree() {
echo "[agent-branch-start] Hydrated local helper in worktree: ${relative_path}"
}

resolve_local_helper_script_path() {
local repo="$1"
local worktree="$2"
local relative_path="$3"
local candidate

candidate="${worktree}/${relative_path}"
if [[ -f "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi

candidate="${repo}/${relative_path}"
if [[ -f "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi

candidate="${repo}/templates/${relative_path}"
if [[ -f "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi

return 1
}

hydrate_dependency_dir_symlink_in_worktree() {
local repo="$1"
local worktree="$2"
Expand All @@ -218,26 +266,21 @@ initialize_openspec_plan_workspace() {
local worktree="$2"
local plan_slug="$3"

hydrate_local_helper_in_worktree "$repo" "$worktree" "scripts/openspec/init-plan-workspace.sh"

if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
return 0
fi

local openspec_script="${worktree}/scripts/openspec/init-plan-workspace.sh"
if [[ ! -f "$openspec_script" ]]; then
local openspec_script
if ! openspec_script="$(resolve_local_helper_script_path "$repo" "$worktree" "scripts/openspec/init-plan-workspace.sh")"; then
echo "[agent-branch-start] OpenSpec init script is missing in sandbox worktree." >&2
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
return 1
fi
if [[ ! -x "$openspec_script" ]]; then
chmod +x "$openspec_script" 2>/dev/null || true
fi

local init_output=""
if ! init_output="$(
cd "$worktree"
bash "scripts/openspec/init-plan-workspace.sh" "$plan_slug" 2>&1
bash "$openspec_script" "$plan_slug" 2>&1
)"; then
printf '%s\n' "$init_output" >&2
echo "[agent-branch-start] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
Expand All @@ -250,6 +293,38 @@ initialize_openspec_plan_workspace() {
echo "[agent-branch-start] OpenSpec plan workspace: ${worktree}/openspec/plan/${plan_slug}"
}

initialize_openspec_change_workspace() {
local repo="$1"
local worktree="$2"
local change_slug="$3"
local capability_slug="$4"

if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
return 0
fi

local openspec_script
if ! openspec_script="$(resolve_local_helper_script_path "$repo" "$worktree" "scripts/openspec/init-change-workspace.sh")"; then
echo "[agent-branch-start] OpenSpec change init script is missing in sandbox worktree." >&2
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
return 1
fi

local init_output=""
if ! init_output="$(
cd "$worktree"
bash "$openspec_script" "$change_slug" "$capability_slug" 2>&1
)"; then
printf '%s\n' "$init_output" >&2
echo "[agent-branch-start] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
return 1
fi

if [[ -n "$init_output" ]]; then
printf '%s\n' "$init_output"
fi
echo "[agent-branch-start] OpenSpec change workspace: ${worktree}/openspec/changes/${change_slug}"
}
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "[agent-branch-start] Not inside a git repository." >&2
exit 1
Expand Down Expand Up @@ -312,6 +387,8 @@ worktree_root="${repo_root}/${WORKTREE_ROOT_REL}"
mkdir -p "$worktree_root"
worktree_path="${worktree_root}/${branch_name//\//__}"
openspec_plan_slug="$(resolve_openspec_plan_slug "$branch_name" "$task_slug")"
openspec_change_slug="$(resolve_openspec_change_slug "$branch_name" "$task_slug")"
openspec_capability_slug="$(resolve_openspec_capability_slug "$task_slug")"

if [[ -e "$worktree_path" ]]; then
echo "[agent-branch-start] Worktree path already exists: ${worktree_path}" >&2
Expand Down Expand Up @@ -394,12 +471,16 @@ hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-ag
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
if ! initialize_openspec_change_workspace "$repo_root" "$worktree_path" "$openspec_change_slug" "$openspec_capability_slug"; then
exit 1
fi
if ! initialize_openspec_plan_workspace "$repo_root" "$worktree_path" "$openspec_plan_slug"; then
exit 1
fi

echo "[agent-branch-start] Created branch: ${branch_name}"
echo "[agent-branch-start] Worktree: ${worktree_path}"
echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
echo "[agent-branch-start] Next steps:"
echo " cd \"${worktree_path}\""
Expand Down
95 changes: 87 additions & 8 deletions scripts/codex-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ AUTO_CLEANUP_RAW="${MUSAFETY_CODEX_AUTO_CLEANUP:-true}"
AUTO_WAIT_FOR_MERGE_RAW="${MUSAFETY_CODEX_WAIT_FOR_MERGE:-true}"
OPENSPEC_AUTO_INIT_RAW="${MUSAFETY_OPENSPEC_AUTO_INIT:-true}"
OPENSPEC_PLAN_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_PLAN_SLUG:-}"
OPENSPEC_CHANGE_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CHANGE_SLUG:-}"
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${MUSAFETY_OPENSPEC_CAPABILITY_SLUG:-}"

normalize_bool() {
local raw="${1:-}"
Expand Down Expand Up @@ -150,6 +152,27 @@ resolve_openspec_plan_slug() {
sanitize_slug "${branch_name//\//-}" "$task_slug"
}

resolve_openspec_change_slug() {
local branch_name="$1"
local task_slug
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
if [[ -n "$OPENSPEC_CHANGE_SLUG_OVERRIDE" ]]; then
sanitize_slug "$OPENSPEC_CHANGE_SLUG_OVERRIDE" "$task_slug"
return 0
fi
sanitize_slug "${branch_name//\//-}" "$task_slug"
}

resolve_openspec_capability_slug() {
local task_slug
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
if [[ -n "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" ]]; then
sanitize_slug "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" "$task_slug"
return 0
fi
sanitize_slug "$task_slug" "general-behavior"
}

hydrate_local_helper_in_worktree() {
local worktree="$1"
local relative_path="$2"
Expand Down Expand Up @@ -179,6 +202,32 @@ hydrate_local_helper_in_worktree() {
echo "[codex-agent] Hydrated local helper in sandbox: ${relative_path}"
}

resolve_local_helper_script_path() {
local worktree="$1"
local relative_path="$2"
local candidate=""

candidate="${worktree}/${relative_path}"
if [[ -f "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi

candidate="${repo_root}/${relative_path}"
if [[ -f "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi

candidate="${repo_root}/templates/${relative_path}"
if [[ -f "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi

return 1
}

resolve_start_base_branch() {
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
printf '%s' "$BASE_BRANCH"
Expand Down Expand Up @@ -397,24 +446,19 @@ ensure_openspec_plan_workspace() {
return 0
fi

hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-plan-workspace.sh"

local openspec_script="${wt}/scripts/openspec/init-plan-workspace.sh"
if [[ ! -f "$openspec_script" ]]; then
local openspec_script
if ! openspec_script="$(resolve_local_helper_script_path "$wt" "scripts/openspec/init-plan-workspace.sh")"; then
echo "[codex-agent] Missing OpenSpec init script in sandbox: ${openspec_script}" >&2
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
return 1
fi
if [[ ! -x "$openspec_script" ]]; then
chmod +x "$openspec_script" 2>/dev/null || true
fi

local plan_slug
plan_slug="$(resolve_openspec_plan_slug "$branch")"
local init_output=""
if ! init_output="$(
cd "$wt"
bash "scripts/openspec/init-plan-workspace.sh" "$plan_slug" 2>&1
bash "$openspec_script" "$plan_slug" 2>&1
)"; then
printf '%s\n' "$init_output" >&2
echo "[codex-agent] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
Expand All @@ -426,6 +470,37 @@ ensure_openspec_plan_workspace() {
echo "[codex-agent] OpenSpec plan workspace: ${wt}/openspec/plan/${plan_slug}"
}

ensure_openspec_change_workspace() {
local wt="$1"
local branch="$2"

if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
return 0
fi

local openspec_script
if ! openspec_script="$(resolve_local_helper_script_path "$wt" "scripts/openspec/init-change-workspace.sh")"; then
echo "[codex-agent] Missing OpenSpec change init script in sandbox: ${openspec_script}" >&2
echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
return 1
fi

local change_slug capability_slug init_output=""
change_slug="$(resolve_openspec_change_slug "$branch")"
capability_slug="$(resolve_openspec_capability_slug)"
if ! init_output="$(
cd "$wt"
bash "$openspec_script" "$change_slug" "$capability_slug" 2>&1
)"; then
printf '%s\n' "$init_output" >&2
echo "[codex-agent] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
return 1
fi
if [[ -n "$init_output" ]]; then
printf '%s\n' "$init_output"
fi
echo "[codex-agent] OpenSpec change workspace: ${wt}/openspec/changes/${change_slug}"
}
worktree_has_changes() {
local wt="$1"
if ! git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
Expand Down Expand Up @@ -656,6 +731,10 @@ if [[ -z "$worktree_branch" || "$worktree_branch" == "HEAD" ]]; then
exit 1
fi

if ! ensure_openspec_change_workspace "$worktree_path" "$worktree_branch"; then
exit 1
fi

if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
exit 1
fi
Expand Down
6 changes: 4 additions & 2 deletions templates/AGENTS.multiagent-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ per-branch plan workspace automatically under:
openspec/plan/<agent-branch-slug>/
```

For manual `scripts/agent-branch-start.sh` usage, enable auto-bootstrap with
`MUSAFETY_OPENSPEC_AUTO_INIT=true` or scaffold manually before implementation:
For manual `scripts/agent-branch-start.sh` usage, OpenSpec auto-bootstrap is
enabled by default. Set `MUSAFETY_OPENSPEC_AUTO_INIT=false` only when you
intentionally need to skip scaffold generation, or scaffold manually before
implementation:

```bash
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
Expand Down
Loading