@imdeadpool/guardex 7.0.22 → 7.0.24
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 +11 -0
- package/package.json +5 -1
- package/src/cli/args.js +89 -0
- package/src/cli/main.js +54 -75
- package/src/context.js +16 -2
- package/src/core/stdin.js +52 -0
- package/src/core/versions.js +33 -0
- package/src/hooks/index.js +64 -0
- package/src/output/index.js +64 -4
- package/src/report/session-severity.js +282 -0
- package/src/scaffold/index.js +78 -131
- package/src/toolchain/index.js +6 -70
- package/templates/AGENTS.multiagent-safety.md +25 -0
- package/templates/scripts/agent-branch-finish.sh +79 -1
- package/templates/scripts/agent-branch-start.sh +35 -0
- package/templates/scripts/agent-session-state.js +62 -1
- package/templates/scripts/codex-agent.sh +38 -0
- package/templates/scripts/install-vscode-active-agents-extension.js +38 -11
- package/templates/scripts/openspec/init-plan-workspace.sh +34 -3
- package/templates/vscode/guardex-active-agents/README.md +7 -6
- package/templates/vscode/guardex-active-agents/extension.js +523 -73
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/package.json +13 -3
- package/templates/vscode/guardex-active-agents/session-schema.js +311 -4
|
@@ -10,6 +10,29 @@
|
|
|
10
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
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
12
|
|
|
13
|
+
## Token / Context Budget
|
|
14
|
+
|
|
15
|
+
Default: less word, same proof.
|
|
16
|
+
|
|
17
|
+
- For prompts about `token inefficiency`, `reviewer mode`, `minimal token overhead`, or session waste patterns, switch into low-overhead mode: plan in at most 4 bullets, execute by phase, batch related reads/commands, avoid duplicate reads and interactive loops, keep outputs compact, and verify once per phase.
|
|
18
|
+
- Low output alone is not a defect. A bounded run that finishes in roughly <=10 steps is usually fine; low output spread across 20+ steps with rising per-turn input is fragmentation and should be treated as context growth first.
|
|
19
|
+
- Startup / resume summaries stay tiny: `branch`, `task`, `blocker`, `next step`, and `evidence`.
|
|
20
|
+
- Memory-driven starts stay ordered: read active `.omx/state` first, then one live `.omx/notepad.md` handoff, then external memory only when the task depends on prior repo decisions, a previous lane, or ambiguous continuity. Stop after the first 1-2 relevant hits.
|
|
21
|
+
- Front-load scaffold/path discovery into one grouped inspection pass. Avoid serial `ls` / `find` / `rg` / `cat` retries that only rediscover the same path state.
|
|
22
|
+
- Treat repeated `write_stdin`, repeated `sed` / `cat` peeks, and tiny diagnostic follow-up checks as strong negative signals. If they appear alongside climbing input cost, stop the probe loop and batch the next phase.
|
|
23
|
+
- Tool / hook summaries stay tiny: command, status, last meaningful lines only. Drop routine hook boilerplate.
|
|
24
|
+
- Treat local edit/commit, remote publish/PR, CI diagnosis, and cleanup as bounded phases. Do not spend fresh narration or approval turns on obvious safe follow-ons inside an already authorized phase unless the risk changes.
|
|
25
|
+
- When a session turns fragmented, collapse back to inspect once, patch once, verify once, and summarize once.
|
|
26
|
+
- Keep `.omx/notepad.md` lean: live handoffs only. Use exactly `branch`, `task`, `blocker`, `next step`, and `evidence`; move narrative proof into OpenSpec artifacts, PRs, or command output.
|
|
27
|
+
|
|
28
|
+
## OMX Caveman Style
|
|
29
|
+
|
|
30
|
+
- Commentary and progress updates use smart-caveman `ultra` by default: drop articles, filler, pleasantries, and hedging. Fragments are fine when they stay clear.
|
|
31
|
+
- Answer order stays fixed: answer first, cause next, fix or next step last. If yes/no fits, say yes/no first.
|
|
32
|
+
- Keep literals exact: code, commands, file paths, flags, env vars, URLs, numbers, timestamps, and error text are never caveman-compressed.
|
|
33
|
+
- Auto-clarity wins: switch back to `lite` or normal wording for security warnings, irreversible actions, privacy/compliance notes, ordered instructions where fragments may confuse, or when the user is confused and needs more detail.
|
|
34
|
+
- Boundaries stay normal/exact for code, commits, PR text, specs, logs, and blocker evidence.
|
|
35
|
+
|
|
13
36
|
**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`.
|
|
14
37
|
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.
|
|
15
38
|
Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
@@ -25,6 +48,8 @@ OMX completion policy: when a task is done, the agent must commit the task chang
|
|
|
25
48
|
|
|
26
49
|
**Reporting.** Every completion handoff includes: files changed, behavior touched, verification commands + results, risks/follow-ups.
|
|
27
50
|
|
|
51
|
+
**Open questions.** If Codex/Claude hits an unresolved question, branching decision, or blocker that should survive chat, record it in `openspec/plan/<plan-slug>/open-questions.md` as an unchecked `- [ ]` item. Resolve it in-place when answered instead of burying it in chat-only notes.
|
|
52
|
+
|
|
28
53
|
**OpenSpec (when change-driven).** Keep `openspec/changes/<slug>/tasks.md` checkboxes current during work, not batched at the end. Task scaffolds and manual task edits must include an explicit final completion/cleanup section that ends with PR merge + sandbox cleanup (`gx finish --via-pr --wait-for-merge --cleanup` or `gx branch finish ... --cleanup`) and records PR URL + final `MERGED` evidence. Verify specs with `openspec validate --specs` before archive. Don't archive unverified.
|
|
29
54
|
|
|
30
55
|
**Version bumps.** If a change bumps a published version, the same PR updates release notes/changelog.
|
|
@@ -409,6 +409,33 @@ is_remote_branch_missing_error() {
|
|
|
409
409
|
return 1
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
+
local_branch_exists() {
|
|
413
|
+
local branch="$1"
|
|
414
|
+
git -C "$repo_root" show-ref --verify --quiet "refs/heads/${branch}"
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
delete_local_branch_for_cleanup() {
|
|
418
|
+
local branch="$1"
|
|
419
|
+
local delete_output=""
|
|
420
|
+
|
|
421
|
+
if ! local_branch_exists "$branch"; then
|
|
422
|
+
echo "[agent-branch-finish] Local branch '${branch}' was already deleted; continuing cleanup." >&2
|
|
423
|
+
return 0
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
if delete_output="$(git -C "$repo_root" branch -d "$branch" 2>&1)"; then
|
|
427
|
+
return 0
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
if ! local_branch_exists "$branch"; then
|
|
431
|
+
echo "[agent-branch-finish] Local branch '${branch}' was already deleted; continuing cleanup." >&2
|
|
432
|
+
return 0
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
echo "$delete_output" >&2
|
|
436
|
+
return 1
|
|
437
|
+
}
|
|
438
|
+
|
|
412
439
|
read_pr_state() {
|
|
413
440
|
local state_line
|
|
414
441
|
state_line="$("$GH_BIN" pr view "$SOURCE_BRANCH" --json state,mergedAt,url --jq '[.state, (.mergedAt // ""), (.url // "")] | join("\u001f")' 2>/dev/null || true)"
|
|
@@ -428,6 +455,44 @@ read_pr_state() {
|
|
|
428
455
|
return 0
|
|
429
456
|
}
|
|
430
457
|
|
|
458
|
+
read_merged_pr_for_head() {
|
|
459
|
+
local head_sha="${1:-}"
|
|
460
|
+
local state_line=""
|
|
461
|
+
local parsed_state=""
|
|
462
|
+
local parsed_merged_at=""
|
|
463
|
+
local parsed_url=""
|
|
464
|
+
|
|
465
|
+
if [[ -z "$head_sha" ]]; then
|
|
466
|
+
return 1
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
state_line="$("$GH_BIN" pr list \
|
|
470
|
+
--state merged \
|
|
471
|
+
--head "$SOURCE_BRANCH" \
|
|
472
|
+
--base "$BASE_BRANCH" \
|
|
473
|
+
--json state,mergedAt,url,headRefOid \
|
|
474
|
+
--jq "map(select(.headRefOid == \"$head_sha\")) | sort_by(.mergedAt // \"\") | reverse | (.[0] // {}) | [(.state // \"\"), (.mergedAt // \"\"), (.url // \"\")] | join(\"\u001f\")" \
|
|
475
|
+
2>/dev/null || true)"
|
|
476
|
+
if [[ -z "$state_line" ]]; then
|
|
477
|
+
return 1
|
|
478
|
+
fi
|
|
479
|
+
|
|
480
|
+
IFS=$'\x1f' read -r parsed_state parsed_merged_at parsed_url <<< "$state_line"
|
|
481
|
+
if [[ -z "$parsed_state" && -z "$parsed_merged_at" && -z "$parsed_url" ]]; then
|
|
482
|
+
return 1
|
|
483
|
+
fi
|
|
484
|
+
if [[ "$parsed_state" != "MERGED" && -z "$parsed_merged_at" ]]; then
|
|
485
|
+
return 1
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
PR_STATE="$parsed_state"
|
|
489
|
+
PR_MERGED_AT="$parsed_merged_at"
|
|
490
|
+
if [[ -n "$parsed_url" ]]; then
|
|
491
|
+
pr_url="$parsed_url"
|
|
492
|
+
fi
|
|
493
|
+
return 0
|
|
494
|
+
}
|
|
495
|
+
|
|
431
496
|
wait_for_pr_merge() {
|
|
432
497
|
local deadline
|
|
433
498
|
deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
|
|
@@ -482,11 +547,22 @@ wait_for_pr_merge() {
|
|
|
482
547
|
}
|
|
483
548
|
|
|
484
549
|
run_pr_flow() {
|
|
550
|
+
local source_head_sha=""
|
|
551
|
+
|
|
485
552
|
if ! command -v "$GH_BIN" >/dev/null 2>&1; then
|
|
486
553
|
echo "[agent-branch-finish] PR fallback requested but GitHub CLI not found: ${GH_BIN}" >&2
|
|
487
554
|
return 1
|
|
488
555
|
fi
|
|
489
556
|
|
|
557
|
+
source_head_sha="$(git -C "$repo_root" rev-parse "$SOURCE_BRANCH" 2>/dev/null || true)"
|
|
558
|
+
if read_merged_pr_for_head "$source_head_sha"; then
|
|
559
|
+
echo "[agent-branch-finish] Source branch head already landed in a merged PR; skipping new PR creation and continuing cleanup." >&2
|
|
560
|
+
if [[ -n "$pr_url" ]]; then
|
|
561
|
+
echo "[agent-branch-finish] Merged PR: ${pr_url}" >&2
|
|
562
|
+
fi
|
|
563
|
+
return 0
|
|
564
|
+
fi
|
|
565
|
+
|
|
490
566
|
git -C "$source_worktree" push -u origin "$SOURCE_BRANCH"
|
|
491
567
|
|
|
492
568
|
pr_title="$(git -C "$repo_root" log -1 --pretty=%s "$SOURCE_BRANCH" 2>/dev/null || true)"
|
|
@@ -607,7 +683,9 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
607
683
|
git -C "$repo_root" worktree remove "$source_worktree" --force >/dev/null 2>&1 || true
|
|
608
684
|
fi
|
|
609
685
|
|
|
610
|
-
|
|
686
|
+
if ! delete_local_branch_for_cleanup "$SOURCE_BRANCH"; then
|
|
687
|
+
exit 1
|
|
688
|
+
fi
|
|
611
689
|
|
|
612
690
|
if [[ "$PUSH_ENABLED" -eq 1 && "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
613
691
|
if git -C "$repo_root" ls-remote --exit-code --heads origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
|
|
@@ -155,6 +155,15 @@ env_flag_truthy() {
|
|
|
155
155
|
esac
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
maybe_fail_after_auto_transfer_stash() {
|
|
159
|
+
if env_flag_truthy "${GUARDEX_TEST_FAIL_AFTER_AUTO_TRANSFER_STASH:-}"; then
|
|
160
|
+
echo "[agent-branch-start] Simulated failure after capturing auto-transfer stash." >&2
|
|
161
|
+
return 1
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
return 0
|
|
165
|
+
}
|
|
166
|
+
|
|
158
167
|
default_worktree_root_rel() {
|
|
159
168
|
local raw_agent="$1"
|
|
160
169
|
local override="${GUARDEX_AGENT_TYPE:-}"
|
|
@@ -580,6 +589,27 @@ fi
|
|
|
580
589
|
auto_transfer_stash_ref=""
|
|
581
590
|
auto_transfer_message=""
|
|
582
591
|
auto_transfer_source_branch=""
|
|
592
|
+
auto_transfer_completed=0
|
|
593
|
+
|
|
594
|
+
restore_auto_transfer_stash_on_failure() {
|
|
595
|
+
local exit_code="${1:-0}"
|
|
596
|
+
if [[ "$exit_code" -eq 0 ]] || [[ -z "$auto_transfer_stash_ref" ]] || [[ "$auto_transfer_completed" -eq 1 ]]; then
|
|
597
|
+
return 0
|
|
598
|
+
fi
|
|
599
|
+
|
|
600
|
+
local transfer_label="${auto_transfer_source_branch:-$BASE_BRANCH}"
|
|
601
|
+
if git -C "$repo_root" stash apply "$auto_transfer_stash_ref" >/dev/null 2>&1; then
|
|
602
|
+
git -C "$repo_root" stash drop "$auto_transfer_stash_ref" >/dev/null 2>&1 || true
|
|
603
|
+
auto_transfer_stash_ref=""
|
|
604
|
+
echo "[agent-branch-start] Restored moved changes back to '${transfer_label}' after startup failure." >&2
|
|
605
|
+
else
|
|
606
|
+
echo "[agent-branch-start] Startup failed and auto-restore also failed." >&2
|
|
607
|
+
echo "[agent-branch-start] Changes are preserved in ${auto_transfer_stash_ref} on ${transfer_label}." >&2
|
|
608
|
+
fi
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
trap 'restore_auto_transfer_stash_on_failure "$?"' EXIT
|
|
612
|
+
|
|
583
613
|
current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
584
614
|
protected_branches_raw="$(resolve_protected_branches "$repo_root")"
|
|
585
615
|
if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
|
|
@@ -593,6 +623,9 @@ if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_bra
|
|
|
593
623
|
if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
594
624
|
auto_transfer_source_branch="$current_branch"
|
|
595
625
|
echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}'..."
|
|
626
|
+
if ! maybe_fail_after_auto_transfer_stash; then
|
|
627
|
+
exit 1
|
|
628
|
+
fi
|
|
596
629
|
fi
|
|
597
630
|
fi
|
|
598
631
|
fi
|
|
@@ -610,7 +643,9 @@ git -C "$worktree_path" branch --unset-upstream "$branch_name" >/dev/null 2>&1 |
|
|
|
610
643
|
|
|
611
644
|
if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
612
645
|
if git -C "$worktree_path" stash apply "$auto_transfer_stash_ref" >/dev/null 2>&1; then
|
|
646
|
+
auto_transfer_completed=1
|
|
613
647
|
git -C "$repo_root" stash drop "$auto_transfer_stash_ref" >/dev/null 2>&1 || true
|
|
648
|
+
auto_transfer_stash_ref=""
|
|
614
649
|
transfer_label="${auto_transfer_source_branch:-$BASE_BRANCH}"
|
|
615
650
|
echo "[agent-branch-start] Moved local changes from '${transfer_label}' into '${branch_name}'."
|
|
616
651
|
else
|
|
@@ -23,7 +23,9 @@ const sessionSchema = resolveSessionSchemaModule();
|
|
|
23
23
|
function usage() {
|
|
24
24
|
return (
|
|
25
25
|
'Usage:\n' +
|
|
26
|
-
' node scripts/agent-session-state.js start --repo <path> --branch <name> --task <task> --agent <agent> --worktree <path> --pid <pid> --cli <name
|
|
26
|
+
' node scripts/agent-session-state.js start --repo <path> --branch <name> --task <task> --agent <agent> --worktree <path> --pid <pid> --cli <name> [--task-mode <caveman|omx>] [--openspec-tier <T0|T1|T2|T3>] [--routing-reason <text>] [--state <working|thinking|idle>]\n' +
|
|
27
|
+
' node scripts/agent-session-state.js heartbeat --repo <path> --branch <name> [--state <working|thinking|idle>]\n' +
|
|
28
|
+
' node scripts/agent-session-state.js terminate --repo <path> --branch <name>\n' +
|
|
27
29
|
' node scripts/agent-session-state.js stop --repo <path> --branch <name>\n'
|
|
28
30
|
);
|
|
29
31
|
}
|
|
@@ -65,6 +67,10 @@ function writeSessionRecord(options) {
|
|
|
65
67
|
worktreePath: requireOption(options, 'worktree'),
|
|
66
68
|
pid: requireOption(options, 'pid'),
|
|
67
69
|
cliName: requireOption(options, 'cli'),
|
|
70
|
+
taskMode: options['task-mode'],
|
|
71
|
+
openspecTier: options['openspec-tier'],
|
|
72
|
+
taskRoutingReason: options['routing-reason'],
|
|
73
|
+
state: options.state,
|
|
68
74
|
});
|
|
69
75
|
|
|
70
76
|
const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
|
|
@@ -72,6 +78,53 @@ function writeSessionRecord(options) {
|
|
|
72
78
|
fs.writeFileSync(targetPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
function refreshSessionRecord(options) {
|
|
82
|
+
const repoRoot = requireOption(options, 'repo');
|
|
83
|
+
const branch = requireOption(options, 'branch');
|
|
84
|
+
const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
|
|
85
|
+
if (!fs.existsSync(targetPath)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parsed = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
|
|
90
|
+
const nextRecord = {
|
|
91
|
+
...parsed,
|
|
92
|
+
lastHeartbeatAt: new Date().toISOString(),
|
|
93
|
+
};
|
|
94
|
+
if (options.state) {
|
|
95
|
+
nextRecord.state = options.state;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(nextRecord, null, 2)}\n`, 'utf8');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function readSessionRecord(options) {
|
|
102
|
+
const repoRoot = requireOption(options, 'repo');
|
|
103
|
+
const branch = requireOption(options, 'branch');
|
|
104
|
+
const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
|
|
105
|
+
if (!fs.existsSync(targetPath)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(fs.readFileSync(targetPath, 'utf8'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function terminateSessionProcess(options) {
|
|
112
|
+
const record = readSessionRecord(options);
|
|
113
|
+
const pid = Number(record?.pid);
|
|
114
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
115
|
+
throw new Error('No live pid recorded for branch.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
process.kill(pid, 'SIGTERM');
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error?.code === 'ESRCH') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
75
128
|
function removeSessionRecord(options) {
|
|
76
129
|
const repoRoot = requireOption(options, 'repo');
|
|
77
130
|
const branch = requireOption(options, 'branch');
|
|
@@ -93,6 +146,14 @@ function main() {
|
|
|
93
146
|
writeSessionRecord(options);
|
|
94
147
|
return;
|
|
95
148
|
}
|
|
149
|
+
if (command === 'heartbeat') {
|
|
150
|
+
refreshSessionRecord(options);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (command === 'terminate') {
|
|
154
|
+
terminateSessionProcess(options);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
96
157
|
if (command === 'stop') {
|
|
97
158
|
removeSessionRecord(options);
|
|
98
159
|
return;
|
|
@@ -636,6 +636,41 @@ clear_active_session_state() {
|
|
|
636
636
|
run_active_session_state stop --repo "$repo_root" --branch "$branch"
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
+
heartbeat_active_session_state() {
|
|
640
|
+
local branch="$1"
|
|
641
|
+
run_active_session_state heartbeat --repo "$repo_root" --branch "$branch" --state working
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
normalize_heartbeat_interval_seconds() {
|
|
645
|
+
local raw="${GUARDEX_ACTIVE_SESSION_HEARTBEAT_INTERVAL_SECONDS:-15}"
|
|
646
|
+
if [[ "$raw" =~ ^[0-9]+$ ]] && [[ "$raw" -ge 1 ]]; then
|
|
647
|
+
printf '%s' "$raw"
|
|
648
|
+
return 0
|
|
649
|
+
fi
|
|
650
|
+
printf '15'
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
start_active_session_heartbeat() {
|
|
654
|
+
local branch="$1"
|
|
655
|
+
local interval
|
|
656
|
+
interval="$(normalize_heartbeat_interval_seconds)"
|
|
657
|
+
(
|
|
658
|
+
while true; do
|
|
659
|
+
sleep "$interval" || break
|
|
660
|
+
heartbeat_active_session_state "$branch"
|
|
661
|
+
done
|
|
662
|
+
) &
|
|
663
|
+
active_session_heartbeat_pid="$!"
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
stop_active_session_heartbeat() {
|
|
667
|
+
if [[ -n "${active_session_heartbeat_pid:-}" ]]; then
|
|
668
|
+
kill "$active_session_heartbeat_pid" >/dev/null 2>&1 || true
|
|
669
|
+
wait "$active_session_heartbeat_pid" >/dev/null 2>&1 || true
|
|
670
|
+
active_session_heartbeat_pid=""
|
|
671
|
+
fi
|
|
672
|
+
}
|
|
673
|
+
|
|
639
674
|
origin_remote_supports_pr_finish() {
|
|
640
675
|
local origin_url
|
|
641
676
|
origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
|
|
@@ -1024,9 +1059,11 @@ fi
|
|
|
1024
1059
|
echo "[codex-agent] Task routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
|
|
1025
1060
|
|
|
1026
1061
|
active_session_recorded=0
|
|
1062
|
+
active_session_heartbeat_pid=""
|
|
1027
1063
|
cleanup_active_session_state_on_exit() {
|
|
1028
1064
|
set +e
|
|
1029
1065
|
if [[ "${active_session_recorded:-0}" -eq 1 && -n "${worktree_branch:-}" && "${worktree_branch:-}" != "HEAD" ]]; then
|
|
1066
|
+
stop_active_session_heartbeat
|
|
1030
1067
|
clear_active_session_state "$worktree_branch"
|
|
1031
1068
|
active_session_recorded=0
|
|
1032
1069
|
fi
|
|
@@ -1034,6 +1071,7 @@ cleanup_active_session_state_on_exit() {
|
|
|
1034
1071
|
|
|
1035
1072
|
record_active_session_state "$worktree_path" "$worktree_branch"
|
|
1036
1073
|
active_session_recorded=1
|
|
1074
|
+
start_active_session_heartbeat "$worktree_branch"
|
|
1037
1075
|
trap cleanup_active_session_state_on_exit EXIT INT TERM
|
|
1038
1076
|
|
|
1039
1077
|
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
|
|
@@ -4,6 +4,8 @@ const fs = require('node:fs');
|
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
|
|
7
|
+
const PATCH_COMPATIBILITY_WINDOW = 20;
|
|
8
|
+
|
|
7
9
|
function parseOptions(argv) {
|
|
8
10
|
const options = {};
|
|
9
11
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -43,6 +45,26 @@ function removeIfExists(targetPath) {
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
function parseSimpleSemver(version) {
|
|
49
|
+
const parts = String(version || '').trim().split('.').map((part) => Number.parseInt(part, 10));
|
|
50
|
+
if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
|
|
51
|
+
throw new Error(`Expected simple semver for the Active Agents companion, received "${version}".`);
|
|
52
|
+
}
|
|
53
|
+
return parts;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildInstallTargets(extensionId, version, extensionsDir) {
|
|
57
|
+
const [major, minor, patch] = parseSimpleSemver(version);
|
|
58
|
+
const firstCompatiblePatch = Math.max(0, patch - PATCH_COMPATIBILITY_WINDOW);
|
|
59
|
+
const targets = [path.join(extensionsDir, extensionId)];
|
|
60
|
+
|
|
61
|
+
for (let compatiblePatch = firstCompatiblePatch; compatiblePatch <= patch; compatiblePatch += 1) {
|
|
62
|
+
targets.push(path.join(extensionsDir, `${extensionId}-${major}.${minor}.${compatiblePatch}`));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return targets;
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
function main() {
|
|
47
69
|
const repoRoot = path.resolve(__dirname, '..');
|
|
48
70
|
const options = parseOptions(process.argv.slice(2));
|
|
@@ -57,30 +79,35 @@ function main() {
|
|
|
57
79
|
);
|
|
58
80
|
|
|
59
81
|
fs.mkdirSync(extensionsDir, { recursive: true });
|
|
60
|
-
const
|
|
82
|
+
const targetDirs = buildInstallTargets(extensionId, manifest.version, extensionsDir);
|
|
83
|
+
const canonicalTargetDir = targetDirs[0];
|
|
84
|
+
const keepDirNames = new Set(targetDirs.map((targetDir) => path.basename(targetDir)));
|
|
61
85
|
|
|
62
86
|
for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
|
63
87
|
if (!entry.isDirectory()) {
|
|
64
88
|
continue;
|
|
65
89
|
}
|
|
66
|
-
if (entry.name
|
|
90
|
+
if (keepDirNames.has(entry.name)) {
|
|
67
91
|
continue;
|
|
68
92
|
}
|
|
69
|
-
if (entry.name.startsWith(`${extensionId}-`)) {
|
|
93
|
+
if (entry.name === extensionId || entry.name.startsWith(`${extensionId}-`)) {
|
|
70
94
|
removeIfExists(path.join(extensionsDir, entry.name));
|
|
71
95
|
}
|
|
72
96
|
}
|
|
73
97
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
98
|
+
for (const targetDir of targetDirs) {
|
|
99
|
+
removeIfExists(targetDir);
|
|
100
|
+
fs.cpSync(sourceDir, targetDir, {
|
|
101
|
+
recursive: true,
|
|
102
|
+
force: true,
|
|
103
|
+
preserveTimestamps: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
80
106
|
|
|
81
107
|
process.stdout.write(
|
|
82
|
-
`[guardex-active-agents] Installed ${extensionId}@${manifest.version} to ${
|
|
83
|
-
|
|
108
|
+
`[guardex-active-agents] Installed ${extensionId}@${manifest.version} to ${canonicalTargetDir}\n` +
|
|
109
|
+
`[guardex-active-agents] Refreshed ${targetDirs.length - 1} recent patch compatibility path(s) for already-open windows.\n` +
|
|
110
|
+
'[guardex-active-agents] Reload each already-open VS Code window to activate the newest Source Control companion.\n',
|
|
84
111
|
);
|
|
85
112
|
}
|
|
86
113
|
|
|
@@ -50,17 +50,38 @@ Chronological checkpoint log for all roles.
|
|
|
50
50
|
CPTEOF
|
|
51
51
|
fi
|
|
52
52
|
|
|
53
|
+
if [[ ! -f "$PLAN_DIR/open-questions.md" ]]; then
|
|
54
|
+
cat > "$PLAN_DIR/open-questions.md" <<OPENQUESTIONSEOF
|
|
55
|
+
# Open Questions: ${PLAN_SLUG}
|
|
56
|
+
|
|
57
|
+
Capture unresolved plan questions here as unchecked checklist items.
|
|
58
|
+
Keep each item concrete, decision-shaped, and easy to close with evidence.
|
|
59
|
+
|
|
60
|
+
- [ ] Add the next unresolved question here.
|
|
61
|
+
OPENQUESTIONSEOF
|
|
62
|
+
fi
|
|
63
|
+
|
|
53
64
|
if [[ ! -f "$PLAN_DIR/README.md" ]]; then
|
|
54
65
|
{
|
|
55
66
|
echo "# Plan Workspace: ${PLAN_SLUG}"
|
|
56
67
|
echo
|
|
57
68
|
echo "This folder stores durable planning artifacts before implementation changes."
|
|
58
69
|
echo
|
|
70
|
+
echo "## Shared files"
|
|
71
|
+
echo "- \`summary.md\`"
|
|
72
|
+
echo "- \`checkpoints.md\`"
|
|
73
|
+
echo "- \`phases.md\`"
|
|
74
|
+
echo "- \`open-questions.md\`"
|
|
75
|
+
echo "- \`coordinator-prompt.md\`"
|
|
76
|
+
echo "- \`kickoff-prompts.md\`"
|
|
77
|
+
echo
|
|
59
78
|
echo "## Role folders"
|
|
60
79
|
for role in "${ROLES[@]}"; do
|
|
61
80
|
echo "- \`${role}/\`"
|
|
62
81
|
done
|
|
63
82
|
echo
|
|
83
|
+
echo "When Codex or Claude hits an unresolved question that should survive chat, add it to \`open-questions.md\` as an unchecked \`- [ ]\` item."
|
|
84
|
+
echo
|
|
64
85
|
echo "Each role folder contains OpenSpec-style artifacts:"
|
|
65
86
|
echo "- \`.openspec.yaml\`"
|
|
66
87
|
echo "- \`proposal.md\`"
|
|
@@ -85,15 +106,17 @@ Drive this plan from draft to execution-ready status with strict checkpoint disc
|
|
|
85
106
|
|
|
86
107
|
- \`openspec/plan/${PLAN_SLUG}/summary.md\`
|
|
87
108
|
- \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
|
|
109
|
+
- \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
|
|
88
110
|
- \`openspec/plan/${PLAN_SLUG}/planner/plan.md\`
|
|
89
111
|
- role \`tasks.md\` files for planner/architect/critic/executor/writer/verifier
|
|
90
112
|
|
|
91
113
|
## Coordinator responsibilities
|
|
92
114
|
|
|
93
115
|
1. Keep checkpoints current in each role \`tasks.md\` and root \`checkpoints.md\`.
|
|
94
|
-
2.
|
|
95
|
-
3.
|
|
96
|
-
4.
|
|
116
|
+
2. Route unresolved questions and branching decisions into \`open-questions.md\`.
|
|
117
|
+
3. Ensure each role has explicit acceptance criteria and verification evidence.
|
|
118
|
+
4. Prevent implementation from starting before planning gates are complete.
|
|
119
|
+
5. Keep handoffs concise: files changed, behavior touched, verification output, risks.
|
|
97
120
|
|
|
98
121
|
## Wave-splitting decision (optional)
|
|
99
122
|
|
|
@@ -109,6 +132,7 @@ If wave splitting is not needed, keep execution under a single owner with normal
|
|
|
109
132
|
|
|
110
133
|
- All role checkpoints required for planning are done.
|
|
111
134
|
- Execution lanes (if any) have clear ownership boundaries.
|
|
135
|
+
- \`open-questions.md\` captures unresolved decisions that still need answers.
|
|
112
136
|
- Verification plan and rollback expectations are explicit and testable.
|
|
113
137
|
COORDPROMPTEOF
|
|
114
138
|
fi
|
|
@@ -431,6 +455,7 @@ EXCCPTEOF
|
|
|
431
455
|
|
|
432
456
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
433
457
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
458
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
434
459
|
|
|
435
460
|
## 6. Cleanup
|
|
436
461
|
|
|
@@ -467,6 +492,7 @@ TASKEOF
|
|
|
467
492
|
|
|
468
493
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
469
494
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
495
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
470
496
|
|
|
471
497
|
## 6. Cleanup
|
|
472
498
|
|
|
@@ -503,6 +529,7 @@ TASKEOF
|
|
|
503
529
|
|
|
504
530
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
505
531
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
532
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
506
533
|
|
|
507
534
|
## 6. Cleanup
|
|
508
535
|
|
|
@@ -539,6 +566,7 @@ TASKEOF
|
|
|
539
566
|
|
|
540
567
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
541
568
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
569
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
542
570
|
|
|
543
571
|
## 6. Cleanup
|
|
544
572
|
|
|
@@ -575,6 +603,7 @@ TASKEOF
|
|
|
575
603
|
|
|
576
604
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
577
605
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
606
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
578
607
|
|
|
579
608
|
## 6. Cleanup
|
|
580
609
|
|
|
@@ -611,6 +640,7 @@ TASKEOF
|
|
|
611
640
|
|
|
612
641
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
613
642
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
643
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
614
644
|
|
|
615
645
|
## 6. Cleanup
|
|
616
646
|
|
|
@@ -647,6 +677,7 @@ TASKEOF
|
|
|
647
677
|
|
|
648
678
|
- [ ] 5.1 Owner recorded this lane before edits.
|
|
649
679
|
- [ ] 5.2 Record joined agents / handoffs, or mark \`N/A\` when solo.
|
|
680
|
+
- [ ] 5.3 Record unresolved plan questions in \`../open-questions.md\`, or mark \`N/A\` when none.
|
|
650
681
|
|
|
651
682
|
## 6. Cleanup
|
|
652
683
|
|
|
@@ -13,17 +13,18 @@ node scripts/install-vscode-active-agents-extension.js
|
|
|
13
13
|
```
|
|
14
14
|
|
|
15
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
|
|
16
|
+
3. In Source Control -> `Active Agents`, use `Start agent` to enter a task + agent name and launch the repo Guardex agent runner. The companion prefers `bash scripts/codex-agent.sh` when present, falls back to `npm run agent:codex --`, and only uses `gx branch start` as a last resort.
|
|
17
17
|
|
|
18
18
|
What it does:
|
|
19
19
|
|
|
20
20
|
- Bundles a local GitGuardex icon so repo installs show branded extension metadata inside VS Code.
|
|
21
21
|
- Adds an `Active Agents` view to the Source Control container.
|
|
22
22
|
- Renders one repo node per live Guardex workspace with grouped `ACTIVE AGENTS` and `CHANGES` sections.
|
|
23
|
-
- Splits live sessions inside `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `
|
|
23
|
+
- Splits live sessions inside `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `THINKING`, `STALLED`, and `DEAD` groups so stuck, active, and inactive lanes stand out immediately.
|
|
24
24
|
- Mirrors the same live state in the VS Code status bar so the selected session or active-agent count stays visible outside the tree.
|
|
25
|
-
- Shows one row per live Guardex sandbox session inside those activity groups.
|
|
25
|
+
- Shows one row per live Guardex sandbox session inside those activity groups, with changed-file rows nested under sessions that are touching files.
|
|
26
26
|
- Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
|
|
27
|
-
- 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.
|
|
28
|
-
- Uses distinct VS Code codicons for each session state
|
|
29
|
-
- Reads repo-local presence files from `.omx/state/active-sessions
|
|
27
|
+
- Derives session state from dirty worktree status, git conflict markers, heartbeat freshness, PID liveness, and recent file mtimes, surfaces working/dead/conflict counts in the repo/header summary, and shows changed-file counts for active edits.
|
|
28
|
+
- Uses distinct VS Code codicons for each session state, including animated `loading~spin` for `WORKING NOW`.
|
|
29
|
+
- Reads repo-local presence files from `.omx/state/active-sessions/`, expects `lastHeartbeatAt` freshness, and falls back to managed worktree-root `AGENT.lock` telemetry when the launcher session file is absent.
|
|
30
|
+
- Publishes `guardex.hasAgents` and `guardex.hasConflicts` context keys for other VS Code contributions.
|