@imdeadpool/guardex 7.0.24 → 7.0.26

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 CHANGED
@@ -140,7 +140,7 @@ That's it. Install and update via `@imdeadpool/guardex`. Setup installs the mini
140
140
  </div>
141
141
 
142
142
  > [!NOTE]
143
- > In this repo, `CLAUDE.md` is a symlink to `AGENTS.md`, so Claude reads the same contract. Optional Codex/Claude companion files are installed at the user level with `gx install-agent-skills`, not copied into each repo.
143
+ > In this repo, `CLAUDE.md` is a symlink to `AGENTS.md`, so Claude reads the same contract. Optional Codex/Claude companion files still install at the user level with `gx install-agent-skills`, while the generic repo skill catalog is available through `npx skills add recodeee/` or directly via `npx skills add recodeee/gitguardex`.
144
144
 
145
145
  ### Decision flow
146
146
 
@@ -266,7 +266,7 @@ To install the real companion into local VS Code from a GitGuardex-wired repo:
266
266
  node scripts/install-vscode-active-agents-extension.js
267
267
  ```
268
268
 
269
- It adds an `Active Agents` view to the Source Control container, groups each live repo into `ACTIVE AGENTS` and `CHANGES` sections, splits `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `IDLE`, `STALLED`, and `DEAD` when those states are present, mirrors the selected session or active-agent count in the VS Code status bar, reads `.omx/state/active-sessions/*.json`, derives session state from git conflict markers, dirty worktree status, PID liveness, and recent file mtimes, and surfaces working/dead counts in the repo/header affordances. Reload the VS Code window after install.
269
+ It adds a dedicated `Active Agents` Activity Bar container with a hive icon, shows the live active-agent count as a badge on that icon, groups each live repo into `ACTIVE AGENTS` and `CHANGES` sections, splits `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `IDLE`, `STALLED`, and `DEAD` when those states are present, mirrors the selected session or active-agent count in the VS Code status bar, reads `.omx/state/active-sessions/*.json`, derives session state from git conflict markers, dirty worktree status, PID liveness, and recent file mtimes, and surfaces working/dead counts in the repo/header affordances. Reload the VS Code window after install.
270
270
 
271
271
  ---
272
272
 
@@ -459,6 +459,23 @@ npm i -g oh-my-claude-sisyphus@latest
459
459
  Repo: <https://github.com/Yeachan-Heo/oh-my-claudecode>
460
460
  [![GitHub stars](https://img.shields.io/github/stars/Yeachan-Heo/oh-my-claudecode?style=social)](https://github.com/Yeachan-Heo/oh-my-claudecode)
461
461
 
462
+ ### GitGuardex skills - install the repo skill catalog through `npx skills`
463
+
464
+ For agents that already support the generic `skills` installer flow, GitGuardex now exposes its repo skill catalog directly. You can start from the broader `recodeee` source or jump straight into this repo's catalog.
465
+
466
+ ```sh
467
+ npx skills add recodeee/
468
+ ```
469
+
470
+ ```sh
471
+ npx skills add recodeee/gitguardex
472
+ ```
473
+
474
+ This repo currently exposes `gitguardex` and `guardex-merge-skills-to-dev` through that flow. If the picker does not show a separate `guardex` skill, that is expected: `guardex` remains the legacy CLI alias, while the repo skill itself is named `gitguardex`. Use `gx install-agent-skills` when you want the Codex/Claude user-home startup files instead of the generic `skills` catalog.
475
+
476
+ Repo: <https://github.com/recodeee/gitguardex>
477
+ [![GitHub stars](https://img.shields.io/github/stars/recodeee/gitguardex?style=social)](https://github.com/recodeee/gitguardex)
478
+
462
479
  ### Caveman — output compression for long agent runs
463
480
 
464
481
  Ultra-compressed response mode for Claude/Codex-style agents. Useful when you want less output-token churn during long reviews, debug loops, or multi-agent sessions.
@@ -609,7 +626,7 @@ vscode/guardex-active-agents/README.md
609
626
 
610
627
  Legacy compatibility note: older repos may still contain repo-local workflow scripts under `scripts/`. Direct `gx branch ...`, `gx locks ...`, `gx finish`, `gx cleanup`, `gx merge`, and `gx migrate` do not require them. `gx migrate` removes those leftover workflow shims by default. The CLI still honors repo-local `scripts/review-bot-watch.sh` and `scripts/codex-agent.sh` when they are already present so older repos can keep working during migration.
611
628
 
612
- Optional Codex/Claude user-level companions still install with `gx install-agent-skills`; they are not copied into each repo.
629
+ Optional Codex/Claude user-level companions still install with `gx install-agent-skills`; the generic repo skill catalog is available with `npx skills add recodeee/` or directly via `npx skills add recodeee/gitguardex`. Neither path copies those user-home files into each repo.
613
630
 
614
631
  ---
615
632
 
@@ -672,6 +689,16 @@ npm pack --dry-run
672
689
  <details>
673
690
  <summary><strong>v7.x</strong></summary>
674
691
 
692
+ ### v7.0.26
693
+ - Bumped `@imdeadpool/guardex` from `7.0.25` to `7.0.26` so npm can publish a fresh version after `v7.0.25` reached GitHub Releases while the registry stayed on `7.0.24`.
694
+ - README now documents both `npx skills add recodeee/` and `npx skills add recodeee/gitguardex`, and explains why the picker shows `gitguardex` instead of a separate `guardex` skill.
695
+ - Keep the release scoped to version metadata plus the already-merged README installer guidance on `main`; no additional CLI/runtime behavior changed in this lane.
696
+
697
+ ### v7.0.25
698
+ - Bumped `@imdeadpool/guardex` from `7.0.24` to `7.0.25` so npm and GitHub Releases can ship the current `main` payload.
699
+ - The bundled `GitGuardex Active Agents` VS Code companion now self-heals stale repo-scan ignore settings in older repos, keeps plain managed sandboxes visible in Source Control, and preserves cleanup truth so the tree matches actual sandbox state.
700
+ - Bumped the shipped Active Agents companion manifests from `0.0.8` to `0.0.9` so local VS Code installs can pick up the newer workspace build and show the refreshed extension version from this release.
701
+
675
702
  ### v7.0.24
676
703
  - Bumped `@imdeadpool/guardex` from `7.0.23` to `7.0.24` so GitHub Releases and the npm publish retry can advance together after `v7.0.23` landed on GitHub but not on npm.
677
704
  - Release verification no longer loses its base ref on tag-triggered runs, so the publish workflow keeps the history it needs before packing and publish checks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.24",
3
+ "version": "7.0.26",
4
4
  "description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,
package/src/cli/args.js CHANGED
@@ -770,6 +770,7 @@ function parseFinishArgs(rawArgs, defaults = {}) {
770
770
  cleanup: defaults.cleanup ?? true,
771
771
  keepRemote: false,
772
772
  noAutoCommit: false,
773
+ parentGitlinkCommit: defaults.parentGitlinkCommit ?? true,
773
774
  failFast: false,
774
775
  commitMessage: '',
775
776
  mergeMode: defaults.mergeMode || 'pr',
@@ -865,6 +866,14 @@ function parseFinishArgs(rawArgs, defaults = {}) {
865
866
  options.noAutoCommit = true;
866
867
  continue;
867
868
  }
869
+ if (arg === '--parent-gitlink-commit') {
870
+ options.parentGitlinkCommit = true;
871
+ continue;
872
+ }
873
+ if (arg === '--no-parent-gitlink-commit') {
874
+ options.parentGitlinkCommit = false;
875
+ continue;
876
+ }
868
877
  if (arg === '--fail-fast') {
869
878
  options.failFast = true;
870
879
  continue;
package/src/context.js CHANGED
@@ -129,6 +129,14 @@ const TEMPLATE_FILES = [
129
129
  'vscode/guardex-active-agents/session-schema.js',
130
130
  'vscode/guardex-active-agents/README.md',
131
131
  'vscode/guardex-active-agents/icon.png',
132
+ 'vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json',
133
+ 'vscode/guardex-active-agents/fileicons/icons/agent.svg',
134
+ 'vscode/guardex-active-agents/fileicons/icons/branch.svg',
135
+ 'vscode/guardex-active-agents/fileicons/icons/config.svg',
136
+ 'vscode/guardex-active-agents/fileicons/icons/hook.svg',
137
+ 'vscode/guardex-active-agents/fileicons/icons/openspec.svg',
138
+ 'vscode/guardex-active-agents/fileicons/icons/plan.svg',
139
+ 'vscode/guardex-active-agents/fileicons/icons/spec.svg',
132
140
  ];
133
141
 
134
142
  const PACKAGE_ROOT_SOURCE_OVERRIDES = new Set([
@@ -139,6 +147,14 @@ const PACKAGE_ROOT_SOURCE_OVERRIDES = new Set([
139
147
  'vscode/guardex-active-agents/session-schema.js',
140
148
  'vscode/guardex-active-agents/README.md',
141
149
  'vscode/guardex-active-agents/icon.png',
150
+ 'vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json',
151
+ 'vscode/guardex-active-agents/fileicons/icons/agent.svg',
152
+ 'vscode/guardex-active-agents/fileicons/icons/branch.svg',
153
+ 'vscode/guardex-active-agents/fileicons/icons/config.svg',
154
+ 'vscode/guardex-active-agents/fileicons/icons/hook.svg',
155
+ 'vscode/guardex-active-agents/fileicons/icons/openspec.svg',
156
+ 'vscode/guardex-active-agents/fileicons/icons/plan.svg',
157
+ 'vscode/guardex-active-agents/fileicons/icons/spec.svg',
142
158
  ]);
143
159
 
144
160
  const LEGACY_WORKFLOW_SHIM_SPECS = [
@@ -263,8 +279,12 @@ const AGENT_WORKTREE_RELATIVE_DIRS = [
263
279
  const MANAGED_REPO_SCAN_IGNORED_FOLDERS = [
264
280
  '.omx/agent-worktrees',
265
281
  '**/.omx/agent-worktrees',
