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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ bash scripts/agent-branch-start.sh "refactor-payment-pipeline" "claude-name"
bash scripts/agent-branch-start.sh [--tier T0|T1|T2|T3] "<task>" "claude-<name>"
```

Creates `agent/claude-<name>/<slug>` under `.omx/agent-worktrees/`, scaffolds the OpenSpec change + plan workspaces (sized by tier), and records the bootstrap manifest. Missing `codex-auth` silently falls back to an empty snapshot slug (expected for Claude sessions).
Creates `agent/claude-<name>/<slug>` under `.omc/agent-worktrees/`, scaffolds the OpenSpec change + plan workspaces (sized by tier), and records the bootstrap manifest. Codex sessions keep using `.omx/agent-worktrees/`. Missing `codex-auth` silently falls back to an empty snapshot slug (expected for Claude sessions).

2. Work inside the sandbox only:

```bash
cd .omx/agent-worktrees/agent__claude-<name>__<slug>
cd .omc/agent-worktrees/agent__claude-<name>__<slug>
python3 scripts/agent-file-locks.py claim --branch "agent/claude-<name>/<slug>" <file...>
# implement + commit inside this worktree
```
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ Running Codex across several existing worktrees (e.g. from VS Code Source Contro
gx finish --all
```

Codex sessions default to `.omx/agent-worktrees/`. Claude Code sessions default to `.omc/agent-worktrees/`, so Claude sandboxes stay under the Claude runtime folder instead of sharing the Codex root.

---

## Visual reference
Expand Down Expand Up @@ -161,7 +163,7 @@ gx setup --target /path/to/repo --parent-workspace-view

### Monorepo support

Setup auto-installs into every nested git repo (e.g. `apps/*/.git`). Submodules and worktrees under `.omx/agent-worktrees/` are skipped.
Setup auto-installs into every nested git repo (e.g. `apps/*/.git`). Submodules and worktrees under `.omx/agent-worktrees/` or `.omc/agent-worktrees/` are skipped.

```sh
gx setup --target /mainfolder
Expand Down Expand Up @@ -460,6 +462,7 @@ scripts/openspec/init-plan-workspace.sh
.claude/commands/gitguardex.md
.github/pull.yml.example
.github/workflows/cr.yml
.omc/agent-worktrees
.omx/state/agent-file-locks.json
```

