@imdeadpool/guardex 7.0.18 → 7.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -19
- package/bin/multiagent-safety.js +2 -7784
- package/package.json +2 -1
- package/src/cli/args.js +7 -0
- package/src/cli/dispatch.js +86 -0
- package/src/cli/main.js +7719 -0
- package/src/context.js +503 -0
- package/src/core/runtime.js +119 -0
- package/src/finish/index.js +425 -0
- package/src/git/index.js +112 -0
- package/src/hooks/index.js +74 -0
- package/src/output/index.js +398 -0
- package/src/sandbox/index.js +68 -0
- package/src/scaffold/index.js +169 -0
- package/src/toolchain/index.js +223 -0
- package/templates/AGENTS.multiagent-safety.md +3 -0
- package/templates/codex/skills/gitguardex/SKILL.md +1 -1
- package/templates/codex/skills/guardex-merge-skills-to-dev/SKILL.md +3 -3
- package/templates/githooks/pre-commit +21 -2
- package/templates/scripts/agent-branch-finish.sh +32 -19
- package/templates/scripts/agent-branch-merge.sh +24 -5
- package/templates/scripts/agent-branch-start.sh +74 -36
- package/templates/scripts/agent-file-locks.py +11 -11
- package/templates/scripts/codex-agent.sh +179 -61
- package/templates/scripts/review-bot-watch.sh +30 -7
- package/templates/vscode/guardex-active-agents/README.md +16 -10
- package/templates/vscode/guardex-active-agents/extension.js +901 -49
- package/templates/vscode/guardex-active-agents/package.json +61 -1
- package/templates/vscode/guardex-active-agents/session-schema.js +211 -17
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
function createToolchainApi(deps) {
|
|
2
|
+
const {
|
|
3
|
+
TOOL_NAME,
|
|
4
|
+
NPM_BIN,
|
|
5
|
+
NPX_BIN,
|
|
6
|
+
packageJson,
|
|
7
|
+
OPENSPEC_PACKAGE,
|
|
8
|
+
OPENSPEC_BIN,
|
|
9
|
+
GLOBAL_TOOLCHAIN_PACKAGES,
|
|
10
|
+
parseAutoApproval,
|
|
11
|
+
isInteractiveTerminal,
|
|
12
|
+
promptYesNoStrict,
|
|
13
|
+
run,
|
|
14
|
+
checkForGuardexUpdate,
|
|
15
|
+
printUpdateAvailableBanner,
|
|
16
|
+
readInstalledGuardexVersion,
|
|
17
|
+
restartIntoUpdatedGuardex,
|
|
18
|
+
checkForOpenSpecPackageUpdate,
|
|
19
|
+
printOpenSpecUpdateAvailableBanner,
|
|
20
|
+
resolveGlobalInstallApproval,
|
|
21
|
+
detectGlobalToolchainPackages,
|
|
22
|
+
detectOptionalLocalCompanionTools,
|
|
23
|
+
formatGlobalToolchainServiceName,
|
|
24
|
+
askGlobalInstallForMissing,
|
|
25
|
+
} = deps;
|
|
26
|
+
|
|
27
|
+
function maybeSelfUpdateBeforeStatus() {
|
|
28
|
+
const check = checkForGuardexUpdate();
|
|
29
|
+
if (!check.checked || !check.updateAvailable) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
printUpdateAvailableBanner(check.current, check.latest);
|
|
34
|
+
|
|
35
|
+
const autoApproval = parseAutoApproval('GUARDEX_AUTO_UPDATE_APPROVAL');
|
|
36
|
+
const interactive = isInteractiveTerminal();
|
|
37
|
+
|
|
38
|
+
if (!interactive && autoApproval == null) {
|
|
39
|
+
console.log(`[${TOOL_NAME}] Non-interactive shell; skipping auto-update prompt.`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const shouldUpdate = interactive
|
|
44
|
+
? promptYesNoStrict(
|
|
45
|
+
`Update now? (${NPM_BIN} i -g ${packageJson.name}@latest)`,
|
|
46
|
+
)
|
|
47
|
+
: autoApproval;
|
|
48
|
+
|
|
49
|
+
if (!shouldUpdate) {
|
|
50
|
+
console.log(`[${TOOL_NAME}] Skipped update.`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const installResult = run(NPM_BIN, ['i', '-g', `${packageJson.name}@latest`], { stdio: 'inherit' });
|
|
55
|
+
if (installResult.status !== 0) {
|
|
56
|
+
console.log(`[${TOOL_NAME}] ⚠️ Update failed. You can retry manually.`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const postInstallVersion = readInstalledGuardexVersion();
|
|
61
|
+
if (postInstallVersion != null && postInstallVersion !== check.latest) {
|
|
62
|
+
console.log(
|
|
63
|
+
`[${TOOL_NAME}] Installed version is still ${postInstallVersion} (expected ${check.latest}). ` +
|
|
64
|
+
`Retrying with pinned version ${check.latest}…`,
|
|
65
|
+
);
|
|
66
|
+
const pinnedResult = run(
|
|
67
|
+
NPM_BIN,
|
|
68
|
+
['i', '-g', `${packageJson.name}@${check.latest}`],
|
|
69
|
+
{ stdio: 'inherit' },
|
|
70
|
+
);
|
|
71
|
+
if (pinnedResult.status !== 0) {
|
|
72
|
+
console.log(
|
|
73
|
+
`[${TOOL_NAME}] ⚠️ Pinned retry failed. Run manually: ${NPM_BIN} i -g ${packageJson.name}@${check.latest}`,
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const pinnedVersion = readInstalledGuardexVersion();
|
|
78
|
+
if (pinnedVersion != null && pinnedVersion !== check.latest) {
|
|
79
|
+
console.log(
|
|
80
|
+
`[${TOOL_NAME}] ⚠️ On-disk version still ${pinnedVersion} after pinned retry. ` +
|
|
81
|
+
`Investigate: ${NPM_BIN} root -g && ${NPM_BIN} cache verify`,
|
|
82
|
+
);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(`[${TOOL_NAME}] ✅ Updated to latest published version.`);
|
|
88
|
+
restartIntoUpdatedGuardex(check.latest);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function maybeOpenSpecUpdateBeforeStatus() {
|
|
92
|
+
const check = checkForOpenSpecPackageUpdate();
|
|
93
|
+
if (!check.checked || !check.updateAvailable) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
printOpenSpecUpdateAvailableBanner(check.current, check.latest);
|
|
98
|
+
|
|
99
|
+
const autoApproval = parseAutoApproval('GUARDEX_AUTO_OPENSPEC_UPDATE_APPROVAL');
|
|
100
|
+
const interactive = isInteractiveTerminal();
|
|
101
|
+
|
|
102
|
+
if (!interactive && autoApproval == null) {
|
|
103
|
+
console.log(`[${TOOL_NAME}] Non-interactive shell; skipping OpenSpec update prompt.`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const shouldUpdate = interactive
|
|
108
|
+
? promptYesNoStrict(
|
|
109
|
+
`Update OpenSpec now? (${NPM_BIN} i -g ${OPENSPEC_PACKAGE}@latest && ${OPENSPEC_BIN} update)`,
|
|
110
|
+
)
|
|
111
|
+
: autoApproval;
|
|
112
|
+
|
|
113
|
+
if (!shouldUpdate) {
|
|
114
|
+
console.log(`[${TOOL_NAME}] Skipped OpenSpec update.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const installResult = run(NPM_BIN, ['i', '-g', `${OPENSPEC_PACKAGE}@latest`], { stdio: 'inherit' });
|
|
119
|
+
if (installResult.status !== 0) {
|
|
120
|
+
console.log(`[${TOOL_NAME}] ⚠️ OpenSpec npm install failed. You can retry manually.`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const toolUpdateResult = run(OPENSPEC_BIN, ['update'], { stdio: 'inherit' });
|
|
125
|
+
if (toolUpdateResult.status !== 0) {
|
|
126
|
+
console.log(`[${TOOL_NAME}] ⚠️ OpenSpec tool update failed. Run '${OPENSPEC_BIN} update' manually.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(`[${TOOL_NAME}] ✅ OpenSpec updated to latest package and tool plugins refreshed.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function installGlobalToolchain(options) {
|
|
134
|
+
const approval = resolveGlobalInstallApproval(options);
|
|
135
|
+
if (approval.source === 'flag' && !approval.approved) {
|
|
136
|
+
return {
|
|
137
|
+
status: 'skipped',
|
|
138
|
+
reason: approval.source,
|
|
139
|
+
missingPackages: [],
|
|
140
|
+
missingLocalTools: [],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (options.dryRun) {
|
|
145
|
+
return { status: 'dry-run-skip' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const detection = detectGlobalToolchainPackages();
|
|
149
|
+
const localCompanionTools = detectOptionalLocalCompanionTools();
|
|
150
|
+
if (!detection.ok) {
|
|
151
|
+
console.log(`[${TOOL_NAME}] ⚠️ Could not detect global packages: ${detection.error}`);
|
|
152
|
+
} else {
|
|
153
|
+
if (detection.installed.length > 0) {
|
|
154
|
+
console.log(
|
|
155
|
+
`[${TOOL_NAME}] Already installed globally: ` +
|
|
156
|
+
`${detection.installed.map((pkg) => formatGlobalToolchainServiceName(pkg)).join(', ')}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const installedLocalTools = localCompanionTools
|
|
160
|
+
.filter((tool) => tool.status === 'active')
|
|
161
|
+
.map((tool) => tool.name);
|
|
162
|
+
if (installedLocalTools.length > 0) {
|
|
163
|
+
console.log(`[${TOOL_NAME}] Already installed locally: ${installedLocalTools.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
if (detection.missing.length === 0 && localCompanionTools.every((tool) => tool.status === 'active')) {
|
|
166
|
+
return { status: 'already-installed' };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const missingPackages = detection.ok ? detection.missing : [...GLOBAL_TOOLCHAIN_PACKAGES];
|
|
171
|
+
const missingLocalTools = localCompanionTools.filter((tool) => tool.status !== 'active');
|
|
172
|
+
const installApproval = askGlobalInstallForMissing(options, missingPackages, missingLocalTools);
|
|
173
|
+
if (!installApproval.approved) {
|
|
174
|
+
return {
|
|
175
|
+
status: 'skipped',
|
|
176
|
+
reason: installApproval.source,
|
|
177
|
+
missingPackages,
|
|
178
|
+
missingLocalTools,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const installed = [];
|
|
183
|
+
if (missingPackages.length > 0) {
|
|
184
|
+
console.log(
|
|
185
|
+
`[${TOOL_NAME}] Installing global toolchain: npm i -g ${missingPackages.join(' ')}`,
|
|
186
|
+
);
|
|
187
|
+
const result = run(NPM_BIN, ['i', '-g', ...missingPackages], { stdio: 'inherit' });
|
|
188
|
+
if (result.status !== 0) {
|
|
189
|
+
const stderr = (result.stderr || '').trim();
|
|
190
|
+
return {
|
|
191
|
+
status: 'failed',
|
|
192
|
+
reason: stderr || 'npm global install failed',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
installed.push(...missingPackages);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const tool of missingLocalTools) {
|
|
199
|
+
console.log(`[${TOOL_NAME}] Installing local companion tool: ${tool.installCommand}`);
|
|
200
|
+
const result = run(NPX_BIN, tool.installArgs, { stdio: 'inherit' });
|
|
201
|
+
if (result.status !== 0) {
|
|
202
|
+
const stderr = (result.stderr || '').trim();
|
|
203
|
+
return {
|
|
204
|
+
status: 'failed',
|
|
205
|
+
reason: stderr || `${tool.name} install failed`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
installed.push(tool.name);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { status: 'installed', packages: installed };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
maybeSelfUpdateBeforeStatus,
|
|
216
|
+
maybeOpenSpecUpdateBeforeStatus,
|
|
217
|
+
installGlobalToolchain,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
createToolchainApi,
|
|
223
|
+
};
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
`GUARDEX_ON=0` disables Guardex for that repo.
|
|
8
8
|
`GUARDEX_ON=1` explicitly enables Guardex for that repo again.
|
|
9
9
|
|
|
10
|
+
**Task-size routing.** Small tasks stay in direct caveman-only mode. For typos, single-file tweaks, one-liners, version bumps, or similarly bounded asks, solve directly and do not escalate into heavy OMX orchestration just because a keyword appears. Treat `quick:`, `simple:`, `tiny:`, `minor:`, `small:`, `just:`, and `only:` as explicit lightweight escape hatches.
|
|
11
|
+
Promote to OMX orchestration only when the task is medium/large: multi-file behavior changes, API/schema work, refactors, migrations, architecture, cross-cutting scope, or long prompts. Heavy OMX modes (`ralph`, `autopilot`, `team`, `ultrawork`, `swarm`, `ralplan`) are for that larger scope. If the task grows while working, upgrade then.
|
|
12
|
+
|
|
10
13
|
**Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `gx branch start "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. Never `git checkout <branch>` on a primary working tree (including nested repos); use `git worktree add` instead. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions - bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
|
|
11
14
|
For every new task, including follow-up work in the same chat/session, if an assigned agent sub-branch/worktree is already open, continue in that sub-branch instead of creating a fresh lane unless the user explicitly redirects scope.
|
|
12
15
|
Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
@@ -8,4 +8,4 @@ Use when repo safety may be broken.
|
|
|
8
8
|
`gx status` -> `gx doctor` -> `gx status --strict`
|
|
9
9
|
|
|
10
10
|
Bootstrap: `gx setup`
|
|
11
|
-
Ops: `
|
|
11
|
+
Ops: `gx branch start "<task>" "<agent>"`, `gx locks claim --branch "<agent-branch>" <file...>`, `gx branch finish --branch "<agent-branch>" --base <base> --via-pr --wait-for-merge --cleanup`, `gx finish --all`, `gx cleanup`
|
|
@@ -24,7 +24,7 @@ echo "$BASE_BRANCH"
|
|
|
24
24
|
2. Start a dedicated integration sandbox from base:
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
|
|
27
|
+
gx branch start "merge-skill-files-to-${BASE_BRANCH}" "skill-merge" "$BASE_BRANCH"
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
3. Enter the sandbox worktree printed by the command above.
|
|
@@ -48,11 +48,11 @@ git diff --name-only
|
|
|
48
48
|
```sh
|
|
49
49
|
git add .codex/skills templates/codex/skills
|
|
50
50
|
git commit -m "Merge skill file updates into ${BASE_BRANCH}"
|
|
51
|
-
|
|
51
|
+
gx branch finish --branch "$(git rev-parse --abbrev-ref HEAD)" --base "$BASE_BRANCH" --via-pr --wait-for-merge --cleanup
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
## Notes
|
|
55
55
|
|
|
56
56
|
- If a source branch has non-skill changes, this runbook keeps them out of the merge.
|
|
57
|
-
- If merge conflicts occur, resolve only within the skill files, then rerun `
|
|
57
|
+
- If merge conflicts occur, resolve only within the skill files, then rerun `gx branch finish`.
|
|
58
58
|
- Do not commit directly on `dev`/`main`; always merge through an agent branch/worktree.
|
|
@@ -13,6 +13,8 @@ repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
|
13
13
|
if [[ -z "$repo_root" ]]; then
|
|
14
14
|
exit 0
|
|
15
15
|
fi
|
|
16
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
17
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
16
18
|
guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
|
|
17
19
|
if [[ -f "$guardex_env_helper" ]]; then
|
|
18
20
|
# shellcheck source=/dev/null
|
|
@@ -22,6 +24,23 @@ if declare -F guardex_repo_is_enabled >/dev/null 2>&1 && ! guardex_repo_is_enabl
|
|
|
22
24
|
exit 0
|
|
23
25
|
fi
|
|
24
26
|
|
|
27
|
+
run_guardex_cli() {
|
|
28
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
29
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
30
|
+
return $?
|
|
31
|
+
fi
|
|
32
|
+
if command -v gx >/dev/null 2>&1; then
|
|
33
|
+
gx "$@"
|
|
34
|
+
return $?
|
|
35
|
+
fi
|
|
36
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
37
|
+
gitguardex "$@"
|
|
38
|
+
return $?
|
|
39
|
+
fi
|
|
40
|
+
echo "[agent-branch-guard] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
41
|
+
return 127
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
if [[ "${ALLOW_COMMIT_ON_PROTECTED_BRANCH:-0}" == "1" ]]; then
|
|
26
45
|
exit 0
|
|
27
46
|
fi
|
|
@@ -191,11 +210,11 @@ if [[ "$branch" == agent/* ]]; then
|
|
|
191
210
|
while IFS= read -r staged_file; do
|
|
192
211
|
[[ -z "$staged_file" ]] && continue
|
|
193
212
|
[[ "$staged_file" == ".omx/state/agent-file-locks.json" ]] && continue
|
|
194
|
-
|
|
213
|
+
run_guardex_cli locks claim --branch "$branch" "$staged_file" >/dev/null 2>&1 || true
|
|
195
214
|
done < <(git diff --cached --name-only --diff-filter=ACMRDTUXB)
|
|
196
215
|
fi
|
|
197
216
|
|
|
198
|
-
if !
|
|
217
|
+
if ! run_guardex_cli locks validate --branch "$branch" --staged; then
|
|
199
218
|
cat >&2 <<'MSG'
|
|
200
219
|
[agent-branch-guard] Agent branch commits require file ownership locks.
|
|
201
220
|
Claim files first:
|
|
@@ -9,11 +9,30 @@ DELETE_REMOTE_BRANCH=0
|
|
|
9
9
|
DELETE_REMOTE_BRANCH_EXPLICIT=0
|
|
10
10
|
MERGE_MODE="auto"
|
|
11
11
|
GH_BIN="${GUARDEX_GH_BIN:-gh}"
|
|
12
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
13
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
12
14
|
CLEANUP_AFTER_MERGE_RAW="${GUARDEX_FINISH_CLEANUP:-false}"
|
|
13
15
|
WAIT_FOR_MERGE_RAW="${GUARDEX_FINISH_WAIT_FOR_MERGE:-false}"
|
|
14
16
|
WAIT_TIMEOUT_SECONDS_RAW="${GUARDEX_FINISH_WAIT_TIMEOUT_SECONDS:-1800}"
|
|
15
17
|
WAIT_POLL_SECONDS_RAW="${GUARDEX_FINISH_WAIT_POLL_SECONDS:-10}"
|
|
16
18
|
|
|
19
|
+
run_guardex_cli() {
|
|
20
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
21
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
22
|
+
return $?
|
|
23
|
+
fi
|
|
24
|
+
if command -v gx >/dev/null 2>&1; then
|
|
25
|
+
gx "$@"
|
|
26
|
+
return $?
|
|
27
|
+
fi
|
|
28
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
29
|
+
gitguardex "$@"
|
|
30
|
+
return $?
|
|
31
|
+
fi
|
|
32
|
+
echo "[agent-branch-finish] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
33
|
+
return 127
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
normalize_bool() {
|
|
18
37
|
local raw="${1:-}"
|
|
19
38
|
local fallback="${2:-0}"
|
|
@@ -431,7 +450,7 @@ run_pr_flow() {
|
|
|
431
450
|
if [[ -z "$pr_title" ]]; then
|
|
432
451
|
pr_title="Merge ${SOURCE_BRANCH} into ${BASE_BRANCH}"
|
|
433
452
|
fi
|
|
434
|
-
pr_body="Automated by
|
|
453
|
+
pr_body="Automated by gx branch finish (PR flow)."
|
|
435
454
|
|
|
436
455
|
"$GH_BIN" pr create \
|
|
437
456
|
--base "$BASE_BRANCH" \
|
|
@@ -517,9 +536,7 @@ if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
|
517
536
|
fi
|
|
518
537
|
fi
|
|
519
538
|
|
|
520
|
-
|
|
521
|
-
python3 "${repo_root}/scripts/agent-file-locks.py" release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
522
|
-
fi
|
|
539
|
+
run_guardex_cli locks release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
523
540
|
|
|
524
541
|
base_worktree="$(get_worktree_for_branch "$BASE_BRANCH")"
|
|
525
542
|
if [[ -n "$base_worktree" ]] && is_clean_worktree "$base_worktree" && [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
@@ -555,29 +572,25 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
555
572
|
fi
|
|
556
573
|
fi
|
|
557
574
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
echo "[agent-branch-finish] You can run manual cleanup: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH} --delete-branches" >&2
|
|
566
|
-
fi
|
|
575
|
+
prune_args=(--base "$BASE_BRANCH" --only-dirty-worktrees --delete-branches)
|
|
576
|
+
if [[ "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
577
|
+
prune_args+=(--delete-remote-branches)
|
|
578
|
+
fi
|
|
579
|
+
if ! run_guardex_cli worktree prune "${prune_args[@]}"; then
|
|
580
|
+
echo "[agent-branch-finish] Warning: automatic worktree prune failed." >&2
|
|
581
|
+
echo "[agent-branch-finish] You can run manual cleanup: gx cleanup --base ${BASE_BRANCH}" >&2
|
|
567
582
|
fi
|
|
568
583
|
|
|
569
584
|
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and cleaned source branch/worktree."
|
|
570
585
|
if [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
571
586
|
echo "[agent-branch-finish] Current worktree '${source_worktree}' still exists because it is the active shell cwd." >&2
|
|
572
|
-
echo "[agent-branch-finish] Leave this directory, then run:
|
|
587
|
+
echo "[agent-branch-finish] Leave this directory, then run: gx cleanup --base ${BASE_BRANCH}" >&2
|
|
573
588
|
fi
|
|
574
589
|
else
|
|
575
|
-
if
|
|
576
|
-
|
|
577
|
-
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
578
|
-
fi
|
|
590
|
+
if ! run_guardex_cli worktree prune --base "$BASE_BRANCH"; then
|
|
591
|
+
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
579
592
|
fi
|
|
580
593
|
|
|
581
594
|
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and kept source branch/worktree."
|
|
582
|
-
echo "[agent-branch-finish] Cleanup later with:
|
|
595
|
+
echo "[agent-branch-finish] Cleanup later with: gx cleanup --base ${BASE_BRANCH}"
|
|
583
596
|
fi
|
|
@@ -6,18 +6,37 @@ BASE_BRANCH_EXPLICIT=0
|
|
|
6
6
|
TARGET_BRANCH=""
|
|
7
7
|
TASK_NAME=""
|
|
8
8
|
AGENT_NAME="${GUARDEX_MERGE_AGENT_NAME:-codex}"
|
|
9
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
10
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
9
11
|
declare -a SOURCE_BRANCHES=()
|
|
10
12
|
|
|
11
13
|
usage() {
|
|
12
14
|
cat <<'EOF'
|
|
13
|
-
Usage:
|
|
15
|
+
Usage: gx branch merge --branch <agent/...> [--branch <agent/...> ...] [--into <agent/...>] [--task <task>] [--agent <agent>] [--base <branch>]
|
|
14
16
|
|
|
15
17
|
Examples:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
gx branch merge --branch agent/codex/ui-a --branch agent/codex/ui-b
|
|
19
|
+
gx branch merge --into agent/codex/owner-lane --branch agent/codex/helper-a --branch agent/codex/helper-b
|
|
18
20
|
EOF
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
run_guardex_cli() {
|
|
24
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
25
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
26
|
+
return $?
|
|
27
|
+
fi
|
|
28
|
+
if command -v gx >/dev/null 2>&1; then
|
|
29
|
+
gx "$@"
|
|
30
|
+
return $?
|
|
31
|
+
fi
|
|
32
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
33
|
+
gitguardex "$@"
|
|
34
|
+
return $?
|
|
35
|
+
fi
|
|
36
|
+
echo "[agent-branch-merge] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
37
|
+
return 127
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
sanitize_slug() {
|
|
22
41
|
local raw="$1"
|
|
23
42
|
local fallback="${2:-merge-agent-branches}"
|
|
@@ -262,7 +281,7 @@ if [[ -z "$TARGET_BRANCH" ]]; then
|
|
|
262
281
|
start_output=""
|
|
263
282
|
if ! start_output="$(
|
|
264
283
|
cd "$repo_root"
|
|
265
|
-
|
|
284
|
+
GUARDEX_OPENSPEC_AUTO_INIT=1 run_guardex_cli branch start "$TASK_NAME" "$AGENT_NAME" "$BASE_BRANCH" 2>&1
|
|
266
285
|
)"; then
|
|
267
286
|
printf '%s\n' "$start_output" >&2
|
|
268
287
|
exit 1
|
|
@@ -418,4 +437,4 @@ echo "[agent-branch-merge] Merge sequence complete for '${TARGET_BRANCH}'."
|
|
|
418
437
|
if [[ "$target_created" -eq 1 ]]; then
|
|
419
438
|
echo "[agent-branch-merge] Review and verify in '${target_worktree}', then finish the integration branch when ready."
|
|
420
439
|
fi
|
|
421
|
-
echo "[agent-branch-merge] Next step:
|
|
440
|
+
echo "[agent-branch-merge] Next step: gx branch finish --branch \"${TARGET_BRANCH}\" --base \"${BASE_BRANCH}\" --via-pr --wait-for-merge --cleanup"
|
|
@@ -7,14 +7,34 @@ BASE_BRANCH=""
|
|
|
7
7
|
BASE_BRANCH_EXPLICIT=0
|
|
8
8
|
WORKTREE_ROOT_REL=""
|
|
9
9
|
WORKTREE_ROOT_EXPLICIT=0
|
|
10
|
+
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
11
|
+
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
10
12
|
OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-false}"
|
|
11
13
|
OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
12
14
|
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
13
15
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
14
16
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
17
|
+
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T3}"
|
|
15
18
|
PRINT_NAME_ONLY=0
|
|
16
19
|
POSITIONAL_ARGS=()
|
|
17
20
|
|
|
21
|
+
run_guardex_cli() {
|
|
22
|
+
if [[ -n "$CLI_ENTRY" ]]; then
|
|
23
|
+
"$NODE_BIN" "$CLI_ENTRY" "$@"
|
|
24
|
+
return $?
|
|
25
|
+
fi
|
|
26
|
+
if command -v gx >/dev/null 2>&1; then
|
|
27
|
+
gx "$@"
|
|
28
|
+
return $?
|
|
29
|
+
fi
|
|
30
|
+
if command -v gitguardex >/dev/null 2>&1; then
|
|
31
|
+
gitguardex "$@"
|
|
32
|
+
return $?
|
|
33
|
+
fi
|
|
34
|
+
echo "[agent-branch-start] Guardex CLI entrypoint unavailable; rerun via gx." >&2
|
|
35
|
+
return 127
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
while [[ $# -gt 0 ]]; do
|
|
19
39
|
case "$1" in
|
|
20
40
|
--task)
|
|
@@ -35,8 +55,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
35
55
|
shift
|
|
36
56
|
;;
|
|
37
57
|
--tier)
|
|
38
|
-
|
|
39
|
-
# through this script. Consume the value so callers can pass it.
|
|
58
|
+
OPENSPEC_TIER_RAW="${2:-$OPENSPEC_TIER_RAW}"
|
|
40
59
|
shift 2
|
|
41
60
|
;;
|
|
42
61
|
--in-place|--allow-in-place)
|
|
@@ -227,11 +246,45 @@ normalize_bool() {
|
|
|
227
246
|
|
|
228
247
|
OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
|
|
229
248
|
|
|
249
|
+
normalize_tier() {
|
|
250
|
+
local raw="${1:-}"
|
|
251
|
+
local fallback="${2:-T3}"
|
|
252
|
+
local upper
|
|
253
|
+
upper="$(printf '%s' "$raw" | tr '[:lower:]' '[:upper:]')"
|
|
254
|
+
case "$upper" in
|
|
255
|
+
T0|T1|T2|T3) printf '%s' "$upper" ;;
|
|
256
|
+
'') printf '%s' "$fallback" ;;
|
|
257
|
+
*) return 1 ;;
|
|
258
|
+
esac
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if ! OPENSPEC_TIER="$(normalize_tier "$OPENSPEC_TIER_RAW" "T3")"; then
|
|
262
|
+
echo "[agent-branch-start] Unsupported OpenSpec tier: ${OPENSPEC_TIER_RAW}" >&2
|
|
263
|
+
exit 1
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
OPENSPEC_SKIP_CHANGE=0
|
|
267
|
+
OPENSPEC_SKIP_PLAN=0
|
|
268
|
+
OPENSPEC_MINIMAL=0
|
|
269
|
+
case "$OPENSPEC_TIER" in
|
|
270
|
+
T0)
|
|
271
|
+
OPENSPEC_SKIP_CHANGE=1
|
|
272
|
+
OPENSPEC_SKIP_PLAN=1
|
|
273
|
+
;;
|
|
274
|
+
T1)
|
|
275
|
+
OPENSPEC_SKIP_PLAN=1
|
|
276
|
+
OPENSPEC_MINIMAL=1
|
|
277
|
+
;;
|
|
278
|
+
T2)
|
|
279
|
+
OPENSPEC_SKIP_PLAN=1
|
|
280
|
+
;;
|
|
281
|
+
esac
|
|
282
|
+
|
|
230
283
|
resolve_openspec_masterplan_label() {
|
|
231
284
|
local raw="${OPENSPEC_MASTERPLAN_LABEL_RAW:-}"
|
|
232
285
|
local label
|
|
233
286
|
|
|
234
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ -z "$raw" ]]; then
|
|
287
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]] || [[ -z "$raw" ]]; then
|
|
235
288
|
printf ''
|
|
236
289
|
return 0
|
|
237
290
|
fi
|
|
@@ -385,26 +438,14 @@ initialize_openspec_plan_workspace() {
|
|
|
385
438
|
local worktree="$2"
|
|
386
439
|
local plan_slug="$3"
|
|
387
440
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
441
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
|
|
391
442
|
return 0
|
|
392
443
|
fi
|
|
393
444
|
|
|
394
|
-
local openspec_script="${worktree}/scripts/openspec/init-plan-workspace.sh"
|
|
395
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
396
|
-
echo "[agent-branch-start] OpenSpec init script is missing in sandbox worktree." >&2
|
|
397
|
-
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
|
|
398
|
-
return 1
|
|
399
|
-
fi
|
|
400
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
401
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
402
|
-
fi
|
|
403
|
-
|
|
404
445
|
local init_output=""
|
|
405
446
|
if ! init_output="$(
|
|
406
447
|
cd "$worktree"
|
|
407
|
-
|
|
448
|
+
run_guardex_cli internal run-shell planInit "$plan_slug" 2>&1
|
|
408
449
|
)"; then
|
|
409
450
|
printf '%s\n' "$init_output" >&2
|
|
410
451
|
echo "[agent-branch-start] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
|
|
@@ -423,26 +464,15 @@ initialize_openspec_change_workspace() {
|
|
|
423
464
|
local change_slug="$3"
|
|
424
465
|
local capability_slug="$4"
|
|
425
466
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
467
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
429
468
|
return 0
|
|
430
469
|
fi
|
|
431
470
|
|
|
432
|
-
local openspec_script="${worktree}/scripts/openspec/init-change-workspace.sh"
|
|
433
|
-
if [[ ! -f "$openspec_script" ]]; then
|
|
434
|
-
echo "[agent-branch-start] OpenSpec change init script is missing in sandbox worktree." >&2
|
|
435
|
-
echo "[agent-branch-start] Run 'gx setup --target \"$repo\"' to repair templates, then retry." >&2
|
|
436
|
-
return 1
|
|
437
|
-
fi
|
|
438
|
-
if [[ ! -x "$openspec_script" ]]; then
|
|
439
|
-
chmod +x "$openspec_script" 2>/dev/null || true
|
|
440
|
-
fi
|
|
441
|
-
|
|
442
471
|
local init_output=""
|
|
443
472
|
if ! init_output="$(
|
|
444
473
|
cd "$worktree"
|
|
445
|
-
|
|
474
|
+
GUARDEX_OPENSPEC_MINIMAL="$OPENSPEC_MINIMAL" \
|
|
475
|
+
run_guardex_cli internal run-shell changeInit "$change_slug" "$capability_slug" 2>&1
|
|
446
476
|
)"; then
|
|
447
477
|
printf '%s\n' "$init_output" >&2
|
|
448
478
|
echo "[agent-branch-start] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
@@ -592,7 +622,6 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
|
592
622
|
fi
|
|
593
623
|
fi
|
|
594
624
|
|
|
595
|
-
hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-agent.sh"
|
|
596
625
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "node_modules"
|
|
597
626
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/frontend/node_modules"
|
|
598
627
|
hydrate_dependency_dir_symlink_in_worktree "$repo_root" "$worktree_path" "apps/backend/node_modules"
|
|
@@ -605,10 +634,19 @@ fi
|
|
|
605
634
|
|
|
606
635
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
607
636
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
608
|
-
echo "[agent-branch-start] OpenSpec
|
|
609
|
-
|
|
637
|
+
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
638
|
+
if [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
639
|
+
echo "[agent-branch-start] OpenSpec change: skipped by tier ${OPENSPEC_TIER}"
|
|
640
|
+
else
|
|
641
|
+
echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
|
|
642
|
+
fi
|
|
643
|
+
if [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
|
|
644
|
+
echo "[agent-branch-start] OpenSpec plan: skipped by tier ${OPENSPEC_TIER}"
|
|
645
|
+
else
|
|
646
|
+
echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
|
|
647
|
+
fi
|
|
610
648
|
echo "[agent-branch-start] Next steps:"
|
|
611
649
|
echo " cd \"${worktree_path}\""
|
|
612
|
-
echo "
|
|
650
|
+
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
613
651
|
echo " # implement + commit"
|
|
614
|
-
echo "
|
|
652
|
+
echo " gx branch finish --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge"
|