282
+ '.omx/.tmp-worktrees',
283
+ '**/.omx/.tmp-worktrees',
266
284
  '.omc/agent-worktrees',
267
285
  '**/.omc/agent-worktrees',
286
+ '.omc/.tmp-worktrees',
287
+ '**/.omc/.tmp-worktrees',
268
288
  ];
269
289
  const MANAGED_GITIGNORE_PATHS = [
270
290
  '.omx/',
@@ -427,6 +447,8 @@ const AI_SETUP_PARTS = [
427
447
  'gx branch start "<task>" "<agent>"',
428
448
  'then gx locks claim --branch "<agent-branch>" <file...> -> inspect once -> patch once -> verify once -> gx branch finish',
429
449
  'batch discovery, git/PR, and CI by phase; avoid repeated peeks or stdin loops',
450
+ 'checkpoint after each milestone: Task -> Done -> Current status -> Next; keep only the latest checkpoint(s) in active context',
451
+ 'summarize tool results, keep stdin/process chatter ephemeral, and keep execution log separate from reasoning context',
430
452
  ],
431
453
  execLines: [
432
454
  'gx branch start "<task>" "<agent>"',
@@ -314,6 +314,118 @@ function doctorFinishFlowIsPending(output) {
314
314
  );
315
315
  }
316
316
 
317
+ function verifyDoctorSandboxCleanup(repoRoot, metadata) {
318
+ const branchExists = Boolean(metadata.branch) && gitRefExists(repoRoot, `refs/heads/${metadata.branch}`);
319
+ const worktreeExists = Boolean(metadata.worktreePath) && fs.existsSync(metadata.worktreePath);
320
+
321
+ if (!branchExists && !worktreeExists) {
322
+ return {
323
+ status: 'verified',
324
+ note: 'doctor sandbox cleanup verified',
325
+ };
326
+ }
327
+
328
+ const cleanup = cleanupProtectedBaseSandbox(repoRoot, metadata);
329
+ const branchStillExists = Boolean(metadata.branch) && gitRefExists(repoRoot, `refs/heads/${metadata.branch}`);
330
+ const worktreeStillExists = Boolean(metadata.worktreePath) && fs.existsSync(metadata.worktreePath);
331
+ if (branchStillExists || worktreeStillExists) {
332
+ return {
333
+ status: 'failed',
334
+ note:
335
+ 'doctor sandbox cleanup incomplete ' +
336
+ `(branch=${branchStillExists ? 'present' : 'missing'}, worktree=${worktreeStillExists ? 'present' : 'missing'})`,
337
+ cleanup,
338
+ };
339
+ }
340
+
341
+ return {
342
+ status: 'verified',
343
+ note: 'doctor sandbox cleanup verified',
344
+ cleanup,
345
+ };
346
+ }
347
+
348
+ function verifyDoctorSandboxRemoteCleanup(repoRoot, metadata) {
349
+ if (!metadata.branch || !hasOriginRemote(repoRoot)) {
350
+ return {
351
+ status: 'skipped',
352
+ note: 'doctor sandbox remote cleanup skipped',
353
+ };
354
+ }
355
+
356
+ const remoteBefore = run(
357
+ 'git',
358
+ ['-C', repoRoot, 'ls-remote', '--heads', 'origin', metadata.branch],
359
+ { timeout: 20_000 },
360
+ );
361
+ if (isSpawnFailure(remoteBefore)) {
362
+ throw remoteBefore.error;
363
+ }
364
+ if (remoteBefore.status !== 0) {
365
+ return {
366
+ status: 'failed',
367
+ note: 'doctor sandbox remote branch inspection failed',
368
+ stdout: remoteBefore.stdout || '',
369
+ stderr: remoteBefore.stderr || '',
370
+ };
371
+ }
372
+ if (!String(remoteBefore.stdout || '').trim()) {
373
+ return {
374
+ status: 'verified',
375
+ note: 'doctor sandbox remote cleanup verified',
376
+ };
377
+ }
378
+
379
+ const deleteResult = run(
380
+ 'git',
381
+ ['-C', repoRoot, 'push', 'origin', '--delete', metadata.branch],
382
+ { timeout: 30_000 },
383
+ );
384
+ if (isSpawnFailure(deleteResult)) {
385
+ throw deleteResult.error;
386
+ }
387
+ if (deleteResult.status !== 0) {
388
+ return {
389
+ status: 'failed',
390
+ note: 'doctor sandbox remote branch cleanup failed',
391
+ stdout: deleteResult.stdout || '',
392
+ stderr: deleteResult.stderr || '',
393
+ };
394
+ }
395
+
396
+ const remoteAfter = run(
397
+ 'git',
398
+ ['-C', repoRoot, 'ls-remote', '--heads', 'origin', metadata.branch],
399
+ { timeout: 20_000 },
400
+ );
401
+ if (isSpawnFailure(remoteAfter)) {
402
+ throw remoteAfter.error;
403
+ }
404
+ if (remoteAfter.status !== 0) {
405
+ return {
406
+ status: 'failed',
407
+ note: 'doctor sandbox remote cleanup recheck failed',
408
+ stdout: remoteAfter.stdout || '',
409
+ stderr: remoteAfter.stderr || '',
410
+ };
411
+ }
412
+ if (String(remoteAfter.stdout || '').trim()) {
413
+ return {
414
+ status: 'failed',
415
+ note: 'doctor sandbox remote branch still present after cleanup',
416
+ stdout: remoteAfter.stdout || '',
417
+ stderr: remoteAfter.stderr || '',
418
+ };
419
+ }
420
+
421
+ return {
422
+ status: 'verified',
423
+ note: 'doctor sandbox remote cleanup verified',
424
+ stdout: deleteResult.stdout || '',
425
+ stderr: deleteResult.stderr || '',
426
+ };
427
+ }
428
+
317
429
  function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
318
430
  if (!hasOriginRemote(blocked.repoRoot)) {
319
431
  return {
@@ -353,8 +465,8 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
353
465
 
354
466
  const finishResult = runPackageAsset(
355
467
  'branchFinish',
356
- ['--branch', metadata.branch, '--base', blocked.branch, '--via-pr', waitForMergeArg, '--cleanup'],
357
- { cwd: metadata.worktreePath, timeout: finishTimeoutMs },
468
+ ['--branch', metadata.branch, '--base', blocked.branch, '--via-pr', waitForMergeArg, '--no-cleanup'],
469
+ { cwd: blocked.repoRoot, timeout: finishTimeoutMs },
358
470
  );
359
471
  if (isSpawnFailure(finishResult)) {
360
472
  return {
@@ -384,11 +496,52 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
384
496
  };
385
497
  }
386
498
 
499
+ let cleanupVerification;
500
+ try {
501
+ cleanupVerification = verifyDoctorSandboxCleanup(blocked.repoRoot, metadata);
502
+ } catch (error) {
503
+ return {
504
+ status: 'failed',
505
+ note: `doctor sandbox cleanup verification failed: ${error.message}`,
506
+ stdout: finishResult.stdout || '',
507
+ stderr: finishResult.stderr || '',
508
+ };
509
+ }
510
+ if (cleanupVerification.status === 'failed') {
511
+ return {
512
+ status: 'failed',
513
+ note: cleanupVerification.note,
514
+ stdout: finishResult.stdout || '',
515
+ stderr: finishResult.stderr || '',
516
+ };
517
+ }
518
+
519
+ let remoteCleanupVerification;
520
+ try {
521
+ remoteCleanupVerification = verifyDoctorSandboxRemoteCleanup(blocked.repoRoot, metadata);
522
+ } catch (error) {
523
+ return {
524
+ status: 'failed',
525
+ note: `doctor sandbox remote cleanup verification failed: ${error.message}`,
526
+ stdout: finishResult.stdout || '',
527
+ stderr: finishResult.stderr || '',
528
+ };
529
+ }
530
+ if (remoteCleanupVerification.status === 'failed') {
531
+ return {
532
+ status: 'failed',
533
+ note: remoteCleanupVerification.note,
534
+ stdout: [finishResult.stdout || '', remoteCleanupVerification.stdout || ''].filter(Boolean).join('\n'),
535
+ stderr: [finishResult.stderr || '', remoteCleanupVerification.stderr || ''].filter(Boolean).join('\n'),
536
+ };
537
+ }
538
+
387
539
  return {
388
540
  status: 'completed',
389
- note: 'doctor sandbox finish flow completed',
390
- stdout: finishResult.stdout || '',
391
- stderr: finishResult.stderr || '',
541
+ note: 'doctor sandbox finish flow completed and cleanup verified',
542
+ stdout: [finishResult.stdout || '', remoteCleanupVerification.stdout || ''].filter(Boolean).join('\n'),
543
+ stderr: [finishResult.stderr || '', remoteCleanupVerification.stderr || ''].filter(Boolean).join('\n'),
544
+ cleanup: cleanupVerification.cleanup,
392
545
  };
393
546
  }
394
547
 
@@ -317,6 +317,7 @@ function finish(rawArgs, defaults = {}) {
317
317
  if (options.keepRemote) {
318
318
  finishArgs.push('--keep-remote-branch');
319
319
  }
320
+ finishArgs.push(options.parentGitlinkCommit ? '--parent-gitlink-commit' : '--no-parent-gitlink-commit');
320
321
 
321
322
  if (options.dryRun) {
322
323
  console.log(`[${TOOL_NAME}] [dry-run] Would run: gx branch finish ${finishArgs.join(' ')}`);
@@ -21,8 +21,11 @@ Default: less word, same proof.
21
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
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
23
  - Tool / hook summaries stay tiny: command, status, last meaningful lines only. Drop routine hook boilerplate.
24
+ - Keep raw terminal interaction out of long-lived context. For `write_stdin` or interactive babysitting, retain only process, action sent, current result, and next action.
25
+ - Keep execution log separate from reasoning context: full commands/stdout belong in logs, while prompt context keeps only the latest 1-2 checkpoints plus the newest tool-result summary.
24
26
  - 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
27
  - When a session turns fragmented, collapse back to inspect once, patch once, verify once, and summarize once.
28
+ - Use a fixed checkpoint shape when compacting: `Task`, `Done`, `Current status`, and `Next`.
26
29
  - 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
30
 
28
31
  ## OMX Caveman Style
@@ -9,6 +9,7 @@ Use this skill when you only want to promote Codex skill file updates into the b
9
9
 
10
10
  ## What this merges
11
11
 
12
+ - `skills/**/SKILL.md`
12
13
  - `.codex/skills/**/SKILL.md`
13
14
  - `templates/codex/skills/**/SKILL.md`
14
15
 
@@ -33,7 +34,7 @@ gx branch start "merge-skill-files-to-${BASE_BRANCH}" "skill-merge" "$BASE_BRANC
33
34
 
34
35
  ```sh
35
36
  SOURCE_BRANCH="<agent-branch>"
36
- git checkout "$SOURCE_BRANCH" -- ':(glob).codex/skills/**/SKILL.md' ':(glob)templates/codex/skills/**/SKILL.md'
37
+ git checkout "$SOURCE_BRANCH" -- ':(glob)skills/**/SKILL.md' ':(glob).codex/skills/**/SKILL.md' ':(glob)templates/codex/skills/**/SKILL.md'
37
38
  ```
38
39
 
39
40
  5. Verify scope before commit:
@@ -46,7 +47,7 @@ git diff --name-only
46
47
  6. Commit and merge back to base using guardex finish flow:
47
48
 
48
49
  ```sh
49
- git add .codex/skills templates/codex/skills
50
+ git add skills .codex/skills templates/codex/skills
50
51
  git commit -m "Merge skill file updates into ${BASE_BRANCH}"
51
52
  gx branch finish --branch "$(git rev-parse --abbrev-ref HEAD)" --base "$BASE_BRANCH" --via-pr --wait-for-merge --cleanup
52
53
  ```
@@ -15,6 +15,7 @@ CLEANUP_AFTER_MERGE_RAW="${GUARDEX_FINISH_CLEANUP:-false}"
15
15
  WAIT_FOR_MERGE_RAW="${GUARDEX_FINISH_WAIT_FOR_MERGE:-false}"
16
16
  WAIT_TIMEOUT_SECONDS_RAW="${GUARDEX_FINISH_WAIT_TIMEOUT_SECONDS:-1800}"
17
17
  WAIT_POLL_SECONDS_RAW="${GUARDEX_FINISH_WAIT_POLL_SECONDS:-10}"
18
+ PARENT_GITLINK_AUTO_COMMIT_RAW="${GUARDEX_FINISH_PARENT_GITLINK_AUTO_COMMIT:-true}"
18
19
 
19
20
  run_guardex_cli() {
20
21
  if [[ -n "$CLI_ENTRY" ]]; then
@@ -67,6 +68,7 @@ CLEANUP_AFTER_MERGE="$(normalize_bool "$CLEANUP_AFTER_MERGE_RAW" "0")"
67
68
  WAIT_FOR_MERGE="$(normalize_bool "$WAIT_FOR_MERGE_RAW" "0")"
68
69
  WAIT_TIMEOUT_SECONDS="$(normalize_int "$WAIT_TIMEOUT_SECONDS_RAW" "1800" "30")"
69
70
  WAIT_POLL_SECONDS="$(normalize_int "$WAIT_POLL_SECONDS_RAW" "10" "0")"
71
+ PARENT_GITLINK_AUTO_COMMIT="$(normalize_bool "$PARENT_GITLINK_AUTO_COMMIT_RAW" "1")"
70
72
 
71
73
  while [[ $# -gt 0 ]]; do
72
74
  case "$1" in
@@ -117,6 +119,14 @@ while [[ $# -gt 0 ]]; do
117
119
  WAIT_POLL_SECONDS="$(normalize_int "${2:-}" "10" "0")"
118
120
  shift 2
119
121
  ;;
122
+ --parent-gitlink-commit)
123
+ PARENT_GITLINK_AUTO_COMMIT=1
124
+ shift
125
+ ;;
126
+ --no-parent-gitlink-commit)
127
+ PARENT_GITLINK_AUTO_COMMIT=0
128
+ shift
129
+ ;;
120
130
  --mode)
121
131
  MERGE_MODE="${2:-auto}"
122
132
  shift 2
@@ -131,7 +141,7 @@ while [[ $# -gt 0 ]]; do
131
141
  ;;
132
142
  *)
133
143
  echo "[agent-branch-finish] Unknown argument: $1" >&2
134
- echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--cleanup|--no-cleanup] [--wait-for-merge|--no-wait-for-merge] [--wait-timeout-seconds <n>] [--wait-poll-seconds <n>] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only]" >&2
144
+ echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--cleanup|--no-cleanup] [--wait-for-merge|--no-wait-for-merge] [--wait-timeout-seconds <n>] [--wait-poll-seconds <n>] [--parent-gitlink-commit|--no-parent-gitlink-commit] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only]" >&2
135
145
  exit 1
136
146
  ;;
137
147
  esac
@@ -173,6 +183,8 @@ if [[ -z "$stored_worktree_root_rel" ]]; then
173
183
  stored_worktree_root_rel=".omx/agent-worktrees"
174
184
  fi
175
185
  agent_worktree_root="${repo_common_root}/${stored_worktree_root_rel}"
186
+ runtime_state_root_rel="$(dirname "$stored_worktree_root_rel")"
187
+ temp_worktree_root="${repo_common_root}/${runtime_state_root_rel}/.tmp-worktrees"
176
188
 
177
189
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
178
190
  echo "[agent-branch-finish] --base requires a non-empty branch name." >&2
@@ -218,7 +230,7 @@ fi
218
230
 
219
231
  get_worktree_for_branch() {
220
232
  local branch="$1"
221
- git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${agent_worktree_root}/__source-probe-" '
233
+ git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${temp_worktree_root}/__source-probe-" '
222
234
  $1 == "worktree" { wt = $2 }
223
235
  $1 == "branch" && $2 == target {
224
236
  if (index(wt, probe_prefix) != 1) {
@@ -242,7 +254,7 @@ remove_stale_source_probe_worktrees() {
242
254
  git -C "$stale_probe" merge --abort >/dev/null 2>&1 || true
243
255
  git -C "$repo_root" worktree remove "$stale_probe" --force >/dev/null 2>&1 || true
244
256
  done < <(
245
- git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${agent_worktree_root}/__source-probe-" '
257
+ git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${temp_worktree_root}/__source-probe-" '
246
258
  $1 == "worktree" { wt = $2 }
247
259
  $1 == "branch" && $2 == target {
248
260
  if (index(wt, probe_prefix) == 1) {
@@ -264,11 +276,15 @@ source_worktree="$(get_worktree_for_branch "$SOURCE_BRANCH")"
264
276
  created_source_probe=0
265
277
  source_probe_path=""
266
278
  integration_worktree=""
279
+ integration_branch=""
267
280
 
268
281
  cleanup() {
269
282
  if [[ -n "$integration_worktree" && -d "$integration_worktree" ]]; then
270
283
  git -C "$repo_root" worktree remove "$integration_worktree" --force >/dev/null 2>&1 || true
271
284
  fi
285
+ if [[ -n "${integration_branch:-}" ]]; then
286
+ git -C "$repo_root" branch -D "$integration_branch" >/dev/null 2>&1 || true
287
+ fi
272
288
  if [[ "$created_source_probe" -eq 1 && -n "$source_probe_path" && -d "$source_probe_path" ]]; then
273
289
  # Abort any in-progress git op so `worktree remove --force` succeeds on conflict-stuck probes.
274
290
  git -C "$source_probe_path" rebase --abort >/dev/null 2>&1 || true
@@ -279,7 +295,7 @@ cleanup() {
279
295
  trap cleanup EXIT
280
296
 
281
297
  if [[ -z "$source_worktree" ]]; then
282
- source_probe_path="${agent_worktree_root}/__source-probe-${SOURCE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
298
+ source_probe_path="${temp_worktree_root}/__source-probe-${SOURCE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
283
299
  mkdir -p "$(dirname "$source_probe_path")"
284
300
  git -C "$repo_root" worktree add "$source_probe_path" "$SOURCE_BRANCH" >/dev/null
285
301
  source_worktree="$source_probe_path"
@@ -343,7 +359,7 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
343
359
  fi
344
360
 
345
361
  integration_stamp="$(date +%Y%m%d-%H%M%S)"
346
- integration_worktree_base="${agent_worktree_root}/__integrate-${BASE_BRANCH//\//__}-${integration_stamp}"
362
+ integration_worktree_base="${temp_worktree_root}/__integrate-${BASE_BRANCH//\//__}-${integration_stamp}"
347
363
  integration_branch_base="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
348
364
  integration_worktree="$integration_worktree_base"
349
365
  integration_branch="$integration_branch_base"
@@ -493,6 +509,90 @@ read_merged_pr_for_head() {
493
509
  return 0
494
510
  }
495
511
 
512
+ maybe_auto_commit_parent_gitlink() {
513
+ local base_wt="${1:-}"
514
+ local base_wt_real=""
515
+ local super_root_raw=""
516
+ local super_root=""
517
+ local subrepo_rel=""
518
+ local gitlink_mode=""
519
+ local gitlink_index_sha=""
520
+ local gitlink_parent_head_sha=""
521
+ local subrepo_head_sha=""
522
+ local update_index_output=""
523
+ local commit_output=""
524
+ local commit_message=""
525
+
526
+ if [[ "$PARENT_GITLINK_AUTO_COMMIT" -ne 1 || "$PUSH_ENABLED" -ne 1 ]]; then
527
+ return 0
528
+ fi
529
+ if [[ -z "$base_wt" ]]; then
530
+ return 0
531
+ fi
532
+ if ! base_wt_real="$(cd "$base_wt" && pwd -P 2>/dev/null)"; then
533
+ return 0
534
+ fi
535
+ if [[ "$base_wt_real" != "$repo_common_root" ]]; then
536
+ return 0
537
+ fi
538
+ if ! is_clean_worktree "$repo_common_root"; then
539
+ echo "[agent-branch-finish] Parent gitlink auto-commit skipped; nested base worktree is dirty: ${repo_common_root}" >&2
540
+ return 0
541
+ fi
542
+
543
+ super_root_raw="$(git -C "$repo_common_root" rev-parse --show-superproject-working-tree 2>/dev/null || true)"
544
+ if [[ -z "$super_root_raw" ]]; then
545
+ return 0
546
+ fi
547
+ if ! super_root="$(cd "$super_root_raw" && pwd -P 2>/dev/null)"; then
548
+ return 0
549
+ fi
550
+
551
+ case "$repo_common_root" in
552
+ "$super_root"/*) subrepo_rel="${repo_common_root#"$super_root"/}" ;;
553
+ *) return 0 ;;
554
+ esac
555
+ if [[ -z "$subrepo_rel" || "$subrepo_rel" == "$repo_common_root" ]]; then
556
+ return 0
557
+ fi
558
+
559
+ gitlink_mode="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $1 }')"
560
+ if [[ "$gitlink_mode" != "160000" ]]; then
561
+ return 0
562
+ fi
563
+ gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')"
564
+ gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')"
565
+ subrepo_head_sha="$(git -C "$repo_common_root" rev-parse HEAD 2>/dev/null || true)"
566
+ if [[ -z "$subrepo_head_sha" ]]; then
567
+ return 0
568
+ fi
569
+ if [[ -n "$gitlink_index_sha" && "$gitlink_index_sha" == "$gitlink_parent_head_sha" && "$gitlink_index_sha" == "$subrepo_head_sha" ]]; then
570
+ return 0
571
+ fi
572
+
573
+ if [[ "$gitlink_index_sha" != "$subrepo_head_sha" ]]; then
574
+ if ! update_index_output="$(git -C "$super_root" update-index --cacheinfo 160000 "$subrepo_head_sha" "$subrepo_rel" 2>&1)"; then
575
+ echo "[agent-branch-finish] Warning: parent gitlink staging failed for ${subrepo_rel} in ${super_root}." >&2
576
+ [[ -n "$update_index_output" ]] && echo "$update_index_output" >&2
577
+ return 0
578
+ fi
579
+ gitlink_index_sha="$(git -C "$super_root" ls-files -s -- "$subrepo_rel" | awk 'NR == 1 { print $2 }')"
580
+ fi
581
+ gitlink_parent_head_sha="$(git -C "$super_root" ls-tree HEAD -- "$subrepo_rel" | awk 'NR == 1 { print $3 }')"
582
+ if [[ "$gitlink_index_sha" == "$gitlink_parent_head_sha" ]]; then
583
+ return 0
584
+ fi
585
+
586
+ commit_message="Update ${subrepo_rel} subrepo pointer"
587
+ if ! commit_output="$(git -C "$super_root" commit -m "$commit_message" -- "$subrepo_rel" 2>&1)"; then
588
+ echo "[agent-branch-finish] Warning: parent gitlink auto-commit failed in ${super_root}." >&2
589
+ [[ -n "$commit_output" ]] && echo "$commit_output" >&2
590
+ return 0
591
+ fi
592
+
593
+ echo "[agent-branch-finish] Parent gitlink auto-committed '${subrepo_rel}' in ${super_root}."
594
+ }
595
+
496
596
  wait_for_pr_merge() {
497
597
  local deadline
498
598
  deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
@@ -661,6 +761,7 @@ base_worktree="$(get_worktree_for_branch "$BASE_BRANCH")"
661
761
  if [[ -n "$base_worktree" ]] && is_clean_worktree "$base_worktree" && [[ "$PUSH_ENABLED" -eq 1 ]]; then
662
762
  git -C "$base_worktree" pull --ff-only origin "$BASE_BRANCH" >/dev/null 2>&1 || true
663
763
  fi
764
+ maybe_auto_commit_parent_gitlink "$base_worktree"
664
765
 
665
766
  if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
666
767
  if [[ "$source_worktree" == "$repo_root" ]]; then
@@ -20,7 +20,9 @@ PR_MERGED_LOOKUP_LOADED=0
20
20
  declare -A MERGED_PR_BRANCHES=()
21
21
  WORKTREE_ROOT_RELS=(
22
22
  ".omx/agent-worktrees"
23
+ ".omx/.tmp-worktrees"
23
24
  ".omc/agent-worktrees"
25
+ ".omc/.tmp-worktrees"
24
26
  )
25
27
 
26
28
  if [[ -n "$BASE_BRANCH" ]]; then
@@ -90,9 +92,15 @@ repo_common_dir="$(cd "$repo_common_dir" && pwd -P)"
90
92
  resolve_worktree_root_rel_for_entry() {
91
93
  local entry="$1"
92
94
  case "$entry" in
95
+ */.omc/.tmp-worktrees/*)
96
+ printf '%s' '.omc/.tmp-worktrees'
97
+ ;;
93
98
  */.omc/agent-worktrees/*)
94
99
  printf '%s' '.omc/agent-worktrees'
95
100
  ;;
101
+ */.omx/.tmp-worktrees/*)
102
+ printf '%s' '.omx/.tmp-worktrees'
103
+ ;;
96
104
  *)
97
105
  printf '%s' '.omx/agent-worktrees'
98
106
  ;;
@@ -538,6 +546,19 @@ if [[ "$DELETE_BRANCHES" -eq 1 ]]; then
538
546
  if branch_has_worktree "$branch"; then
539
547
  continue
540
548
  fi
549
+ if [[ "$branch" == __agent_integrate_* || "$branch" == __source-probe-* ]]; then
550
+ if ! branch_idle_gate "$branch" "" "temporary-worktree"; then
551
+ continue
552
+ fi
553
+ if run_cmd git -C "$repo_root" branch -D "$branch" >/dev/null 2>&1; then
554
+ removed_branches=$((removed_branches + 1))
555
+ echo "[agent-worktree-prune] Deleted stale temporary branch: ${branch}"
556
+ fi
557
+ continue
558
+ fi
559
+ if [[ "$branch" != agent/* ]]; then
560
+ continue
561
+ fi
541
562
  if ! branch_idle_gate "$branch" "" "stale-merged-branch"; then
542
563
  continue
543
564
  fi
@@ -566,7 +587,7 @@ if [[ "$DELETE_BRANCHES" -eq 1 ]]; then
566
587
  fi
567
588
  fi
568
589
  fi
569
- done < <(git -C "$repo_root" for-each-ref --format='%(refname:short)' refs/heads/agent)
590
+ done < <(git -C "$repo_root" for-each-ref --format='%(refname:short)' refs/heads)
570
591
  fi
571
592
 
572
593
  run_cmd git -C "$repo_root" worktree prune