diff --git a/README.md b/README.md index f9cb3b7..e273418 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ gx report scorecard --repo github.com/recodeecom/multiagent-safety - Interactive self-update prompt defaults to **No** (`[y/N]`). - In initialized repos, `setup`/`install`/`fix` block protected-base writes unless explicitly overridden. - On protected `main`, `gx doctor` auto-runs in a sandbox agent branch/worktree. +- `scripts/agent-branch-start.sh` hydrates `scripts/codex-agent.sh` into new sandbox worktrees when missing, so auto-finish launcher flow stays available. ## Configure protected branches diff --git a/scripts/agent-branch-start.sh b/scripts/agent-branch-start.sh index 56e80fd..40367b2 100755 --- a/scripts/agent-branch-start.sh +++ b/scripts/agent-branch-start.sh @@ -152,6 +152,36 @@ is_protected_branch_name() { return 1 } +hydrate_local_helper_in_worktree() { + local repo="$1" + local worktree="$2" + local relative_path="$3" + local worktree_target="${worktree}/${relative_path}" + local source_path="" + + if [[ -e "$worktree_target" ]]; then + return 0 + fi + + if [[ -f "${repo}/${relative_path}" ]]; then + source_path="${repo}/${relative_path}" + elif [[ -f "${repo}/templates/${relative_path}" ]]; then + source_path="${repo}/templates/${relative_path}" + fi + + if [[ -z "$source_path" ]]; then + return 0 + fi + + mkdir -p "$(dirname "$worktree_target")" + cp "$source_path" "$worktree_target" + if [[ -x "$source_path" ]]; then + chmod +x "$worktree_target" + fi + + echo "[agent-branch-start] Hydrated local helper in worktree: ${relative_path}" +} + 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 @@ -282,6 +312,8 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then fi fi +hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-agent.sh" + echo "[agent-branch-start] Created branch: ${branch_name}" echo "[agent-branch-start] Worktree: ${worktree_path}" echo "[agent-branch-start] Next steps:" diff --git a/templates/scripts/agent-branch-start.sh b/templates/scripts/agent-branch-start.sh index 56e80fd..40367b2 100755 --- a/templates/scripts/agent-branch-start.sh +++ b/templates/scripts/agent-branch-start.sh @@ -152,6 +152,36 @@ is_protected_branch_name() { return 1 } +hydrate_local_helper_in_worktree() { + local repo="$1" + local worktree="$2" + local relative_path="$3" + local worktree_target="${worktree}/${relative_path}" + local source_path="" + + if [[ -e "$worktree_target" ]]; then + return 0 + fi + + if [[ -f "${repo}/${relative_path}" ]]; then + source_path="${repo}/${relative_path}" + elif [[ -f "${repo}/templates/${relative_path}" ]]; then + source_path="${repo}/templates/${relative_path}" + fi + + if [[ -z "$source_path" ]]; then + return 0 + fi + + mkdir -p "$(dirname "$worktree_target")" + cp "$source_path" "$worktree_target" + if [[ -x "$source_path" ]]; then + chmod +x "$worktree_target" + fi + + echo "[agent-branch-start] Hydrated local helper in worktree: ${relative_path}" +} + 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 @@ -282,6 +312,8 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then fi fi +hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-agent.sh" + echo "[agent-branch-start] Created branch: ${branch_name}" echo "[agent-branch-start] Worktree: ${worktree_path}" echo "[agent-branch-start] Next steps:" diff --git a/test/install.test.js b/test/install.test.js index 32f83b8..7ae8a1a 100644 --- a/test/install.test.js +++ b/test/install.test.js @@ -671,6 +671,34 @@ test('agent-branch-start moves protected-branch local changes into the new agent assert.doesNotMatch(stashList.stdout, /musafety-auto-transfer-/); }); +test('agent-branch-start hydrates codex-agent helper into new worktrees when missing locally', () => { + const repoDir = initRepo(); + seedCommit(repoDir); + + let result = runNode(['setup', '--target', repoDir, '--no-global-install'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + + result = runCmd('git', ['add', '.'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + result = runCmd('git', ['commit', '-m', 'apply gx setup'], repoDir, { + ALLOW_COMMIT_ON_PROTECTED_BRANCH: '1', + }); + assert.equal(result.status, 0, result.stderr || result.stdout); + + const localCodexAgent = path.join(repoDir, 'scripts', 'codex-agent.sh'); + assert.equal(fs.existsSync(localCodexAgent), true, 'setup should provision local codex-agent helper'); + + result = runCmd('bash', ['scripts/agent-branch-start.sh', 'hydrate-codex', 'bot'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /Hydrated local helper in worktree: scripts\/codex-agent\.sh/); + + const createdWorktree = extractCreatedWorktree(result.stdout); + const worktreeCodexAgent = path.join(createdWorktree, 'scripts', 'codex-agent.sh'); + assert.equal(fs.existsSync(worktreeCodexAgent), true, 'worktree should receive codex-agent helper'); + const mode = fs.statSync(worktreeCodexAgent).mode; + assert.equal((mode & 0o111) !== 0, true, 'hydrated codex-agent helper should be executable'); +}); + test('agent-branch-finish infers base from source branch metadata and updates main worktree', () => { const repoDir = initRepoOnBranch('main'); seedCommit(repoDir);