@imdeadpool/guardex 7.0.19 → 7.0.21
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 +51 -13
- package/bin/multiagent-safety.js +2 -7861
- package/package.json +2 -1
- package/src/cli/args.js +809 -0
- package/src/cli/dispatch.js +86 -0
- package/src/cli/main.js +5564 -0
- package/src/context.js +517 -0
- package/src/core/runtime.js +119 -0
- package/src/finish/index.js +425 -0
- package/src/git/index.js +122 -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 +148 -0
- package/src/toolchain/index.js +223 -0
- package/templates/scripts/agent-branch-start.sh +52 -8
- package/templates/scripts/codex-agent.sh +155 -7
- package/templates/vscode/guardex-active-agents/README.md +16 -11
- package/templates/vscode/guardex-active-agents/extension.js +908 -65
- package/templates/vscode/guardex-active-agents/package.json +63 -1
- package/templates/vscode/guardex-active-agents/session-schema.js +440 -42
|
@@ -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
|
+
};
|
|
@@ -14,6 +14,7 @@ OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
|
14
14
|
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
15
15
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
16
16
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
17
|
+
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T3}"
|
|
17
18
|
PRINT_NAME_ONLY=0
|
|
18
19
|
POSITIONAL_ARGS=()
|
|
19
20
|
|
|
@@ -54,8 +55,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
54
55
|
shift
|
|
55
56
|
;;
|
|
56
57
|
--tier)
|
|
57
|
-
|
|
58
|
-
# through this script. Consume the value so callers can pass it.
|
|
58
|
+
OPENSPEC_TIER_RAW="${2:-$OPENSPEC_TIER_RAW}"
|
|
59
59
|
shift 2
|
|
60
60
|
;;
|
|
61
61
|
--in-place|--allow-in-place)
|
|
@@ -246,11 +246,45 @@ normalize_bool() {
|
|
|
246
246
|
|
|
247
247
|
OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
|
|
248
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
|
+
|
|
249
283
|
resolve_openspec_masterplan_label() {
|
|
250
284
|
local raw="${OPENSPEC_MASTERPLAN_LABEL_RAW:-}"
|
|
251
285
|
local label
|
|
252
286
|
|
|
253
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ -z "$raw" ]]; then
|
|
287
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]] || [[ -z "$raw" ]]; then
|
|
254
288
|
printf ''
|
|
255
289
|
return 0
|
|
256
290
|
fi
|
|
@@ -404,7 +438,7 @@ initialize_openspec_plan_workspace() {
|
|
|
404
438
|
local worktree="$2"
|
|
405
439
|
local plan_slug="$3"
|
|
406
440
|
|
|
407
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
441
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
|
|
408
442
|
return 0
|
|
409
443
|
fi
|
|
410
444
|
|
|
@@ -430,14 +464,15 @@ initialize_openspec_change_workspace() {
|
|
|
430
464
|
local change_slug="$3"
|
|
431
465
|
local capability_slug="$4"
|
|
432
466
|
|
|
433
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
467
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
434
468
|
return 0
|
|
435
469
|
fi
|
|
436
470
|
|
|
437
471
|
local init_output=""
|
|
438
472
|
if ! init_output="$(
|
|
439
473
|
cd "$worktree"
|
|
440
|
-
|
|
474
|
+
GUARDEX_OPENSPEC_MINIMAL="$OPENSPEC_MINIMAL" \
|
|
475
|
+
run_guardex_cli internal run-shell changeInit "$change_slug" "$capability_slug" 2>&1
|
|
441
476
|
)"; then
|
|
442
477
|
printf '%s\n' "$init_output" >&2
|
|
443
478
|
echo "[agent-branch-start] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
@@ -599,8 +634,17 @@ fi
|
|
|
599
634
|
|
|
600
635
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
601
636
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
602
|
-
echo "[agent-branch-start] OpenSpec
|
|
603
|
-
|
|
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
|
|
604
648
|
echo "[agent-branch-start] Next steps:"
|
|
605
649
|
echo " cd \"${worktree_path}\""
|
|
606
650
|
echo " gx locks claim --branch \"${branch_name}\" <file...>"
|
|
@@ -17,6 +17,13 @@ OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
|
17
17
|
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
18
18
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
19
19
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
20
|
+
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-}"
|
|
21
|
+
OPENSPEC_TIER=""
|
|
22
|
+
OPENSPEC_SKIP_CHANGE=0
|
|
23
|
+
OPENSPEC_SKIP_PLAN=0
|
|
24
|
+
OPENSPEC_MINIMAL=0
|
|
25
|
+
TASK_MODE=""
|
|
26
|
+
TASK_ROUTING_REASON=""
|
|
20
27
|
|
|
21
28
|
run_guardex_cli() {
|
|
22
29
|
if [[ -n "$CLI_ENTRY" ]]; then
|
|
@@ -48,6 +55,129 @@ normalize_bool() {
|
|
|
48
55
|
esac
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
normalize_tier() {
|
|
59
|
+
local raw="${1:-}"
|
|
60
|
+
local fallback="${2:-T2}"
|
|
61
|
+
local upper
|
|
62
|
+
upper="$(printf '%s' "$raw" | tr '[:lower:]' '[:upper:]')"
|
|
63
|
+
case "$upper" in
|
|
64
|
+
T0|T1|T2|T3) printf '%s' "$upper" ;;
|
|
65
|
+
'') printf '%s' "$fallback" ;;
|
|
66
|
+
*) return 1 ;;
|
|
67
|
+
esac
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
string_contains_any() {
|
|
71
|
+
local haystack="$1"
|
|
72
|
+
shift
|
|
73
|
+
local needle
|
|
74
|
+
for needle in "$@"; do
|
|
75
|
+
if [[ "$haystack" == *"$needle"* ]]; then
|
|
76
|
+
return 0
|
|
77
|
+
fi
|
|
78
|
+
done
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
string_has_lightweight_prefix() {
|
|
83
|
+
local text="$1"
|
|
84
|
+
local prefix
|
|
85
|
+
for prefix in "quick:" "simple:" "tiny:" "minor:" "small:" "just:" "only:"; do
|
|
86
|
+
if [[ "$text" == "$prefix"* ]]; then
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
done
|
|
90
|
+
return 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
task_requires_full_change_workspace() {
|
|
94
|
+
local text="$1"
|
|
95
|
+
string_contains_any "$text" \
|
|
96
|
+
"cleanup evidence" "merged cleanup" "merged state" "pr url" \
|
|
97
|
+
"cleanup pipeline" "finish pipeline" "sandbox cleanup" "tasks.md"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
derive_task_mode_from_tier() {
|
|
101
|
+
case "$1" in
|
|
102
|
+
T0|T1) printf 'caveman' ;;
|
|
103
|
+
T2|T3) printf 'omx' ;;
|
|
104
|
+
*) return 1 ;;
|
|
105
|
+
esac
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
apply_openspec_tier() {
|
|
109
|
+
OPENSPEC_SKIP_CHANGE=0
|
|
110
|
+
OPENSPEC_SKIP_PLAN=0
|
|
111
|
+
OPENSPEC_MINIMAL=0
|
|
112
|
+
case "$1" in
|
|
113
|
+
T0)
|
|
114
|
+
OPENSPEC_SKIP_CHANGE=1
|
|
115
|
+
OPENSPEC_SKIP_PLAN=1
|
|
116
|
+
;;
|
|
117
|
+
T1)
|
|
118
|
+
OPENSPEC_SKIP_PLAN=1
|
|
119
|
+
OPENSPEC_MINIMAL=1
|
|
120
|
+
;;
|
|
121
|
+
T2)
|
|
122
|
+
OPENSPEC_SKIP_PLAN=1
|
|
123
|
+
;;
|
|
124
|
+
esac
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
decide_task_routing() {
|
|
128
|
+
local task_lower
|
|
129
|
+
task_lower="$(printf '%s' "$TASK_NAME" | tr '[:upper:]' '[:lower:]')"
|
|
130
|
+
|
|
131
|
+
if [[ -n "$OPENSPEC_TIER_RAW" ]]; then
|
|
132
|
+
if ! OPENSPEC_TIER="$(normalize_tier "$OPENSPEC_TIER_RAW" "T2")"; then
|
|
133
|
+
echo "[codex-agent] Unsupported OpenSpec tier: ${OPENSPEC_TIER_RAW}" >&2
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
TASK_ROUTING_REASON="explicit tier override"
|
|
137
|
+
elif string_has_lightweight_prefix "$task_lower"; then
|
|
138
|
+
if task_requires_full_change_workspace "$task_lower"; then
|
|
139
|
+
OPENSPEC_TIER="T2"
|
|
140
|
+
TASK_ROUTING_REASON="cleanup-evidence artifact wording overrides lightweight prefix"
|
|
141
|
+
else
|
|
142
|
+
OPENSPEC_TIER="T1"
|
|
143
|
+
TASK_ROUTING_REASON="explicit lightweight prefix"
|
|
144
|
+
fi
|
|
145
|
+
elif string_contains_any "$task_lower" \
|
|
146
|
+
"ralph" "autopilot" "ultrawork" "ultraqa" "ralplan" "deep interview" "ouroboros" \
|
|
147
|
+
"migration" "refactor" "architecture" "re-architect" "cross-cutting" "multi-agent" \
|
|
148
|
+
"multiagent" "parallel" "orchestr" "release" "zero-copy" "install surface" "workflow"
|
|
149
|
+
then
|
|
150
|
+
OPENSPEC_TIER="T3"
|
|
151
|
+
TASK_ROUTING_REASON="plan-heavy or orchestration-heavy task wording"
|
|
152
|
+
elif string_contains_any "$task_lower" \
|
|
153
|
+
"typo" "spelling" "comment-only" "comment only" "format-only" "format only" \
|
|
154
|
+
"whitespace" "one-liner" "one liner" "version bump" "bump version" \
|
|
155
|
+
"single-file" "single file"
|
|
156
|
+
then
|
|
157
|
+
OPENSPEC_TIER="T1"
|
|
158
|
+
TASK_ROUTING_REASON="small bounded maintenance wording"
|
|
159
|
+
else
|
|
160
|
+
OPENSPEC_TIER="T2"
|
|
161
|
+
TASK_ROUTING_REASON="default behavior-change route"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if ! TASK_MODE="$(derive_task_mode_from_tier "$OPENSPEC_TIER")"; then
|
|
165
|
+
echo "[codex-agent] Unsupported task mode tier: ${OPENSPEC_TIER}" >&2
|
|
166
|
+
return 1
|
|
167
|
+
fi
|
|
168
|
+
apply_openspec_tier "$OPENSPEC_TIER"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
describe_task_routing() {
|
|
172
|
+
case "$OPENSPEC_TIER" in
|
|
173
|
+
T0) printf 'caveman / T0 (no OpenSpec scaffold)' ;;
|
|
174
|
+
T1) printf 'caveman / T1 (notes-only OpenSpec)' ;;
|
|
175
|
+
T2) printf 'omx / T2 (change workspace only)' ;;
|
|
176
|
+
T3) printf 'omx / T3 (change plus plan workspace)' ;;
|
|
177
|
+
*) printf 'unknown / %s' "${OPENSPEC_TIER:-unset}" ;;
|
|
178
|
+
esac
|
|
179
|
+
}
|
|
180
|
+
|
|
51
181
|
AUTO_FINISH="$(normalize_bool "$AUTO_FINISH_RAW" "1")"
|
|
52
182
|
AUTO_REVIEW_ON_CONFLICT="$(normalize_bool "$AUTO_REVIEW_ON_CONFLICT_RAW" "1")"
|
|
53
183
|
AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "1")"
|
|
@@ -58,7 +188,7 @@ resolve_openspec_masterplan_label() {
|
|
|
58
188
|
local raw="${OPENSPEC_MASTERPLAN_LABEL_RAW:-}"
|
|
59
189
|
local label
|
|
60
190
|
|
|
61
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ -z "$raw" ]]; then
|
|
191
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]] || [[ -z "$raw" ]]; then
|
|
62
192
|
printf ''
|
|
63
193
|
return 0
|
|
64
194
|
fi
|
|
@@ -86,6 +216,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
86
216
|
BASE_BRANCH_EXPLICIT=1
|
|
87
217
|
shift 2
|
|
88
218
|
;;
|
|
219
|
+
--tier)
|
|
220
|
+
OPENSPEC_TIER_RAW="${2:-$OPENSPEC_TIER_RAW}"
|
|
221
|
+
shift 2
|
|
222
|
+
;;
|
|
89
223
|
--codex-bin)
|
|
90
224
|
CODEX_BIN="${2:-$CODEX_BIN}"
|
|
91
225
|
shift 2
|
|
@@ -151,6 +285,10 @@ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
|
|
|
151
285
|
exit 1
|
|
152
286
|
fi
|
|
153
287
|
|
|
288
|
+
if ! decide_task_routing; then
|
|
289
|
+
exit 1
|
|
290
|
+
fi
|
|
291
|
+
|
|
154
292
|
if ! command -v "$CODEX_BIN" >/dev/null 2>&1; then
|
|
155
293
|
echo "[codex-agent] Missing Codex CLI command: $CODEX_BIN" >&2
|
|
156
294
|
echo "[codex-agent] Install Codex first, then retry." >&2
|
|
@@ -393,7 +531,7 @@ start_sandbox_fallback() {
|
|
|
393
531
|
printf '[agent-branch-start] Worktree: %s\n' "$worktree_path"
|
|
394
532
|
}
|
|
395
533
|
|
|
396
|
-
start_args=("$TASK_NAME" "$AGENT_NAME")
|
|
534
|
+
start_args=(--tier "$OPENSPEC_TIER" "$TASK_NAME" "$AGENT_NAME")
|
|
397
535
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
398
536
|
start_args+=("$BASE_BRANCH")
|
|
399
537
|
fi
|
|
@@ -487,7 +625,10 @@ record_active_session_state() {
|
|
|
487
625
|
--agent "$AGENT_NAME" \
|
|
488
626
|
--worktree "$wt" \
|
|
489
627
|
--pid "$$" \
|
|
490
|
-
--cli "$CODEX_BIN"
|
|
628
|
+
--cli "$CODEX_BIN" \
|
|
629
|
+
--task-mode "$TASK_MODE" \
|
|
630
|
+
--openspec-tier "$OPENSPEC_TIER" \
|
|
631
|
+
--routing-reason "$TASK_ROUTING_REASON"
|
|
491
632
|
}
|
|
492
633
|
|
|
493
634
|
clear_active_session_state() {
|
|
@@ -545,6 +686,7 @@ print_takeover_prompt() {
|
|
|
545
686
|
finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
|
|
546
687
|
|
|
547
688
|
echo "[codex-agent] Takeover sandbox: ${wt}"
|
|
689
|
+
echo "[codex-agent] Takeover routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
|
|
548
690
|
echo "[codex-agent] Takeover prompt: Continue \`${change_slug}\` on branch \`${branch}\`. Work inside \`${wt}\`, review \`${change_artifact}\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`${finish_cmd}\`."
|
|
549
691
|
}
|
|
550
692
|
|
|
@@ -594,7 +736,7 @@ ensure_openspec_plan_workspace() {
|
|
|
594
736
|
local wt="$1"
|
|
595
737
|
local branch="$2"
|
|
596
738
|
|
|
597
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
739
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
|
|
598
740
|
return 0
|
|
599
741
|
fi
|
|
600
742
|
|
|
@@ -619,7 +761,7 @@ ensure_openspec_change_workspace() {
|
|
|
619
761
|
local wt="$1"
|
|
620
762
|
local branch="$2"
|
|
621
763
|
|
|
622
|
-
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
764
|
+
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]] || [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
623
765
|
return 0
|
|
624
766
|
fi
|
|
625
767
|
|
|
@@ -628,7 +770,8 @@ ensure_openspec_change_workspace() {
|
|
|
628
770
|
capability_slug="$(resolve_openspec_capability_slug)"
|
|
629
771
|
if ! init_output="$(
|
|
630
772
|
cd "$wt"
|
|
631
|
-
|
|
773
|
+
GUARDEX_OPENSPEC_MINIMAL="$OPENSPEC_MINIMAL" \
|
|
774
|
+
run_guardex_cli internal run-shell changeInit "$change_slug" "$capability_slug" 2>&1
|
|
632
775
|
)"; then
|
|
633
776
|
printf '%s\n' "$init_output" >&2
|
|
634
777
|
echo "[codex-agent] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
|
|
@@ -878,6 +1021,8 @@ if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
|
|
|
878
1021
|
exit 1
|
|
879
1022
|
fi
|
|
880
1023
|
|
|
1024
|
+
echo "[codex-agent] Task routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
|
|
1025
|
+
|
|
881
1026
|
active_session_recorded=0
|
|
882
1027
|
cleanup_active_session_state_on_exit() {
|
|
883
1028
|
set +e
|
|
@@ -894,7 +1039,10 @@ trap cleanup_active_session_state_on_exit EXIT INT TERM
|
|
|
894
1039
|
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
|
|
895
1040
|
cd "$worktree_path"
|
|
896
1041
|
set +e
|
|
897
|
-
"$
|
|
1042
|
+
GUARDEX_TASK_MODE="$TASK_MODE" \
|
|
1043
|
+
GUARDEX_OPENSPEC_TIER="$OPENSPEC_TIER" \
|
|
1044
|
+
GUARDEX_TASK_ROUTING_REASON="$TASK_ROUTING_REASON" \
|
|
1045
|
+
"$CODEX_BIN" "$@"
|
|
898
1046
|
codex_exit="$?"
|
|
899
1047
|
set -e
|
|
900
1048
|
|
|
@@ -2,21 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Local VS Code companion for Guardex-managed repos.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Renders one repo node per live Guardex workspace with grouped `ACTIVE AGENTS` and `CHANGES` sections.
|
|
9
|
-
- Splits live sessions inside `ACTIVE AGENTS` into `WORKING NOW` and `THINKING` groups so active edit lanes stand out immediately.
|
|
10
|
-
- Shows one row per live Guardex sandbox session inside those activity groups.
|
|
11
|
-
- Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
|
|
12
|
-
- Derives `thinking` versus `working` from the live sandbox worktree, surfaces working counts in the repo/header summary, and shows changed-file counts for active edits.
|
|
13
|
-
- Uses VS Code's native animated `loading~spin` icon for the running-state affordance.
|
|
14
|
-
- Reads repo-local presence files from `.omx/state/active-sessions/`.
|
|
7
|
+
Use the welcome view in Source Control to create or inspect Guardex sandboxes quickly.
|
|
15
8
|
|
|
16
|
-
Install from a Guardex-wired repo:
|
|
9
|
+
1. Install from a Guardex-wired repo:
|
|
17
10
|
|
|
18
11
|
```sh
|
|
19
12
|
node scripts/install-vscode-active-agents-extension.js
|
|
20
13
|
```
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
2. Reload the VS Code window.
|
|
16
|
+
3. In Source Control -> `Active Agents`, use `Start agent` to enter a task + agent name and run `gx branch start`.
|
|
17
|
+
|
|
18
|
+
What it does:
|
|
19
|
+
|
|
20
|
+
- Adds an `Active Agents` view to the Source Control container.
|
|
21
|
+
- Renders one repo node per live Guardex workspace with grouped `ACTIVE AGENTS` and `CHANGES` sections.
|
|
22
|
+
- Splits live sessions inside `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `IDLE`, `STALLED`, and `DEAD` groups so stuck, active, and inactive lanes stand out immediately.
|
|
23
|
+
- Shows one row per live Guardex sandbox session inside those activity groups.
|
|
24
|
+
- Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
|
|
25
|
+
- Derives session state from dirty worktree status, git conflict markers, PID liveness, and recent file mtimes, surfaces working/dead counts in the repo/header summary, and shows changed-file counts for active edits.
|
|
26
|
+
- Uses distinct VS Code codicons for each session state: `warning`, `edit`, `loading~spin`, `clock`, and `error`.
|
|
27
|
+
- Reads repo-local presence files from `.omx/state/active-sessions/` and falls back to managed worktree-root `AGENT.lock` telemetry when the launcher session file is absent.
|