@imdeadpool/guardex 7.0.10 → 7.0.12

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
@@ -9,6 +9,9 @@
9
9
  > [!WARNING]
10
10
  > Not affiliated with OpenAI, Anthropic, or Codex. Not an official tool.
11
11
 
12
+ > [!IMPORTANT]
13
+ > GitGuardex is still being tested in real multi-agent repos. If something feels rough or broken, especially around cleanup, finish, merge, or recovery flows, sorry. We need to test those paths under real load first, and we'll patch issues as we find them.
14
+
12
15
  ---
13
16
 
14
17
  ## The problem
@@ -17,22 +20,24 @@ I was running ~30 Codex agents in parallel and hit a wall: they kept working on
17
20
 
18
21
  GitGuardex exists to stop that loop. Every agent gets its own worktree, claims the files it's touching, and can't clobber files another agent has claimed. Your local branch stays clean; agents stay in their lanes.
19
22
 
20
- ![Multi-agent dashboard example](https://raw.githubusercontent.com/recodeee/gitguardex/main/docs/images/dashboard-multi-agent.png)
21
-
22
23
  ```mermaid
23
24
  flowchart LR
24
- A[Agent A edits shared files] --> S[Same target surface]
25
- B[Agent B edits shared files] --> S
26
- C[Agent C edits shared files] --> S
27
- D[Agent D edits shared files] --> S
28
- E[Agent E edits shared files] --> S
29
- S --> F[Conflict / overwrite churn]
30
- F --> G[Deleted or lost code]
31
- G --> H[Rework and confusion]
32
- H --> I[Regression risk grows]
33
- I --> F
25
+ A[Agent A adds assertions in a shared test] --> S[Several agents touch the same files]
26
+ B[Agent B rewrites the same test flow] --> S
27
+ C[Agent C updates the shared helper] --> S
28
+ D[Agent D deletes lines Agent A just added] --> S
29
+ E[Agent E saves an older snapshot of the file] --> S
30
+ S --> F[One agent overwrites another agent's edits]
31
+ F --> G[Another agent deletes code the others just added]
32
+ G --> H[Lost work, rework, and review confusion]
33
+ H --> I[Regression risk and flaky fixes grow]
34
+ I --> S
34
35
  ```
35
36
 
37
+ ![Multi-agent dashboard example](https://raw.githubusercontent.com/recodeee/gitguardex/main/docs/images/dashboard-multi-agent.png)
38
+
39
+ Coming soon: [recodee.com](https://recodee.com) — live account health, usage, routing, and capacity in one place.
40
+
36
41
  ---
37
42
 
38
43
  ## What it does
@@ -41,7 +46,7 @@ flowchart LR
41
46
  - **Explicit file lock claiming** — an agent declares which files it's editing before it edits them.
42
47
  - **Deletion guard** — claimed files can't be removed by another agent.
43
48
  - **Protected-base safety** — `main`, `dev`, `master` are blocked by default; agents must go through PRs.
44
- - **Auto-merges agent configs into every worktree** — `oh-my-codex`, `oh-my-claude`, caveman mode, and OpenSpec all get applied automatically so every spawned agent starts tuned, not bare.
49
+ - **Auto-merges agent configs into every worktree** — `oh-my-codex`, `oh-my-claudecode`, caveman mode, and OpenSpec all get applied automatically so every spawned agent starts tuned, not bare.
45
50
  - **Repair/doctor flow** — when drift happens (and it will), `gx doctor` gets you back to a clean state.
46
51
  - **Auto-finish** — when Codex exits a session, Guardex commits sandbox changes, syncs against the base, retries once if the base moved, and opens a PR.
47
52
 
@@ -59,6 +64,16 @@ That's it. Setup installs hooks, scripts, templates, and scaffolds OpenSpec/cave
59
64
 
60
65
  ---
61
66
 
67
+ ## What `gx` shows first
68
+
69
+ Before you branch, repair, or start agents, run plain `gx`. It gives you a one-screen status view for the CLI, global helpers, repo safety service, current repo path, and active branch.
70
+
71
+ ![GitGuardex terminal status output](https://raw.githubusercontent.com/recodeee/gitguardex/main/docs/images/workflow-gx-terminal-status.svg)
72
+
73
+ Use `gx setup` the first time you wire GitGuardex into a repo. It bootstraps the managed hooks, scripts, templates, and optional workspace/OpenSpec wiring. If the repo drifts later, use `gx doctor` as the repair path: it reapplies the managed safety files, verifies the setup, and on protected `main` it auto-sandboxes the repair so your visible base branch stays clean.
74
+
75
+ ---
76
+
62
77
  ## Daily workflow
63
78
 
64
79
  Per new agent task:
@@ -107,7 +122,7 @@ gx finish --all
107
122
 
108
123
  This is the real Source Control shape Guardex is aiming for: isolated agent branches, clear OpenSpec artifacts, and no pile-up on one shared checkout.
109
124
 
110
- ![Exact VS Code Source Control workflow screenshot](https://raw.githubusercontent.com/recodeee/gitguardex/main/docs/images/workflow-vscode-source-control-exact.png)
125
+ ![Guarded VS Code Source Control example](https://raw.githubusercontent.com/recodeee/gitguardex/main/docs/images/workflow-source-control-grouped.png)
111
126
 
112
127
  ---
113
128
 
@@ -222,7 +237,7 @@ A few things worth knowing up front:
222
237
 
223
238
  - Running `gx` with no command opens the status/health view.
224
239
  - `gx init` is just an alias for `gx setup`.
225
- - Setup/doctor can install missing global OMX, OpenSpec, and codex-auth — but only with explicit Y/N confirmation.
240
+ - Setup/doctor can install missing companion tooling (OMC runtime, OpenSpec, cavemem, codex-auth, caveman, cavekit) — but only with explicit Y/N confirmation.
226
241
  - Direct commits/pushes to protected branches are **blocked** by default. Agents must use the `agent/*` + PR flow.
227
242
  - **Exception:** VS Code Source Control commits are allowed on protected branches that exist only locally (no upstream, no remote branch).
228
243
  - On protected `main`, `gx doctor` auto-runs in a sandbox agent branch/worktree so it can't touch your real main.
@@ -240,21 +255,84 @@ git config multiagent.allowVscodeProtectedBranchWrites true
240
255
 
241
256
  ## Companion tools
242
257
 
243
- GitGuardex is designed to work alongside these. All optional — but if you're running many agents, you probably want them.
258
+ GitGuardex is designed to work alongside these. All optional — but if you're running many agents, you probably want them. `gx status` reports the machine-detectable companion helpers, including local `caveman` / `cavekit` installs when their home-directory footprints are present.
244
259
 
245
- ### GitHub CLI (`gh`)
260
+ ```text
261
+ ● oh-my-codex: active
262
+ ● oh-my-claude-sisyphus: active
263
+ ● @fission-ai/openspec: active
264
+ ● cavemem: active
265
+ ● cavekit: active
266
+ ● caveman: active
267
+ ● @imdeadpool/codex-account-switcher: active
268
+ ● gh: active
269
+ ```
246
270
 
247
- Required for PR/merge automation. `agent-branch-finish.sh` and `codex-agent.sh` auto-finish both depend on it.
271
+ ### oh-my-codex Codex config + skills framework
272
+
273
+ Loads skills, slash commands, and session defaults into Codex. Guardex merges `oh-my-codex` into every agent worktree automatically, so every spawned agent starts with the same tuned config instead of vanilla Codex.
248
274
 
249
275
  ```sh
250
- # https://cli.github.com/
251
- gh --version
252
- gh auth status
276
+ npm i -g oh-my-codex
277
+ ```
278
+
279
+ Repo: <https://github.com/Yeachan-Heo/oh-my-codex>
280
+
281
+ ### oh-my-claudecode — Claude Code equivalent
282
+
283
+ Claude-side mirror of oh-my-codex. Same idea: skills, commands, and defaults loaded into every Claude Code session. Guardex merges it into worktrees alongside oh-my-codex so mixed Codex + Claude agent fleets behave consistently. For the npm CLI/runtime path, the published package name is `oh-my-claude-sisyphus`.
284
+
285
+ ```sh
286
+ npm i -g oh-my-claude-sisyphus@latest
287
+ ```
288
+
289
+ Repo: <https://github.com/Yeachan-Heo/oh-my-claudecode>
290
+
291
+ ### Caveman — output compression for long agent runs
292
+
293
+ 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.
294
+
295
+ ```sh
296
+ npx skills add JuliusBrussee/caveman
253
297
  ```
254
298
 
299
+ Repo: <https://github.com/JuliusBrussee/caveman>
300
+
301
+ ### Cavemem — local persistent memory for agents
302
+
303
+ Cross-agent memory with local SQLite + MCP. Helpful when you want Codex or Claude sessions to retain compressed history across runs. `gx setup` can install the CLI; you still run the IDE wiring once per machine.
304
+
305
+ ```sh
306
+ npm install -g cavemem
307
+ cavemem install --ide codex
308
+ cavemem status
309
+ ```
310
+
311
+ Repo: <https://github.com/JuliusBrussee/cavemem>
312
+
313
+ ### Cavekit — spec-driven build loop
314
+
315
+ Spec-driven workflow layer for building from durable specs with explicit build/check commands. The current install path also brings in its `spec`, `build`, `check`, `caveman`, and `backprop` skills.
316
+
317
+ ```sh
318
+ npx skills add JuliusBrussee/cavekit
319
+ ```
320
+
321
+ Repo: <https://github.com/JuliusBrussee/cavekit>
322
+
323
+ ### OpenSpec — spec-driven workflows
324
+
325
+ Structured plan/change/apply/archive flow for agents. Prevents them from drifting off-task on long jobs. Full guide: [`docs/openspec-getting-started.md`](./docs/openspec-getting-started.md).
326
+
327
+ ```sh
328
+ npm i -g @fission-ai/openspec
329
+ ```
330
+
331
+ Repo: <https://github.com/Fission-AI/OpenSpec>
332
+
255
333
  ### codex-auth — multi-account switcher
256
334
 
257
- For multi-identity Codex workflows. I built this because switching accounts manually for 30 agents was impossible.
335
+ For multi-identity Codex workflows. I built this because switching accounts manually for 30 agents was impossible. Auto-registers accounts to a dashboard on `codex login` so you can see every account and switch with one command.
258
336
 
259
337
  ```sh
260
338
  npm i -g @imdeadpool/codex-account-switcher
@@ -267,6 +345,16 @@ codex-auth current
267
345
 
268
346
  Repo: [recodeecom/codex-account-switcher-cli](https://github.com/recodeecom/codex-account-switcher-cli)
269
347
 
348
+ ### GitHub CLI (`gh`)
349
+
350
+ Required for PR/merge automation. `agent-branch-finish.sh` and `codex-agent.sh` auto-finish both depend on it.
351
+
352
+ ```sh
353
+ # https://cli.github.com/
354
+ gh --version
355
+ gh auth status
356
+ ```
357
+
270
358
  ### Pull app — fork auto-sync
271
359
 
272
360
  Guardex installs a starter config at `.github/pull.yml.example`.
@@ -283,7 +371,7 @@ Validate: `https://pull.git.ci/check/<owner>/<repo>`
283
371
 
284
372
  Install: <https://github.com/apps/cr-gpt>
285
373
 
286
- `gx setup` installs `.github/workflows/cr.yml`. Then add `OPENAI_API_KEY` under `Settings → Secrets and variables → Actions → Variables`. After that, new and updated PRs get reviewed automatically.
374
+ `gx setup` installs `.github/workflows/cr.yml`. Add `OPENAI_API_KEY` under `Settings → Secrets and variables → Actions → Secrets`. After that, new and updated PRs get reviewed automatically.
287
375
 
288
376
  ---
289
377
 
@@ -402,6 +490,16 @@ npm pack --dry-run
402
490
  <details>
403
491
  <summary><strong>v7.x</strong></summary>
404
492
 
493
+ ### v7.0.12
494
+ - Fixed the self-update handoff after `gx` installs a newer global package. When the on-disk install advances, GitGuardex now restarts into the installed CLI instead of continuing in the old process and printing the stale in-memory version.
495
+ - This removes the confusing `Updated to latest published version` followed by `CLI: ...7.0.10` mismatch that happened when `7.0.11` finished installing during the same `gx` invocation.
496
+ - Bumped `@imdeadpool/guardex` from `7.0.11` → `7.0.12`.
497
+
498
+ ### v7.0.11
499
+ - Fixed the npm release workflow trigger so publishes run from `release.published` or explicit manual dispatch, instead of double-firing on both the tag push and the release event.
500
+ - This keeps the GitHub `npm` environment from collecting duplicate cancelled deploy cards for the same version and leaves one canonical release deployment to monitor.
501
+ - Bumped `@imdeadpool/guardex` from `7.0.10` → `7.0.11` so the next release can publish cleanly after `7.0.10` was already taken on npm.
502
+
405
503
  ### v7.0.10
406
504
  - Primary user-facing long name is now **GitGuardex**. CLI/help presents `gitguardex` as the long-form command; `gx` stays the preferred short alias; `guardex` remains as legacy compatibility.
407
505
  - Installed Codex/Claude startup files now use `gitguardex` paths: `.codex/skills/gitguardex/SKILL.md` and `.claude/commands/gitguardex.md`.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('node:fs');
4
+ const os = require('node:os');
4
5
  const path = require('node:path');
5
6
  const cp = require('node:child_process');
6
7
 
@@ -11,12 +12,37 @@ const TOOL_NAME = 'gitguardex';
11
12
  const SHORT_TOOL_NAME = 'gx';
12
13
  const LEGACY_NAMES = ['guardex', 'multiagent-safety'];
13
14
  const OPENSPEC_PACKAGE = '@fission-ai/openspec';
15
+ const OMC_PACKAGE = 'oh-my-claude-sisyphus';
16
+ const CAVEMEM_PACKAGE = 'cavemem';
17
+ const NPX_BIN = process.env.GUARDEX_NPX_BIN || 'npx';
18
+ const GUARDEX_HOME_DIR = path.resolve(process.env.GUARDEX_HOME_DIR || os.homedir());
14
19
  const GLOBAL_TOOLCHAIN_PACKAGES = [
15
20
  'oh-my-codex',
16
- 'oh-my-claude',
21
+ OMC_PACKAGE,
17
22
  OPENSPEC_PACKAGE,
23
+ CAVEMEM_PACKAGE,
18
24
  '@imdeadpool/codex-account-switcher',
19
25
  ];
26
+ const OPTIONAL_LOCAL_COMPANION_TOOLS = [
27
+ {
28
+ name: 'cavekit',
29
+ candidatePaths: [
30
+ '.cavekit/plugin.json',
31
+ '.codex/local-marketplaces/cavekit/.agents/plugins/marketplace.json',
32
+ ],
33
+ installCommand: `${NPX_BIN} skills add JuliusBrussee/cavekit`,
34
+ installArgs: ['skills', 'add', 'JuliusBrussee/cavekit'],
35
+ },
36
+ {
37
+ name: 'caveman',
38
+ candidatePaths: [
39
+ '.config/caveman/config.json',
40
+ '.cavekit/skills/caveman/SKILL.md',
41
+ ],
42
+ installCommand: `${NPX_BIN} skills add JuliusBrussee/caveman`,
43
+ installArgs: ['skills', 'add', 'JuliusBrussee/caveman'],
44
+ },
45
+ ];
20
46
  const GH_BIN = process.env.GUARDEX_GH_BIN || 'gh';
21
47
  const REQUIRED_SYSTEM_TOOLS = [
22
48
  {
@@ -3390,9 +3416,15 @@ function maybeSelfUpdateBeforeStatus() {
3390
3416
  }
3391
3417
 
3392
3418
  console.log(`[${TOOL_NAME}] ✅ Updated to latest published version.`);
3419
+ restartIntoUpdatedGuardex(check.latest);
3393
3420
  }
3394
3421
 
3395
3422
  function readInstalledGuardexVersion() {
3423
+ const installInfo = readInstalledGuardexInstallInfo();
3424
+ return installInfo ? installInfo.version : null;
3425
+ }
3426
+
3427
+ function readInstalledGuardexInstallInfo() {
3396
3428
  // Resolves the globally-installed package's on-disk version so we can
3397
3429
  // verify npm actually wrote new bytes. Uses `npm root -g` to locate the
3398
3430
  // global install root so we don't accidentally read the running source
@@ -3414,7 +3446,24 @@ function readInstalledGuardexVersion() {
3414
3446
  }
3415
3447
  const parsed = JSON.parse(fs.readFileSync(installedPkgPath, 'utf8'));
3416
3448
  if (parsed && typeof parsed.version === 'string') {
3417
- return parsed.version;
3449
+ let binRelative = null;
3450
+ if (typeof parsed.bin === 'string') {
3451
+ binRelative = parsed.bin;
3452
+ } else if (parsed.bin && typeof parsed.bin === 'object') {
3453
+ const invokedName = path.basename(process.argv[1] || '');
3454
+ binRelative =
3455
+ parsed.bin[invokedName] ||
3456
+ parsed.bin[SHORT_TOOL_NAME] ||
3457
+ Object.values(parsed.bin).find((value) => typeof value === 'string') ||
3458
+ null;
3459
+ }
3460
+ const packageRoot = path.dirname(installedPkgPath);
3461
+ const binPath = binRelative ? path.join(packageRoot, binRelative) : null;
3462
+ return {
3463
+ version: parsed.version,
3464
+ packageRoot,
3465
+ binPath,
3466
+ };
3418
3467
  }
3419
3468
  } catch (error) {
3420
3469
  return null;
@@ -3422,6 +3471,38 @@ function readInstalledGuardexVersion() {
3422
3471
  return null;
3423
3472
  }
3424
3473
 
3474
+ function restartIntoUpdatedGuardex(expectedVersion) {
3475
+ const installInfo = readInstalledGuardexInstallInfo();
3476
+ if (!installInfo || installInfo.version !== expectedVersion || installInfo.version === packageJson.version) {
3477
+ return;
3478
+ }
3479
+ if (!installInfo.binPath || !fs.existsSync(installInfo.binPath)) {
3480
+ console.log(`[${TOOL_NAME}] Restart required to use ${installInfo.version}. Rerun ${SHORT_TOOL_NAME}.`);
3481
+ return;
3482
+ }
3483
+
3484
+ console.log(`[${TOOL_NAME}] Restarting into ${installInfo.version}…`);
3485
+ const restartResult = cp.spawnSync(
3486
+ process.execPath,
3487
+ [installInfo.binPath, ...process.argv.slice(2)],
3488
+ {
3489
+ cwd: process.cwd(),
3490
+ env: {
3491
+ ...process.env,
3492
+ GUARDEX_SKIP_UPDATE_CHECK: '1',
3493
+ },
3494
+ stdio: 'inherit',
3495
+ },
3496
+ );
3497
+ if (restartResult.error) {
3498
+ console.log(
3499
+ `[${TOOL_NAME}] Restart into ${installInfo.version} failed. Rerun ${SHORT_TOOL_NAME}.`,
3500
+ );
3501
+ return;
3502
+ }
3503
+ process.exit(restartResult.status == null ? 0 : restartResult.status);
3504
+ }
3505
+
3425
3506
  function checkForOpenSpecPackageUpdate() {
3426
3507
  if (envFlagEnabled('GUARDEX_SKIP_OPENSPEC_UPDATE_CHECK')) {
3427
3508
  return { checked: false, reason: 'disabled' };
@@ -3613,15 +3694,44 @@ function detectRequiredSystemTools() {
3613
3694
  return services;
3614
3695
  }
3615
3696
 
3697
+ function detectOptionalLocalCompanionTools() {
3698
+ return OPTIONAL_LOCAL_COMPANION_TOOLS.map((tool) => {
3699
+ const detectedPath = tool.candidatePaths
3700
+ .map((relativePath) => path.join(GUARDEX_HOME_DIR, relativePath))
3701
+ .find((candidatePath) => fs.existsSync(candidatePath));
3702
+ return {
3703
+ name: tool.name,
3704
+ displayName: tool.displayName || tool.name,
3705
+ installCommand: tool.installCommand,
3706
+ installArgs: [...tool.installArgs],
3707
+ status: detectedPath ? 'active' : 'inactive',
3708
+ detectedPath: detectedPath || null,
3709
+ };
3710
+ });
3711
+ }
3712
+
3713
+ function describeCompanionInstallCommands(missingPackages, missingLocalTools) {
3714
+ const commands = [];
3715
+ if (missingPackages.length > 0) {
3716
+ commands.push(`${NPM_BIN} i -g ${missingPackages.join(' ')}`);
3717
+ }
3718
+ for (const tool of missingLocalTools) {
3719
+ commands.push(tool.installCommand);
3720
+ }
3721
+ return commands;
3722
+ }
3723
+
3616
3724
  function askGlobalInstallForMissing(options, missingPackages) {
3617
3725
  const approval = resolveGlobalInstallApproval(options);
3618
3726
  if (!approval.approved) {
3619
3727
  return approval;
3620
3728
  }
3621
3729
 
3730
+ const missingLocalTools = detectOptionalLocalCompanionTools().filter((tool) => tool.status !== 'active');
3731
+ const installCommands = describeCompanionInstallCommands(missingPackages, missingLocalTools);
3622
3732
  if (approval.source === 'prompt') {
3623
3733
  const approved = promptYesNoStrict(
3624
- `Install missing global tools now? (npm i -g ${missingPackages.join(' ')})`,
3734
+ `Install missing companion tools now? (${installCommands.join(' && ')})`,
3625
3735
  );
3626
3736
  return { approved, source: 'prompt' };
3627
3737
  }
@@ -3635,36 +3745,61 @@ function installGlobalToolchain(options) {
3635
3745
  }
3636
3746
 
3637
3747
  const detection = detectGlobalToolchainPackages();
3748
+ const localCompanionTools = detectOptionalLocalCompanionTools();
3638
3749
  if (!detection.ok) {
3639
3750
  console.log(`[${TOOL_NAME}] ⚠️ Could not detect global packages: ${detection.error}`);
3640
3751
  } else {
3641
3752
  if (detection.installed.length > 0) {
3642
3753
  console.log(`[${TOOL_NAME}] Already installed globally: ${detection.installed.join(', ')}`);
3643
3754
  }
3644
- if (detection.missing.length === 0) {
3755
+ const installedLocalTools = localCompanionTools
3756
+ .filter((tool) => tool.status === 'active')
3757
+ .map((tool) => tool.name);
3758
+ if (installedLocalTools.length > 0) {
3759
+ console.log(`[${TOOL_NAME}] Already installed locally: ${installedLocalTools.join(', ')}`);
3760
+ }
3761
+ if (detection.missing.length === 0 && localCompanionTools.every((tool) => tool.status === 'active')) {
3645
3762
  return { status: 'already-installed' };
3646
3763
  }
3647
3764
  }
3648
3765
 
3649
3766
  const missingPackages = detection.ok ? detection.missing : [...GLOBAL_TOOLCHAIN_PACKAGES];
3767
+ const missingLocalTools = localCompanionTools.filter((tool) => tool.status !== 'active');
3650
3768
  const approval = askGlobalInstallForMissing(options, missingPackages);
3651
3769
  if (!approval.approved) {
3652
3770
  return { status: 'skipped', reason: approval.source };
3653
3771
  }
3654
3772
 
3655
- console.log(
3656
- `[${TOOL_NAME}] Installing global toolchain: npm i -g ${missingPackages.join(' ')}`,
3657
- );
3658
- const result = run(NPM_BIN, ['i', '-g', ...missingPackages], { stdio: 'inherit' });
3659
- if (result.status !== 0) {
3660
- const stderr = (result.stderr || '').trim();
3661
- return {
3662
- status: 'failed',
3663
- reason: stderr || 'npm global install failed',
3664
- };
3773
+ const installed = [];
3774
+ if (missingPackages.length > 0) {
3775
+ console.log(
3776
+ `[${TOOL_NAME}] Installing global toolchain: npm i -g ${missingPackages.join(' ')}`,
3777
+ );
3778
+ const result = run(NPM_BIN, ['i', '-g', ...missingPackages], { stdio: 'inherit' });
3779
+ if (result.status !== 0) {
3780
+ const stderr = (result.stderr || '').trim();
3781
+ return {
3782
+ status: 'failed',
3783
+ reason: stderr || 'npm global install failed',
3784
+ };
3785
+ }
3786
+ installed.push(...missingPackages);
3665
3787
  }
3666
3788
 
3667
- return { status: 'installed', packages: missingPackages };
3789
+ for (const tool of missingLocalTools) {
3790
+ console.log(`[${TOOL_NAME}] Installing local companion tool: ${tool.installCommand}`);
3791
+ const result = run(NPX_BIN, tool.installArgs, { stdio: 'inherit' });
3792
+ if (result.status !== 0) {
3793
+ const stderr = (result.stderr || '').trim();
3794
+ return {
3795
+ status: 'failed',
3796
+ reason: stderr || `${tool.name} install failed`,
3797
+ };
3798
+ }
3799
+ installed.push(tool.name);
3800
+ }
3801
+
3802
+ return { status: 'installed', packages: installed };
3668
3803
  }
3669
3804
 
3670
3805
  function gitRefExists(repoRoot, refName) {
@@ -4015,9 +4150,15 @@ function status(rawArgs) {
4015
4150
  status: toolchain.installed.includes(pkg) ? 'active' : 'inactive',
4016
4151
  };
4017
4152
  });
4153
+ const localCompanionServices = detectOptionalLocalCompanionTools().map((tool) => ({
4154
+ name: tool.name,
4155
+ displayName: tool.displayName || tool.name,
4156
+ status: tool.status,
4157
+ }));
4018
4158
  const requiredSystemTools = detectRequiredSystemTools();
4019
4159
  const services = [
4020
4160
  ...npmServices,
4161
+ ...localCompanionServices,
4021
4162
  ...requiredSystemTools.map((tool) => ({
4022
4163
  name: tool.name,
4023
4164
  displayName: tool.displayName || tool.name,
@@ -4076,6 +4217,17 @@ function status(rawArgs) {
4076
4217
  const serviceLabel = service.displayName || service.name;
4077
4218
  console.log(` - ${statusDot(service.status)} ${serviceLabel}: ${service.status}`);
4078
4219
  }
4220
+ const inactiveOptionalCompanions = [...npmServices, ...localCompanionServices]
4221
+ .filter((service) => service.status !== 'active')
4222
+ .map((service) => service.displayName || service.name);
4223
+ if (inactiveOptionalCompanions.length > 0) {
4224
+ console.log(
4225
+ `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.join(', ')}`,
4226
+ );
4227
+ console.log(
4228
+ `[${TOOL_NAME}] Run '${SHORT_TOOL_NAME} setup' to install missing companions with an explicit Y/N prompt.`,
4229
+ );
4230
+ }
4079
4231
  const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
4080
4232
  if (missingSystemTools.length > 0) {
4081
4233
  const tools = missingSystemTools
@@ -4710,19 +4862,23 @@ function setup(rawArgs) {
4710
4862
  const globalInstallStatus = installGlobalToolchain(options);
4711
4863
  if (globalInstallStatus.status === 'installed') {
4712
4864
  console.log(
4713
- `[${TOOL_NAME}] ✅ Global tools installed (${(globalInstallStatus.packages || []).join(', ')}).`,
4865
+ `[${TOOL_NAME}] ✅ Companion tools installed (${(globalInstallStatus.packages || []).join(', ')}).`,
4714
4866
  );
4715
4867
  } else if (globalInstallStatus.status === 'already-installed') {
4716
- console.log(`[${TOOL_NAME}] ✅ OMX/OpenSpec/codex-auth npm global tools already installed. Skipping.`);
4868
+ console.log(`[${TOOL_NAME}] ✅ Companion tools already installed. Skipping.`);
4717
4869
  } else if (globalInstallStatus.status === 'failed') {
4870
+ const installCommands = describeCompanionInstallCommands(
4871
+ GLOBAL_TOOLCHAIN_PACKAGES,
4872
+ OPTIONAL_LOCAL_COMPANION_TOOLS,
4873
+ );
4718
4874
  console.log(
4719
4875
  `[${TOOL_NAME}] ⚠️ Global install failed: ${globalInstallStatus.reason}\n` +
4720
4876
  `[${TOOL_NAME}] Continue with local safety setup. You can retry later with:\n` +
4721
- ` ${NPM_BIN} i -g ${GLOBAL_TOOLCHAIN_PACKAGES.join(' ')}`,
4877
+ installCommands.map((command) => ` ${command}`).join('\n'),
4722
4878
  );
4723
4879
  } else if (globalInstallStatus.status === 'skipped' && globalInstallStatus.reason === 'non-interactive-default') {
4724
4880
  console.log(
4725
- `[${TOOL_NAME}] Skipping global installs (non-interactive mode). ` +
4881
+ `[${TOOL_NAME}] Skipping companion installs (non-interactive mode). ` +
4726
4882
  `Use --yes-global-install to force or run interactively for Y/N prompt.`,
4727
4883
  );
4728
4884
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.10",
3
+ "version": "7.0.12",
4
4
  "description": "GitGuardex: hardened multi-agent git guardrails for parallel agent work.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,