@imdeadpool/guardex 7.0.21 → 7.0.23

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.
@@ -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.
@@ -218,18 +218,48 @@ fi
218
218
 
219
219
  get_worktree_for_branch() {
220
220
  local branch="$1"
221
- git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" '
221
+ git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${agent_worktree_root}/__source-probe-" '
222
222
  $1 == "worktree" { wt = $2 }
223
- $1 == "branch" && $2 == target { print wt; exit }
223
+ $1 == "branch" && $2 == target {
224
+ if (index(wt, probe_prefix) != 1) {
225
+ print wt
226
+ exit
227
+ }
228
+ }
224
229
  '
225
230
  }
226
231
 
232
+ remove_stale_source_probe_worktrees() {
233
+ local branch="$1"
234
+ local stale_probe=""
235
+
236
+ while IFS= read -r stale_probe; do
237
+ [[ -z "$stale_probe" ]] && continue
238
+ [[ "$stale_probe" == "$current_worktree" ]] && continue
239
+
240
+ echo "[agent-branch-finish] Removing stale source-probe worktree for '${branch}': ${stale_probe}" >&2
241
+ git -C "$stale_probe" rebase --abort >/dev/null 2>&1 || true
242
+ git -C "$stale_probe" merge --abort >/dev/null 2>&1 || true
243
+ git -C "$repo_root" worktree remove "$stale_probe" --force >/dev/null 2>&1 || true
244
+ done < <(
245
+ git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${agent_worktree_root}/__source-probe-" '
246
+ $1 == "worktree" { wt = $2 }
247
+ $1 == "branch" && $2 == target {
248
+ if (index(wt, probe_prefix) == 1) {
249
+ print wt
250
+ }
251
+ }
252
+ '
253
+ )
254
+ }
255
+
227
256
  is_clean_worktree() {
228
257
  local wt="$1"
229
258
  git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
230
259
  && git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"
231
260
  }
232
261
 
262
+ remove_stale_source_probe_worktrees "$SOURCE_BRANCH"
233
263
  source_worktree="$(get_worktree_for_branch "$SOURCE_BRANCH")"
234
264
  created_source_probe=0
235
265
  source_probe_path=""
@@ -295,8 +325,13 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
295
325
 
296
326
  echo "[agent-sync-guard] Auto-sync failed while rebasing '${SOURCE_BRANCH}' onto origin/${BASE_BRANCH}." >&2
297
327
  if [[ "$rebase_active" -eq 1 ]]; then
298
- echo "[agent-sync-guard] Resolve conflicts, then run: git -C \"$source_worktree\" rebase --continue" >&2
299
- echo "[agent-sync-guard] Or abort: git -C \"$source_worktree\" rebase --abort" >&2
328
+ if [[ "$created_source_probe" -eq 1 ]]; then
329
+ echo "[agent-sync-guard] Temporary source-probe worktree will be cleaned up on exit." >&2
330
+ echo "[agent-sync-guard] Reattach '${SOURCE_BRANCH}' in a regular worktree, then rebase it onto origin/${BASE_BRANCH} manually." >&2
331
+ else
332
+ echo "[agent-sync-guard] Resolve conflicts, then run: git -C \"$source_worktree\" rebase --continue" >&2
333
+ echo "[agent-sync-guard] Or abort: git -C \"$source_worktree\" rebase --abort" >&2
334
+ fi
300
335
  fi
301
336
  exit 1
302
337
  fi
@@ -366,6 +401,41 @@ is_local_branch_delete_error() {
366
401
  return 1
367
402
  }
368
403
 
404
+ is_remote_branch_missing_error() {
405
+ local output="$1"
406
+ if [[ "$output" == *"remote ref does not exist"* ]] || [[ "$output" == *"failed to push some refs"* ]]; then
407
+ return 0
408
+ fi
409
+ return 1
410
+ }
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
+
369
439
  read_pr_state() {
370
440
  local state_line
371
441
  state_line="$("$GH_BIN" pr view "$SOURCE_BRANCH" --json state,mergedAt,url --jq '[.state, (.mergedAt // ""), (.url // "")] | join("\u001f")' 2>/dev/null || true)"
@@ -564,11 +634,21 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
564
634
  git -C "$repo_root" worktree remove "$source_worktree" --force >/dev/null 2>&1 || true
565
635
  fi
566
636
 
567
- git -C "$repo_root" branch -d "$SOURCE_BRANCH"
637
+ if ! delete_local_branch_for_cleanup "$SOURCE_BRANCH"; then
638
+ exit 1
639
+ fi
568
640
 
569
641
  if [[ "$PUSH_ENABLED" -eq 1 && "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
570
642
  if git -C "$repo_root" ls-remote --exit-code --heads origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
571
- git -C "$repo_root" push origin --delete "$SOURCE_BRANCH"
643
+ remote_delete_output=""
644
+ if ! remote_delete_output="$(git -C "$repo_root" push origin --delete "$SOURCE_BRANCH" 2>&1)"; then
645
+ if is_remote_branch_missing_error "$remote_delete_output"; then
646
+ echo "[agent-branch-finish] Remote branch '${SOURCE_BRANCH}' was already deleted; continuing cleanup." >&2
647
+ else
648
+ echo "$remote_delete_output" >&2
649
+ exit 1
650
+ fi
651
+ fi
572
652
  fi
573
653
  fi
574
654
 
@@ -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>\n' +
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;
@@ -111,6 +111,13 @@ is_managed_worktree_path() {
111
111
  return 1
112
112
  }
113
113
 
114
+ is_temporary_worktree_path() {
115
+ local entry="$1"
116
+ local name
117
+ name="$(basename "$entry")"
118
+ [[ "$name" == __agent_integrate-* || "$name" == __source-probe-* ]]
119
+ }
120
+
114
121
  resolve_base_branch() {
115
122
  local configured=""
116
123
  local current=""
@@ -425,7 +432,9 @@ process_entry() {
425
432
  local remove_reason=""
426
433
  local branch_delete_mode="safe"
427
434
 
428
- if [[ -z "$branch_ref" ]]; then
435
+ if is_temporary_worktree_path "$wt"; then
436
+ remove_reason="temporary-worktree"
437
+ elif [[ -z "$branch_ref" ]]; then
429
438
  remove_reason="detached-worktree"
430
439
  elif ! git -C "$repo_root" show-ref --verify --quiet "refs/heads/${branch}"; then
431
440
  remove_reason="missing-branch"
@@ -452,6 +461,11 @@ process_entry() {
452
461
  return
453
462
  fi
454
463
 
464
+ if [[ "$remove_reason" == "temporary-worktree" ]]; then
465
+ git -C "$wt" rebase --abort >/dev/null 2>&1 || true
466
+ git -C "$wt" merge --abort >/dev/null 2>&1 || true
467
+ fi
468
+
455
469
  if [[ "$FORCE_DIRTY" -ne 1 ]] && ! is_clean_worktree "$wt"; then
456
470
  skipped_dirty=$((skipped_dirty + 1))
457
471
  echo "[agent-worktree-prune] Skipping dirty worktree (${remove_reason}): ${wt}"
@@ -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 targetDir = path.join(extensionsDir, `${extensionId}-${manifest.version}`);
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 === path.basename(targetDir)) {
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
- removeIfExists(targetDir);
75
- fs.cpSync(sourceDir, targetDir, {
76
- recursive: true,
77
- force: true,
78
- preserveTimestamps: true,
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 ${targetDir}\n` +
83
- '[guardex-active-agents] Reload the VS Code window to activate the Source Control companion.\n',
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. Ensure each role has explicit acceptance criteria and verification evidence.
95
- 3. Prevent implementation from starting before planning gates are complete.
96
- 4. Keep handoffs concise: files changed, behavior touched, verification output, risks.
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,15 +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
+ - Bundles a local GitGuardex icon so repo installs show branded extension metadata inside VS Code.
20
21
  - Adds an `Active Agents` view to the Source Control container.
21
22
  - 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.
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
+ - 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, with changed-file rows nested under sessions that are touching files.
24
26
  - 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.
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.