Expand Down
43 changes: 30 additions & 13 deletions bin/multiagent-safety.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ const AGENTS_MARKER_START = '<!-- multiagent-safety:START -->';
const AGENTS_MARKER_END = '<!-- multiagent-safety:END -->';
const GITIGNORE_MARKER_START = '# multiagent-safety:START';
const GITIGNORE_MARKER_END = '# multiagent-safety:END';
const CODEX_WORKTREE_RELATIVE_DIR = path.join('.omx', 'agent-worktrees');
const CLAUDE_WORKTREE_RELATIVE_DIR = path.join('.omc', 'agent-worktrees');
const AGENT_WORKTREE_RELATIVE_DIRS = [
CODEX_WORKTREE_RELATIVE_DIR,
CLAUDE_WORKTREE_RELATIVE_DIR,
];
const MANAGED_GITIGNORE_PATHS = [
'.omx/',
'.omc/',
Expand All @@ -202,7 +208,9 @@ const OMX_SCAFFOLD_DIRECTORIES = [
'.omx/state',
'.omx/logs',
'.omx/plans',
'.omx/agent-worktrees',
CODEX_WORKTREE_RELATIVE_DIR,
'.omc',
CLAUDE_WORKTREE_RELATIVE_DIR,
];
const OMX_SCAFFOLD_FILES = new Map([
['.omx/notepad.md', '\n\n## WORKING MEMORY\n'],
Expand Down Expand Up @@ -273,6 +281,19 @@ const AGENT_BOT_DESCRIPTIONS = [
['agents', 'Start/stop review + cleanup bots for this repo'],
];

function envFlagIsTruthy(raw) {
const lowered = String(raw || '').trim().toLowerCase();
return lowered === '1' || lowered === 'true' || lowered === 'yes' || lowered === 'on';
}

function isClaudeCodeSession(env = process.env) {
return envFlagIsTruthy(env.CLAUDECODE) || Boolean(env.CLAUDE_CODE_SESSION_ID);
}

function defaultAgentWorktreeRelativeDir(env = process.env) {
return isClaudeCodeSession(env) ? CLAUDE_WORKTREE_RELATIVE_DIR : CODEX_WORKTREE_RELATIVE_DIR;
}

const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in this repo.

1) Install: npm i -g @imdeadpool/guardex && gh --version
Expand Down Expand Up @@ -522,8 +543,6 @@ const NESTED_REPO_DEFAULT_SKIP_DIRS = new Set([
'.venv',
'.pnpm-store',
]);
const NESTED_REPO_WORKTREE_RELATIVE_DIR = path.join('.omx', 'agent-worktrees');

function discoverNestedGitRepos(rootPath, opts = {}) {
const maxDepth = Number.isFinite(opts.maxDepth) ? Math.max(1, opts.maxDepth) : NESTED_REPO_DEFAULT_MAX_DEPTH;
const extraSkip = new Set(Array.isArray(opts.extraSkip) ? opts.extraSkip : []);
Expand All @@ -538,7 +557,7 @@ function discoverNestedGitRepos(rootPath, opts = {}) {
return path.resolve(resolvedRoot, raw);
})();

const workreeSkipAbsolute = path.join(resolvedRoot, NESTED_REPO_WORKTREE_RELATIVE_DIR);
const worktreeSkipAbsolutes = AGENT_WORKTREE_RELATIVE_DIRS.map((relativeDir) => path.join(resolvedRoot, relativeDir));
const found = new Set();
found.add(resolvedRoot);

Expand Down Expand Up @@ -570,7 +589,7 @@ function discoverNestedGitRepos(rootPath, opts = {}) {

if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
if (shouldSkipDir(entry.name)) continue;
if (entryPath === workreeSkipAbsolute) continue;
if (worktreeSkipAbsolutes.includes(entryPath)) continue;
walk(entryPath, depth + 1);
}
}
Expand Down Expand Up @@ -1124,16 +1143,15 @@ function buildParentWorkspaceView(repoRoot) {
const workspaceFileName = `${path.basename(repoRoot)}-branches.code-workspace`;
const workspacePath = path.join(parentDir, workspaceFileName);
const repoRelativePath = normalizeWorkspacePath(path.relative(parentDir, repoRoot) || '.');
const worktreesRelativePath = normalizeWorkspacePath(
path.join(repoRelativePath === '.' ? '' : repoRelativePath, '.omx', 'agent-worktrees'),
);

return {
workspacePath,
payload: {
folders: [
{ path: repoRelativePath },
{ path: worktreesRelativePath },
...AGENT_WORKTREE_RELATIVE_DIRS.map((relativeDir) => ({
path: normalizeWorkspacePath(path.join(repoRelativePath === '.' ? '' : repoRelativePath, relativeDir)),
})),
],
settings: {
'scm.alwaysShowRepositories': true,
Expand Down Expand Up @@ -1341,7 +1359,7 @@ function protectedBaseSandboxBranchPrefix() {
}

function protectedBaseSandboxWorktreePath(repoRoot, branchName) {
return path.join(repoRoot, '.omx', 'agent-worktrees', branchName.replace(/\//g, '__'));
return path.join(repoRoot, defaultAgentWorktreeRelativeDir(), branchName.replace(/\//g, '__'));
}

function gitRefExists(repoRoot, ref) {
Expand Down Expand Up @@ -1876,9 +1894,8 @@ function mergeDoctorSandboxRepairsBackToProtectedBase(options, blocked, metadata
if (allowedPaths.has(filePath)) {
return false;
}
return !(
filePath === NESTED_REPO_WORKTREE_RELATIVE_DIR
|| filePath.startsWith(`${NESTED_REPO_WORKTREE_RELATIVE_DIR}/`)
return !AGENT_WORKTREE_RELATIVE_DIRS.some(
(relativeDir) => filePath === relativeDir || filePath.startsWith(`${relativeDir}/`),
);
});
if (unexpectedPaths.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# T1 Notes

- Route Claude-triggered Guardex worktrees into `.omc/agent-worktrees` by default while keeping Codex worktrees under `.omx/agent-worktrees`.
- Persist the selected worktree root on each branch so `agent-branch-finish` and prune/recovery flows can resolve the correct sandbox root later.
- Expand setup/workspace discovery, docs, and install regressions so nested-repo scans and VS Code parent-workspace views treat both `.omx` and `.omc` agent-worktree roots as managed paths.
7 changes: 6 additions & 1 deletion scripts/agent-branch-finish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,17 @@ else
common_git_dir="$(cd "$repo_root/$common_git_dir_raw" && pwd -P)"
fi
repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
agent_worktree_root="${repo_common_root}/.omx/agent-worktrees"

if [[ -z "$SOURCE_BRANCH" ]]; then
SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
fi

stored_worktree_root_rel="$(git -C "$repo_root" config --get "branch.${SOURCE_BRANCH}.guardexWorktreeRoot" || true)"
if [[ -z "$stored_worktree_root_rel" ]]; then
stored_worktree_root_rel=".omx/agent-worktrees"
fi
agent_worktree_root="${repo_common_root}/${stored_worktree_root_rel}"

if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
echo "[agent-branch-finish] --base requires a non-empty branch name." >&2
exit 1
Expand Down
38 changes: 37 additions & 1 deletion scripts/agent-branch-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ TASK_NAME="task"
AGENT_NAME="agent"
BASE_BRANCH=""
BASE_BRANCH_EXPLICIT=0
WORKTREE_ROOT_REL=".omx/agent-worktrees"
WORKTREE_ROOT_REL=""
WORKTREE_ROOT_EXPLICIT=0
OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-false}"
OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
Expand Down Expand Up @@ -44,6 +45,7 @@ while [[ $# -gt 0 ]]; do
;;
--worktree-root)
WORKTREE_ROOT_REL="${2:-.omx/agent-worktrees}"
WORKTREE_ROOT_EXPLICIT=1
shift 2
;;
--)
Expand Down Expand Up @@ -123,6 +125,36 @@ shorten_slug() {
printf '%s' "$shortened"
}

env_flag_truthy() {
local raw="${1:-}"
local lowered
lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
case "$lowered" in
1|true|yes|on) return 0 ;;
*) return 1 ;;
esac
}

default_worktree_root_rel() {
local raw_agent="$1"
local override="${GUARDEX_AGENT_TYPE:-}"
local lowered_agent lowered_override
lowered_agent="$(printf '%s' "$raw_agent" | tr '[:upper:]' '[:lower:]')"
lowered_override="$(printf '%s' "$override" | tr '[:upper:]' '[:lower:]')"

if [[ -n "${CLAUDE_CODE_SESSION_ID:-}" ]] || env_flag_truthy "${CLAUDECODE:-}"; then
printf '.omc/agent-worktrees'
return 0
fi

if [[ "$lowered_agent" == *claude* ]] || [[ "$lowered_override" == *claude* ]]; then
printf '.omc/agent-worktrees'
return 0
fi

printf '.omx/agent-worktrees'
}

# Collapse arbitrary agent identifiers to a clean role token. Priority:
# GUARDEX_AGENT_TYPE env override, then recognizable claude/codex aliases, then
# a small legacy compatibility set, then the literal requested role after slug
Expand Down Expand Up @@ -415,6 +447,9 @@ fi

task_slug="$(sanitize_slug "$TASK_NAME" "task")"
agent_slug="$(normalize_role "$AGENT_NAME")"
if [[ "$WORKTREE_ROOT_EXPLICIT" -eq 0 ]]; then
WORKTREE_ROOT_REL="$(default_worktree_root_rel "$AGENT_NAME")"
fi
branch_timestamp="$(compose_branch_timestamp)"
branch_descriptor="$(compose_branch_descriptor "$task_slug" "$branch_timestamp")"
branch_name_base="agent/${agent_slug}/${branch_descriptor}"
Expand Down Expand Up @@ -499,6 +534,7 @@ if ! worktree_add_output="$(git -C "$repo_root" worktree add -b "$branch_name" "
exit 1
fi
git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$BASE_BRANCH" >/dev/null 2>&1 || true
git -C "$repo_root" config "branch.${branch_name}.guardexWorktreeRoot" "$WORKTREE_ROOT_REL" >/dev/null 2>&1 || true
# Fresh agent branches should start unpublished; clear any inherited base-branch tracking.
git -C "$worktree_path" branch --unset-upstream "$branch_name" >/dev/null 2>&1 || true

Expand Down
Loading