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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 32 additions & 0 deletions scripts/agent-branch-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:"
Expand Down
32 changes: 32 additions & 0 deletions templates/scripts/agent-branch-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:"
Expand Down
28 changes: 28 additions & 0 deletions test/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading