diff --git a/scripts/agent-branch-start.sh b/scripts/agent-branch-start.sh index 61ca938..24331fb 100755 --- a/scripts/agent-branch-start.sh +++ b/scripts/agent-branch-start.sh @@ -46,14 +46,33 @@ done sanitize_slug() { local raw="$1" + local fallback="${2:-task}" local slug slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')" if [[ -z "$slug" ]]; then - slug="task" + slug="$fallback" fi printf '%s' "$slug" } +resolve_active_codex_snapshot_name() { + local override="${MUSAFETY_CODEX_AUTH_SNAPSHOT:-}" + if [[ -n "$override" ]]; then + printf '%s' "$override" + return 0 + fi + + local codex_auth_bin="${MUSAFETY_CODEX_AUTH_BIN:-codex-auth}" + if ! command -v "$codex_auth_bin" >/dev/null 2>&1; then + return 0 + fi + + "$codex_auth_bin" list 2>/dev/null \ + | sed -n 's/^[[:space:]]*\*[[:space:]]\+//p' \ + | head -n 1 \ + | tr -d '\r' || true +} + 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 @@ -72,10 +91,16 @@ else start_ref="${BASE_BRANCH}" fi -task_slug="$(sanitize_slug "$TASK_NAME")" -agent_slug="$(sanitize_slug "$AGENT_NAME")" +task_slug="$(sanitize_slug "$TASK_NAME" "task")" +agent_slug="$(sanitize_slug "$AGENT_NAME" "agent")" +snapshot_name="$(resolve_active_codex_snapshot_name)" +snapshot_slug="$(sanitize_slug "$snapshot_name" "")" timestamp="$(date +%Y%m%d-%H%M%S)" -branch_name="agent/${agent_slug}/${timestamp}-${task_slug}" +if [[ -n "$snapshot_slug" ]]; then + branch_name="agent/${agent_slug}/${timestamp}-${snapshot_slug}-${task_slug}" +else + branch_name="agent/${agent_slug}/${timestamp}-${task_slug}" +fi if git show-ref --verify --quiet "refs/heads/${branch_name}"; then echo "[agent-branch-start] Branch already exists: ${branch_name}" >&2 diff --git a/templates/scripts/agent-branch-start.sh b/templates/scripts/agent-branch-start.sh index 2f5614c..2cb277e 100755 --- a/templates/scripts/agent-branch-start.sh +++ b/templates/scripts/agent-branch-start.sh @@ -75,14 +75,33 @@ fi sanitize_slug() { local raw="$1" + local fallback="${2:-task}" local slug slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')" if [[ -z "$slug" ]]; then - slug="task" + slug="$fallback" fi printf '%s' "$slug" } +resolve_active_codex_snapshot_name() { + local override="${MUSAFETY_CODEX_AUTH_SNAPSHOT:-}" + if [[ -n "$override" ]]; then + printf '%s' "$override" + return 0 + fi + + local codex_auth_bin="${MUSAFETY_CODEX_AUTH_BIN:-codex-auth}" + if ! command -v "$codex_auth_bin" >/dev/null 2>&1; then + return 0 + fi + + "$codex_auth_bin" list 2>/dev/null \ + | sed -n 's/^[[:space:]]*\*[[:space:]]\+//p' \ + | head -n 1 \ + | tr -d '\r' || true +} + 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 @@ -101,10 +120,16 @@ else start_ref="${BASE_BRANCH}" fi -task_slug="$(sanitize_slug "$TASK_NAME")" -agent_slug="$(sanitize_slug "$AGENT_NAME")" +task_slug="$(sanitize_slug "$TASK_NAME" "task")" +agent_slug="$(sanitize_slug "$AGENT_NAME" "agent")" +snapshot_name="$(resolve_active_codex_snapshot_name)" +snapshot_slug="$(sanitize_slug "$snapshot_name" "")" timestamp="$(date +%Y%m%d-%H%M%S)" -branch_name="agent/${agent_slug}/${timestamp}-${task_slug}" +if [[ -n "$snapshot_slug" ]]; then + branch_name="agent/${agent_slug}/${timestamp}-${snapshot_slug}-${task_slug}" +else + branch_name="agent/${agent_slug}/${timestamp}-${task_slug}" +fi if git show-ref --verify --quiet "refs/heads/${branch_name}"; then echo "[agent-branch-start] Branch already exists: ${branch_name}" >&2 diff --git a/test/install.test.js b/test/install.test.js index 6220a90..f9e492d 100644 --- a/test/install.test.js +++ b/test/install.test.js @@ -55,6 +55,14 @@ function createFakeScorecardScript(scriptBody) { return fakePath; } +function createFakeCodexAuthScript(scriptBody) { + const fakeBin = fs.mkdtempSync(path.join(os.tmpdir(), 'musafety-fake-codex-auth-')); + const fakePath = path.join(fakeBin, 'codex-auth'); + fs.writeFileSync(fakePath, `#!/usr/bin/env bash\nset -e\n${scriptBody}\n`, 'utf8'); + fs.chmodSync(fakePath, 0o755); + return { fakeBin, fakePath }; +} + function initRepo() { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'musafety-')); const repoDir = path.join(tempDir, 'repo'); @@ -241,6 +249,50 @@ test('setup agent-branch-start requires --allow-in-place when using --in-place', assert.match(result.stderr, /--in-place --allow-in-place/); }); +test('setup agent-branch-start includes active codex snapshot slug in branch name', () => { + const repoDir = initRepo(); + + let result = runNode(['setup', '--target', repoDir], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + seedCommit(repoDir); + + const { fakeBin } = createFakeCodexAuthScript(` +if [[ "$1" != "list" ]]; then + exit 1 +fi +cat <<'OUT' + default +* Zeus Edix Hu +OUT +`); + + result = runCmd( + 'bash', + ['scripts/agent-branch-start.sh', 'restore-snapshot', 'planner', 'dev'], + repoDir, + { env: { PATH: `${fakeBin}:${process.env.PATH || ''}` } }, + ); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /Created branch: agent\/planner\/\d{8}-\d{6}-zeus-edix-hu-restore-snapshot/); +}); + +test('setup agent-branch-start supports explicit snapshot override without codex-auth', () => { + const repoDir = initRepo(); + + let result = runNode(['setup', '--target', repoDir], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + seedCommit(repoDir); + + result = runCmd( + 'bash', + ['scripts/agent-branch-start.sh', 'ship-fix', 'bot', 'dev'], + repoDir, + { env: { MUSAFETY_CODEX_AUTH_SNAPSHOT: 'Prod Snapshot One' } }, + ); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /Created branch: agent\/bot\/\d{8}-\d{6}-prod-snapshot-one-ship-fix/); +}); + test('default invocation runs non-mutating status output', () => { const repoDir = initRepo();