@imdeadpool/guardex 7.0.11 → 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
@@ -20,24 +20,24 @@ I was running ~30 Codex agents in parallel and hit a wall: they kept working on
20
20
 
21
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.
22
22
 
23
- ![Multi-agent dashboard example](https://raw.githubusercontent.com/recodeee/gitguardex/main/docs/images/dashboard-multi-agent.png)
24
-
25
- Coming soon: [recodee.com](https://recodee.com) — live account health, usage, routing, and capacity in one place.
26
-
27
23
  ```mermaid
28
24
  flowchart LR
29
- A[Agent A edits shared files] --> S[Same target surface]
30
- B[Agent B edits shared files] --> S
31
- C[Agent C edits shared files] --> S
32
- D[Agent D edits shared files] --> S
33
- E[Agent E edits shared files] --> S
34
- S --> F[Conflict / overwrite churn]
35
- F --> G[Deleted or lost code]
36
- G --> H[Rework and confusion]
37
- H --> I[Regression risk grows]
38
- 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
39
35
  ```
40
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
+
41
41
  ---
42
42
 
43
43
  ## What it does
@@ -122,7 +122,7 @@ gx finish --all
122
122
 
123
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.
124
124
 
125
- ![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)
126
126
 
127
127
  ---
128
128
 
@@ -237,7 +237,7 @@ A few things worth knowing up front:
237
237
 
238
238
  - Running `gx` with no command opens the status/health view.
239
239
  - `gx init` is just an alias for `gx setup`.
240
- - Setup/doctor can install missing global companion CLIs (OMC runtime, OpenSpec, cavemem, 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.
241
241
  - Direct commits/pushes to protected branches are **blocked** by default. Agents must use the `agent/*` + PR flow.
242
242
  - **Exception:** VS Code Source Control commits are allowed on protected branches that exist only locally (no upstream, no remote branch).
243
243
  - On protected `main`, `gx doctor` auto-runs in a sandbox agent branch/worktree so it can't touch your real main.
@@ -255,13 +255,15 @@ git config multiagent.allowVscodeProtectedBranchWrites true
255
255
 
256
256
  ## Companion tools
257
257
 
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 global helpers; plugin/skills-first add-ons like `caveman` and `cavekit` are documented below for manual setup.
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.
259
259
 
260
260
  ```text
261
261
  ● oh-my-codex: active
262
262
  ● oh-my-claude-sisyphus: active
263
263
  ● @fission-ai/openspec: active
264
264
  ● cavemem: active
265
+ ● cavekit: active
266
+ ● caveman: active
265
267
  ● @imdeadpool/codex-account-switcher: active
266
268
  ● gh: active
267
269
  ```
@@ -488,6 +490,11 @@ npm pack --dry-run
488
490
  <details>
489
491
  <summary><strong>v7.x</strong></summary>
490
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
+
491
498
  ### v7.0.11
492
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.
493
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.
@@ -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
 
@@ -13,6 +14,8 @@ const LEGACY_NAMES = ['guardex', 'multiagent-safety'];
13
14
  const OPENSPEC_PACKAGE = '@fission-ai/openspec';
14
15
  const OMC_PACKAGE = 'oh-my-claude-sisyphus';
15
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());
16
19
  const GLOBAL_TOOLCHAIN_PACKAGES = [
17
20
  'oh-my-codex',
18
21
  OMC_PACKAGE,
@@ -20,6 +23,26 @@ const GLOBAL_TOOLCHAIN_PACKAGES = [
20
23
  CAVEMEM_PACKAGE,
21
24
  '@imdeadpool/codex-account-switcher',
22
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
+ ];
23
46
  const GH_BIN = process.env.GUARDEX_GH_BIN || 'gh';
24
47
  const REQUIRED_SYSTEM_TOOLS = [
25
48
  {
@@ -3393,9 +3416,15 @@ function maybeSelfUpdateBeforeStatus() {
3393
3416
  }
3394
3417
 
3395
3418
  console.log(`[${TOOL_NAME}] ✅ Updated to latest published version.`);
3419
+ restartIntoUpdatedGuardex(check.latest);
3396
3420
  }
3397
3421
 
3398
3422
  function readInstalledGuardexVersion() {
3423
+ const installInfo = readInstalledGuardexInstallInfo();
3424
+ return installInfo ? installInfo.version : null;
3425
+ }
3426
+
3427
+ function readInstalledGuardexInstallInfo() {
3399
3428
  // Resolves the globally-installed package's on-disk version so we can
3400
3429
  // verify npm actually wrote new bytes. Uses `npm root -g` to locate the
3401
3430
  // global install root so we don't accidentally read the running source
@@ -3417,7 +3446,24 @@ function readInstalledGuardexVersion() {
3417
3446
  }
3418
3447
  const parsed = JSON.parse(fs.readFileSync(installedPkgPath, 'utf8'));
3419
3448
  if (parsed && typeof parsed.version === 'string') {
3420
- 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
+ };
3421
3467
  }
3422
3468
  } catch (error) {
3423
3469
  return null;
@@ -3425,6 +3471,38 @@ function readInstalledGuardexVersion() {
3425
3471
  return null;
3426
3472
  }
3427
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
+
3428
3506
  function checkForOpenSpecPackageUpdate() {
3429
3507
  if (envFlagEnabled('GUARDEX_SKIP_OPENSPEC_UPDATE_CHECK')) {
3430
3508
  return { checked: false, reason: 'disabled' };
@@ -3616,15 +3694,44 @@ function detectRequiredSystemTools() {
3616
3694
  return services;
3617
3695
  }
3618
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
+
3619
3724
  function askGlobalInstallForMissing(options, missingPackages) {
3620
3725
  const approval = resolveGlobalInstallApproval(options);
3621
3726
  if (!approval.approved) {
3622
3727
  return approval;
3623
3728
  }
3624
3729
 
3730
+ const missingLocalTools = detectOptionalLocalCompanionTools().filter((tool) => tool.status !== 'active');
3731
+ const installCommands = describeCompanionInstallCommands(missingPackages, missingLocalTools);
3625
3732
  if (approval.source === 'prompt') {
3626
3733
  const approved = promptYesNoStrict(
3627
- `Install missing global tools now? (npm i -g ${missingPackages.join(' ')})`,
3734
+ `Install missing companion tools now? (${installCommands.join(' && ')})`,
3628
3735
  );
3629
3736
  return { approved, source: 'prompt' };
3630
3737
  }
@@ -3638,36 +3745,61 @@ function installGlobalToolchain(options) {
3638
3745
  }
3639
3746
 
3640
3747
  const detection = detectGlobalToolchainPackages();
3748
+ const localCompanionTools = detectOptionalLocalCompanionTools();
3641
3749
  if (!detection.ok) {
3642
3750
  console.log(`[${TOOL_NAME}] ⚠️ Could not detect global packages: ${detection.error}`);
3643
3751
  } else {
3644
3752
  if (detection.installed.length > 0) {
3645
3753
  console.log(`[${TOOL_NAME}] Already installed globally: ${detection.installed.join(', ')}`);
3646
3754
  }
3647
- 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')) {
3648
3762
  return { status: 'already-installed' };
3649
3763
  }
3650
3764
  }
3651
3765
 
3652
3766
  const missingPackages = detection.ok ? detection.missing : [...GLOBAL_TOOLCHAIN_PACKAGES];
3767
+ const missingLocalTools = localCompanionTools.filter((tool) => tool.status !== 'active');
3653
3768
  const approval = askGlobalInstallForMissing(options, missingPackages);
3654
3769
  if (!approval.approved) {
3655
3770
  return { status: 'skipped', reason: approval.source };
3656
3771
  }
3657
3772
 
3658
- console.log(
3659
- `[${TOOL_NAME}] Installing global toolchain: npm i -g ${missingPackages.join(' ')}`,
3660
- );
3661
- const result = run(NPM_BIN, ['i', '-g', ...missingPackages], { stdio: 'inherit' });
3662
- if (result.status !== 0) {
3663
- const stderr = (result.stderr || '').trim();
3664
- return {
3665
- status: 'failed',
3666
- reason: stderr || 'npm global install failed',
3667
- };
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);
3668
3787
  }
3669
3788
 
3670
- 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 };
3671
3803
  }
3672
3804
 
3673
3805
  function gitRefExists(repoRoot, refName) {
@@ -4018,9 +4150,15 @@ function status(rawArgs) {
4018
4150
  status: toolchain.installed.includes(pkg) ? 'active' : 'inactive',
4019
4151
  };
4020
4152
  });
4153
+ const localCompanionServices = detectOptionalLocalCompanionTools().map((tool) => ({
4154
+ name: tool.name,
4155
+ displayName: tool.displayName || tool.name,
4156
+ status: tool.status,
4157
+ }));
4021
4158
  const requiredSystemTools = detectRequiredSystemTools();
4022
4159
  const services = [
4023
4160
  ...npmServices,
4161
+ ...localCompanionServices,
4024
4162
  ...requiredSystemTools.map((tool) => ({
4025
4163
  name: tool.name,
4026
4164
  displayName: tool.displayName || tool.name,
@@ -4079,6 +4217,17 @@ function status(rawArgs) {
4079
4217
  const serviceLabel = service.displayName || service.name;
4080
4218
  console.log(` - ${statusDot(service.status)} ${serviceLabel}: ${service.status}`);
4081
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
+ }
4082
4231
  const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
4083
4232
  if (missingSystemTools.length > 0) {
4084
4233
  const tools = missingSystemTools
@@ -4713,19 +4862,23 @@ function setup(rawArgs) {
4713
4862
  const globalInstallStatus = installGlobalToolchain(options);
4714
4863
  if (globalInstallStatus.status === 'installed') {
4715
4864
  console.log(
4716
- `[${TOOL_NAME}] ✅ Global tools installed (${(globalInstallStatus.packages || []).join(', ')}).`,
4865
+ `[${TOOL_NAME}] ✅ Companion tools installed (${(globalInstallStatus.packages || []).join(', ')}).`,
4717
4866
  );
4718
4867
  } else if (globalInstallStatus.status === 'already-installed') {
4719
- console.log(`[${TOOL_NAME}] ✅ Companion npm global tools already installed. Skipping.`);
4868
+ console.log(`[${TOOL_NAME}] ✅ Companion tools already installed. Skipping.`);
4720
4869
  } else if (globalInstallStatus.status === 'failed') {
4870
+ const installCommands = describeCompanionInstallCommands(
4871
+ GLOBAL_TOOLCHAIN_PACKAGES,
4872
+ OPTIONAL_LOCAL_COMPANION_TOOLS,
4873
+ );
4721
4874
  console.log(
4722
4875
  `[${TOOL_NAME}] ⚠️ Global install failed: ${globalInstallStatus.reason}\n` +
4723
4876
  `[${TOOL_NAME}] Continue with local safety setup. You can retry later with:\n` +
4724
- ` ${NPM_BIN} i -g ${GLOBAL_TOOLCHAIN_PACKAGES.join(' ')}`,
4877
+ installCommands.map((command) => ` ${command}`).join('\n'),
4725
4878
  );
4726
4879
  } else if (globalInstallStatus.status === 'skipped' && globalInstallStatus.reason === 'non-interactive-default') {
4727
4880
  console.log(
4728
- `[${TOOL_NAME}] Skipping global installs (non-interactive mode). ` +
4881
+ `[${TOOL_NAME}] Skipping companion installs (non-interactive mode). ` +
4729
4882
  `Use --yes-global-install to force or run interactively for Y/N prompt.`,
4730
4883
  );
4731
4884
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.11",
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,