@imdeadpool/guardex 7.0.26 → 7.0.31
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 +38 -7
- package/SECURITY.md +1 -1
- package/package.json +4 -4
- package/src/cli/main.js +279 -23
- package/src/context.js +64 -21
- package/src/doctor/index.js +42 -20
- package/src/finish/index.js +1 -0
- package/src/output/index.js +151 -15
- package/src/toolchain/index.js +6 -0
- package/templates/scripts/agent-branch-finish.sh +48 -24
- package/templates/scripts/agent-branch-start.sh +29 -5
- package/templates/scripts/codex-agent.sh +16 -1
- package/templates/vscode/guardex-active-agents/extension.js +126 -5
- package/templates/vscode/guardex-active-agents/package.json +17 -7
- package/templates/vscode/guardex-active-agents/session-schema.js +23 -5
package/README.md
CHANGED
|
@@ -111,6 +111,16 @@ gx setup
|
|
|
111
111
|
|
|
112
112
|
That's it. Install and update via `@imdeadpool/guardex`. Setup installs the minimal repo footprint: managed hook shims, repo-local state, AGENTS wiring, OpenSpec/caveman/OMX scaffolding, and a small set of repo-local helper assets. Aliases: `gx` (preferred), `gitguardex` (full), `guardex` (legacy compatibility).
|
|
113
113
|
|
|
114
|
+
### Install surfaces at a glance
|
|
115
|
+
|
|
116
|
+
| Goal | Command | Installs |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| Global Guardex CLI | `npm i -g @imdeadpool/guardex` | `gx`, `gitguardex`, `guardex`, and the CLI runtime |
|
|
119
|
+
| User-level Codex/Claude companions | `gx install-agent-skills` | `~/.codex/skills/gitguardex/SKILL.md`, `~/.codex/skills/guardex-merge-skills-to-dev/SKILL.md`, and `~/.claude/commands/gitguardex.md` |
|
|
120
|
+
| Generic repo skill catalog | `npx skills add recodee/` or `npx skills add recodee/gitguardex` | the repo-root `skills/` catalog for agents that support the generic installer |
|
|
121
|
+
|
|
122
|
+
These paths are complementary, not chained together. `npm i -g @imdeadpool/guardex` does not auto-run `npx skills add ...`.
|
|
123
|
+
|
|
114
124
|
---
|
|
115
125
|
|
|
116
126
|
## How `AGENTS.md` and `CLAUDE.md` are handled
|
|
@@ -140,7 +150,14 @@ That's it. Install and update via `@imdeadpool/guardex`. Setup installs the mini
|
|
|
140
150
|
</div>
|
|
141
151
|
|
|
142
152
|
> [!NOTE]
|
|
143
|
-
> In this repo, `CLAUDE.md` is a symlink to `AGENTS.md`, so Claude reads the same contract
|
|
153
|
+
> In this repo, `CLAUDE.md` is a symlink to `AGENTS.md`, so Claude reads the same contract as Codex.
|
|
154
|
+
>
|
|
155
|
+
> Keep the install paths separate:
|
|
156
|
+
> - `npm i -g @imdeadpool/guardex` installs the Guardex CLI.
|
|
157
|
+
> - `gx install-agent-skills` installs the user-level Codex/Claude companion files.
|
|
158
|
+
> - `npx skills add recodee/` or `npx skills add recodee/gitguardex` installs the generic repo skill catalog.
|
|
159
|
+
>
|
|
160
|
+
> The global npm install does not auto-run the generic `npx skills add ...` flow.
|
|
144
161
|
|
|
145
162
|
### Decision flow
|
|
146
163
|
|
|
@@ -461,17 +478,19 @@ Repo: <https://github.com/Yeachan-Heo/oh-my-claudecode>
|
|
|
461
478
|
|
|
462
479
|
### GitGuardex skills - install the repo skill catalog through `npx skills`
|
|
463
480
|
|
|
464
|
-
For agents that already support the generic `skills` installer flow, GitGuardex
|
|
481
|
+
For agents that already support the generic `skills` installer flow, GitGuardex exposes its repo skill catalog directly. Use it as a separate install path after the CLI install. You can start from the broader `recodee` source or jump straight into this repo's catalog.
|
|
465
482
|
|
|
466
483
|
```sh
|
|
467
|
-
npx skills add
|
|
484
|
+
npx skills add recodee/
|
|
468
485
|
```
|
|
469
486
|
|
|
470
487
|
```sh
|
|
471
|
-
npx skills add
|
|
488
|
+
npx skills add recodee/gitguardex
|
|
472
489
|
```
|
|
473
490
|
|
|
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`.
|
|
491
|
+
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`.
|
|
492
|
+
|
|
493
|
+
Need the Codex/Claude user-home startup files instead? Run `gx install-agent-skills`. Need the generic repo catalog? Run `npx skills add ...`. They solve different setup surfaces.
|
|
475
494
|
|
|
476
495
|
Repo: <https://github.com/recodeee/gitguardex>
|
|
477
496
|
[](https://github.com/recodeee/gitguardex)
|
|
@@ -626,7 +645,7 @@ vscode/guardex-active-agents/README.md
|
|
|
626
645
|
|
|
627
646
|
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.
|
|
628
647
|
|
|
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
|
|
648
|
+
Optional Codex/Claude user-level companions still install with `gx install-agent-skills`; the generic repo skill catalog is available with `npx skills add recodee/` or directly via `npx skills add recodee/gitguardex`. Neither path copies those user-home files into each repo, and the global Guardex npm install does not trigger the `npx skills add ...` path for you.
|
|
630
649
|
|
|
631
650
|
---
|
|
632
651
|
|
|
@@ -689,9 +708,21 @@ npm pack --dry-run
|
|
|
689
708
|
<details>
|
|
690
709
|
<summary><strong>v7.x</strong></summary>
|
|
691
710
|
|
|
711
|
+
### v7.0.28
|
|
712
|
+
- Bumped `@imdeadpool/guardex` from `7.0.27` to `7.0.28` so the CLI help redesign can ship on a fresh npm version.
|
|
713
|
+
- `gx --help` and `gx` (no args) now render commands as a grouped catalog (Setup & health / Branch workflow / Coordination / Agents & reports / Meta) with short group descriptions, so the top of the help screen shows the newcomer path instead of a flat 20-row list.
|
|
714
|
+
- Added a three-step Quickstart block (`gx setup` → `gx branch start "<task>" "<agent>"` → `gx branch finish --via-pr --wait-for-merge --cleanup`) to both help surfaces so the intended install/setup sequence is visible before the full command reference.
|
|
715
|
+
- Exposed `CLI_COMMAND_GROUPS` and `CLI_QUICKSTART_STEPS` from `src/context.js` (with `CLI_COMMAND_DESCRIPTIONS` derived from the grouped source of truth), giving future help tooling and integrations a structured way to iterate the catalog without re-parsing flat rows.
|
|
716
|
+
|
|
717
|
+
### v7.0.27
|
|
718
|
+
- Bumped `@imdeadpool/guardex` from `7.0.26` to `7.0.27` so npm can publish a fresh version after `7.0.26` was already taken on the registry.
|
|
719
|
+
- The shipped `agent-branch-start.sh` copies now keep the startup auto-transfer path alive under `set -o pipefail`, so Guardex can still restore moved changes back to the protected checkout when branch startup hits a later failure.
|
|
720
|
+
- The shipped `agent-branch-finish.sh` copies now keep PR-only finish runs from opening temporary integration repos, so maintainers can finish directly through the existing PR lane without extra temp-repo churn.
|
|
721
|
+
- Keep the release scoped to version metadata for the already-merged `main` payload; no additional runtime behavior changes are introduced in this release lane.
|
|
722
|
+
|
|
692
723
|
### v7.0.26
|
|
693
724
|
- 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
|
|
725
|
+
- README now documents both `npx skills add recodee/` and `npx skills add recodee/gitguardex`, clarifies that the global Guardex npm install does not auto-run the generic skills installer, and explains why the picker shows `gitguardex` instead of a separate `guardex` skill.
|
|
695
726
|
- 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
727
|
|
|
697
728
|
### v7.0.25
|
package/SECURITY.md
CHANGED
|
@@ -8,7 +8,7 @@ Only the latest published GitGuardex CLI build is supported for security fixes.
|
|
|
8
8
|
|
|
9
9
|
Please report security issues privately by opening a GitHub security advisory:
|
|
10
10
|
|
|
11
|
-
- https://github.com/
|
|
11
|
+
- https://github.com/recodeee/gitguardex/security/advisories/new
|
|
12
12
|
|
|
13
13
|
If advisories are unavailable, open a private report via GitHub issue contact details and avoid posting exploit details publicly.
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.31",
|
|
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,
|
|
@@ -68,10 +68,10 @@
|
|
|
68
68
|
"access": "public"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"fast-check": "
|
|
71
|
+
"fast-check": "3.23.2"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"jsonc-parser": "
|
|
75
|
-
"semver": "
|
|
74
|
+
"jsonc-parser": "3.3.1",
|
|
75
|
+
"semver": "7.7.4"
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/cli/main.js
CHANGED
|
@@ -146,8 +146,10 @@ const {
|
|
|
146
146
|
colorizeDoctorOutput,
|
|
147
147
|
statusDot,
|
|
148
148
|
printToolLogsSummary,
|
|
149
|
+
getInvokedCliName,
|
|
149
150
|
usage,
|
|
150
151
|
formatElapsedDuration,
|
|
152
|
+
startTransientSpinner,
|
|
151
153
|
compactAutoFinishPathSegments,
|
|
152
154
|
detectRecoverableAutoFinishConflict,
|
|
153
155
|
printAutoFinishSummary,
|
|
@@ -889,6 +891,80 @@ function parseBooleanLike(raw) {
|
|
|
889
891
|
return null;
|
|
890
892
|
}
|
|
891
893
|
|
|
894
|
+
function autoDoctorEnabledForCurrentSession() {
|
|
895
|
+
const explicit = parseBooleanLike(process.env.GUARDEX_AUTO_DOCTOR);
|
|
896
|
+
if (explicit != null) {
|
|
897
|
+
return explicit;
|
|
898
|
+
}
|
|
899
|
+
return isInteractiveTerminal();
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function shouldAutoRunDoctorFromStatus(statusPayload) {
|
|
903
|
+
const repo = statusPayload?.repo || {};
|
|
904
|
+
return Boolean(
|
|
905
|
+
autoDoctorEnabledForCurrentSession()
|
|
906
|
+
&& repo.inGitRepo
|
|
907
|
+
&& repo.guardexEnabled !== false
|
|
908
|
+
&& repo.serviceStatus === 'degraded'
|
|
909
|
+
&& repo.scan
|
|
910
|
+
&& Number(repo.scan.findings || 0) > 0,
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function runCliSubprocessWithSpinner(args, options = {}) {
|
|
915
|
+
return new Promise((resolve, reject) => {
|
|
916
|
+
const spinner = options.spinnerMessage
|
|
917
|
+
? startTransientSpinner(options.spinnerMessage, {
|
|
918
|
+
prefix: options.spinnerPrefix || `[${TOOL_NAME}]`,
|
|
919
|
+
})
|
|
920
|
+
: { stop() {} };
|
|
921
|
+
const child = cp.spawn(process.execPath, [path.resolve(__filename), ...args], {
|
|
922
|
+
cwd: options.cwd || process.cwd(),
|
|
923
|
+
env: {
|
|
924
|
+
...process.env,
|
|
925
|
+
GUARDEX_AUTO_DOCTOR: '0',
|
|
926
|
+
},
|
|
927
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
const stopSpinner = () => spinner.stop();
|
|
931
|
+
child.stdout.on('data', (chunk) => {
|
|
932
|
+
stopSpinner();
|
|
933
|
+
process.stdout.write(chunk);
|
|
934
|
+
});
|
|
935
|
+
child.stderr.on('data', (chunk) => {
|
|
936
|
+
stopSpinner();
|
|
937
|
+
process.stderr.write(chunk);
|
|
938
|
+
});
|
|
939
|
+
child.on('error', (error) => {
|
|
940
|
+
stopSpinner();
|
|
941
|
+
reject(error);
|
|
942
|
+
});
|
|
943
|
+
child.on('close', (code) => {
|
|
944
|
+
stopSpinner();
|
|
945
|
+
resolve(typeof code === 'number' ? code : 1);
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function maybeAutoRunDoctorFromDefaultStatus(statusPayload) {
|
|
951
|
+
if (!shouldAutoRunDoctorFromStatus(statusPayload)) {
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const target = statusPayload?.repo?.target || process.cwd();
|
|
956
|
+
console.log(`[${TOOL_NAME}] Auto-repair: repo safety is degraded. Running '${SHORT_TOOL_NAME} doctor --current' now.`);
|
|
957
|
+
process.exitCode = await runCliSubprocessWithSpinner(
|
|
958
|
+
['doctor', '--target', target, '--current'],
|
|
959
|
+
{
|
|
960
|
+
cwd: target,
|
|
961
|
+
spinnerPrefix: `[${TOOL_NAME}] Auto-repair:`,
|
|
962
|
+
spinnerMessage: 'preparing doctor workspace',
|
|
963
|
+
},
|
|
964
|
+
);
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
|
|
892
968
|
function parseDotenvAssignmentValue(raw) {
|
|
893
969
|
let value = String(raw || '').trim();
|
|
894
970
|
if (!value) return '';
|
|
@@ -1672,12 +1748,62 @@ function setExitCodeFromScan(scan) {
|
|
|
1672
1748
|
process.exitCode = 0;
|
|
1673
1749
|
}
|
|
1674
1750
|
|
|
1675
|
-
function
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1751
|
+
function printStatusRepairHint(scanResult) {
|
|
1752
|
+
if (!scanResult || scanResult.guardexEnabled === false) {
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
if (scanResult.errors === 0 && scanResult.warnings === 0) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const scanHint = scanResult.errors === 0
|
|
1760
|
+
? `review warning details with '${SHORT_TOOL_NAME} scan'`
|
|
1761
|
+
: `inspect detailed findings with '${SHORT_TOOL_NAME} scan'`;
|
|
1762
|
+
console.log(
|
|
1763
|
+
`[${TOOL_NAME}] Quick fix: run '${SHORT_TOOL_NAME} doctor' to repair drift, or ${scanHint}.`,
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function countAgentWorktrees(repoRoot) {
|
|
1768
|
+
if (!repoRoot || typeof repoRoot !== 'string') return 0;
|
|
1769
|
+
const relPaths = ['.omc/agent-worktrees', '.omx/agent-worktrees'];
|
|
1770
|
+
let count = 0;
|
|
1771
|
+
for (const rel of relPaths) {
|
|
1772
|
+
try {
|
|
1773
|
+
const entries = fs.readdirSync(path.join(repoRoot, rel), { withFileTypes: true });
|
|
1774
|
+
count += entries.filter((entry) => entry.isDirectory()).length;
|
|
1775
|
+
} catch (_err) {
|
|
1776
|
+
// missing dir or permission error; not an active-agent signal
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return count;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function deriveNextStepHint({ scanResult, worktreeCount, invoked, inGitRepo }) {
|
|
1783
|
+
if (!inGitRepo) {
|
|
1784
|
+
return `${invoked} setup --target <path-to-git-repo> # initialize guardrails in a repo`;
|
|
1785
|
+
}
|
|
1786
|
+
if (!scanResult) {
|
|
1787
|
+
return `${invoked} setup # bootstrap repo guardrails`;
|
|
1788
|
+
}
|
|
1789
|
+
if (scanResult.guardexEnabled === false) {
|
|
1790
|
+
return `set GUARDEX_ON=1 in .env # re-enable guardrails, then '${invoked} doctor'`;
|
|
1791
|
+
}
|
|
1792
|
+
const branch = scanResult.branch || '';
|
|
1793
|
+
if (branch.startsWith('agent/')) {
|
|
1794
|
+
return `${invoked} branch finish --branch "${branch}" --via-pr --wait-for-merge --cleanup`;
|
|
1795
|
+
}
|
|
1796
|
+
if (worktreeCount > 0) {
|
|
1797
|
+
const plural = worktreeCount === 1 ? 'worktree' : 'worktrees';
|
|
1798
|
+
return `${invoked} finish --all # ${worktreeCount} active agent ${plural}`;
|
|
1799
|
+
}
|
|
1800
|
+
if (scanResult.errors > 0 || scanResult.warnings > 0) {
|
|
1801
|
+
return `${invoked} doctor # repair drift`;
|
|
1802
|
+
}
|
|
1803
|
+
return `${invoked} branch start "<task>" "<agent-name>" # start a sandboxed agent task`;
|
|
1804
|
+
}
|
|
1680
1805
|
|
|
1806
|
+
function collectServicesSnapshot() {
|
|
1681
1807
|
const toolchain = toolchainModule.detectGlobalToolchainPackages();
|
|
1682
1808
|
const npmServices = GLOBAL_TOOLCHAIN_PACKAGES.map((pkg) => {
|
|
1683
1809
|
const service = toolchainModule.getGlobalToolchainService(pkg);
|
|
@@ -1701,18 +1827,103 @@ function status(rawArgs) {
|
|
|
1701
1827
|
const localCompanionServices = toolchainModule.detectOptionalLocalCompanionTools().map((tool) => ({
|
|
1702
1828
|
name: tool.name,
|
|
1703
1829
|
displayName: tool.displayName || tool.name,
|
|
1830
|
+
installCommand: tool.installCommand,
|
|
1831
|
+
installArgs: Array.isArray(tool.installArgs) ? [...tool.installArgs] : [],
|
|
1704
1832
|
status: tool.status,
|
|
1705
1833
|
}));
|
|
1706
1834
|
const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
|
|
1707
1835
|
const services = [
|
|
1708
1836
|
...npmServices,
|
|
1709
|
-
...localCompanionServices
|
|
1837
|
+
...localCompanionServices.map((tool) => ({
|
|
1838
|
+
name: tool.name,
|
|
1839
|
+
displayName: tool.displayName,
|
|
1840
|
+
status: tool.status,
|
|
1841
|
+
})),
|
|
1710
1842
|
...requiredSystemTools.map((tool) => ({
|
|
1711
1843
|
name: tool.name,
|
|
1712
1844
|
displayName: tool.displayName || tool.name,
|
|
1713
1845
|
status: tool.status,
|
|
1714
1846
|
})),
|
|
1715
1847
|
];
|
|
1848
|
+
return { toolchain, npmServices, localCompanionServices, requiredSystemTools, services };
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function maybePromptInstallMissingCompanions(snapshot) {
|
|
1852
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_COMPANION_PROMPT)) {
|
|
1853
|
+
return { handled: false, installed: false };
|
|
1854
|
+
}
|
|
1855
|
+
const interactive = Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY);
|
|
1856
|
+
const autoApproval = toolchainModule.parseAutoApproval('GUARDEX_AUTO_COMPANION_APPROVAL');
|
|
1857
|
+
if (!interactive && autoApproval == null) {
|
|
1858
|
+
return { handled: false, installed: false };
|
|
1859
|
+
}
|
|
1860
|
+
if (!snapshot.toolchain.ok) {
|
|
1861
|
+
return { handled: false, installed: false };
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
const missingPackages = snapshot.npmServices
|
|
1865
|
+
.filter((service) => service.status !== 'active')
|
|
1866
|
+
.map((service) => service.packageName);
|
|
1867
|
+
const missingLocalTools = snapshot.localCompanionServices.filter((tool) => tool.status !== 'active');
|
|
1868
|
+
if (missingPackages.length === 0 && missingLocalTools.length === 0) {
|
|
1869
|
+
return { handled: false, installed: false };
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const missingNames = [
|
|
1873
|
+
...missingPackages.map((pkg) => toolchainModule.formatGlobalToolchainServiceName(pkg)),
|
|
1874
|
+
...missingLocalTools.map((tool) => tool.displayName || tool.name),
|
|
1875
|
+
];
|
|
1876
|
+
console.log(`[${TOOL_NAME}] Missing companion tools: ${missingNames.join(', ')}.`);
|
|
1877
|
+
|
|
1878
|
+
const promptText = toolchainModule.buildMissingCompanionInstallPrompt(missingPackages, missingLocalTools);
|
|
1879
|
+
const approved = interactive
|
|
1880
|
+
? toolchainModule.promptYesNoStrict(promptText)
|
|
1881
|
+
: autoApproval;
|
|
1882
|
+
|
|
1883
|
+
if (!approved) {
|
|
1884
|
+
console.log(
|
|
1885
|
+
`[${TOOL_NAME}] Skipped companion install. Set GUARDEX_SKIP_COMPANION_PROMPT=1 to silence this prompt, ` +
|
|
1886
|
+
`or run '${getInvokedCliName()} setup --install-only' later to install manually.`,
|
|
1887
|
+
);
|
|
1888
|
+
return { handled: true, installed: false };
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
const result = toolchainModule.performCompanionInstall(missingPackages, missingLocalTools);
|
|
1892
|
+
if (result.status === 'installed') {
|
|
1893
|
+
console.log(
|
|
1894
|
+
`[${TOOL_NAME}] ✅ Companion tools installed (${(result.packages || []).join(', ')}).`,
|
|
1895
|
+
);
|
|
1896
|
+
return { handled: true, installed: true };
|
|
1897
|
+
}
|
|
1898
|
+
if (result.status === 'failed') {
|
|
1899
|
+
console.log(
|
|
1900
|
+
`[${TOOL_NAME}] ⚠️ Companion install failed: ${result.reason}. ` +
|
|
1901
|
+
`Retry with '${getInvokedCliName()} setup --install-only'.`,
|
|
1902
|
+
);
|
|
1903
|
+
return { handled: true, installed: false };
|
|
1904
|
+
}
|
|
1905
|
+
return { handled: true, installed: false };
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function status(rawArgs) {
|
|
1909
|
+
const { found: verboseFlag, remaining: afterVerbose } = extractFlag(rawArgs, '--verbose');
|
|
1910
|
+
const options = parseCommonArgs(afterVerbose, {
|
|
1911
|
+
target: process.cwd(),
|
|
1912
|
+
json: false,
|
|
1913
|
+
});
|
|
1914
|
+
const forceCompact = envFlagIsTruthy(process.env.GUARDEX_COMPACT_STATUS);
|
|
1915
|
+
const forceExpand = envFlagIsTruthy(process.env.GUARDEX_VERBOSE_STATUS) || verboseFlag;
|
|
1916
|
+
const interactive = Boolean(process.stdout.isTTY);
|
|
1917
|
+
const invokedBasename = getInvokedCliName();
|
|
1918
|
+
|
|
1919
|
+
let snapshot = collectServicesSnapshot();
|
|
1920
|
+
if (!options.json) {
|
|
1921
|
+
const result = maybePromptInstallMissingCompanions(snapshot);
|
|
1922
|
+
if (result.installed) {
|
|
1923
|
+
snapshot = collectServicesSnapshot();
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
let { toolchain, npmServices, localCompanionServices, requiredSystemTools, services } = snapshot;
|
|
1716
1927
|
|
|
1717
1928
|
const targetPath = path.resolve(options.target);
|
|
1718
1929
|
const inGitRepo = isGitRepo(targetPath);
|
|
@@ -1752,18 +1963,27 @@ function status(rawArgs) {
|
|
|
1752
1963
|
if (options.json) {
|
|
1753
1964
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1754
1965
|
process.exitCode = 0;
|
|
1755
|
-
return;
|
|
1966
|
+
return payload;
|
|
1756
1967
|
}
|
|
1757
1968
|
|
|
1969
|
+
const allServicesActive = toolchain.ok && services.every((service) => service.status === 'active');
|
|
1970
|
+
const compact = !forceExpand && (forceCompact || (interactive && allServicesActive));
|
|
1971
|
+
|
|
1758
1972
|
console.log(`[${TOOL_NAME}] CLI: ${payload.cli.runtime}`);
|
|
1759
1973
|
if (!toolchain.ok) {
|
|
1760
1974
|
console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${toolchain.error}`);
|
|
1761
1975
|
}
|
|
1762
1976
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1977
|
+
if (compact) {
|
|
1978
|
+
console.log(
|
|
1979
|
+
`[${TOOL_NAME}] Global services: ${services.length}/${services.length} ${statusDot('active')} active`,
|
|
1980
|
+
);
|
|
1981
|
+
} else {
|
|
1982
|
+
console.log(`[${TOOL_NAME}] Global services:`);
|
|
1983
|
+
for (const service of services) {
|
|
1984
|
+
const serviceLabel = service.displayName || service.name;
|
|
1985
|
+
console.log(` - ${statusDot(service.status)} ${serviceLabel}: ${service.status}`);
|
|
1986
|
+
}
|
|
1767
1987
|
}
|
|
1768
1988
|
const inactiveOptionalCompanions = [...npmServices, ...localCompanionServices]
|
|
1769
1989
|
.filter((service) => service.status !== 'active')
|
|
@@ -1799,8 +2019,16 @@ function status(rawArgs) {
|
|
|
1799
2019
|
console.log(
|
|
1800
2020
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('inactive')} inactive (no git repository at target).`,
|
|
1801
2021
|
);
|
|
2022
|
+
const inactiveHint = deriveNextStepHint({
|
|
2023
|
+
scanResult: null,
|
|
2024
|
+
worktreeCount: 0,
|
|
2025
|
+
invoked: invokedBasename,
|
|
2026
|
+
inGitRepo,
|
|
2027
|
+
});
|
|
2028
|
+
console.log(`[${TOOL_NAME}] Next: ${inactiveHint}`);
|
|
2029
|
+
printToolLogsSummary({ invokedBasename, compact });
|
|
1802
2030
|
process.exitCode = 0;
|
|
1803
|
-
return;
|
|
2031
|
+
return payload;
|
|
1804
2032
|
}
|
|
1805
2033
|
|
|
1806
2034
|
if (scanResult.guardexEnabled === false) {
|
|
@@ -1809,9 +2037,23 @@ function status(rawArgs) {
|
|
|
1809
2037
|
);
|
|
1810
2038
|
console.log(`[${TOOL_NAME}] Repo: ${scanResult.repoRoot}`);
|
|
1811
2039
|
console.log(`[${TOOL_NAME}] Branch: ${scanResult.branch}`);
|
|
1812
|
-
|
|
2040
|
+
const worktreeCountDisabled = countAgentWorktrees(scanResult.repoRoot);
|
|
2041
|
+
if (worktreeCountDisabled > 0) {
|
|
2042
|
+
const plural = worktreeCountDisabled === 1 ? 'worktree' : 'worktrees';
|
|
2043
|
+
console.log(
|
|
2044
|
+
`[${TOOL_NAME}] ⚠ ${worktreeCountDisabled} active agent ${plural} under .omc/agent-worktrees or .omx/agent-worktrees.`,
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
const disabledHint = deriveNextStepHint({
|
|
2048
|
+
scanResult,
|
|
2049
|
+
worktreeCount: worktreeCountDisabled,
|
|
2050
|
+
invoked: invokedBasename,
|
|
2051
|
+
inGitRepo,
|
|
2052
|
+
});
|
|
2053
|
+
console.log(`[${TOOL_NAME}] Next: ${disabledHint}`);
|
|
2054
|
+
printToolLogsSummary({ invokedBasename, compact });
|
|
1813
2055
|
process.exitCode = 0;
|
|
1814
|
-
return;
|
|
2056
|
+
return payload;
|
|
1815
2057
|
}
|
|
1816
2058
|
|
|
1817
2059
|
if (scanResult.errors === 0 && scanResult.warnings === 0) {
|
|
@@ -1820,23 +2062,36 @@ function status(rawArgs) {
|
|
|
1820
2062
|
console.log(
|
|
1821
2063
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.warnings} warning(s)).`,
|
|
1822
2064
|
);
|
|
1823
|
-
console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' to review warning details.`);
|
|
1824
2065
|
} else if (scanResult.warnings === 0) {
|
|
1825
2066
|
console.log(
|
|
1826
2067
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.errors} error(s)).`,
|
|
1827
2068
|
);
|
|
1828
|
-
console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' for detailed findings.`);
|
|
1829
2069
|
} else {
|
|
1830
2070
|
console.log(
|
|
1831
2071
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.errors} error(s), ${scanResult.warnings} warning(s)).`,
|
|
1832
2072
|
);
|
|
1833
|
-
console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' for detailed findings.`);
|
|
1834
2073
|
}
|
|
2074
|
+
printStatusRepairHint(scanResult);
|
|
1835
2075
|
console.log(`[${TOOL_NAME}] Repo: ${scanResult.repoRoot}`);
|
|
1836
2076
|
console.log(`[${TOOL_NAME}] Branch: ${scanResult.branch}`);
|
|
1837
|
-
|
|
2077
|
+
const worktreeCountActive = countAgentWorktrees(scanResult.repoRoot);
|
|
2078
|
+
if (worktreeCountActive > 0) {
|
|
2079
|
+
const plural = worktreeCountActive === 1 ? 'worktree' : 'worktrees';
|
|
2080
|
+
console.log(
|
|
2081
|
+
`[${TOOL_NAME}] ⚠ ${worktreeCountActive} active agent ${plural} → ${invokedBasename} finish --all`,
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
const activeHint = deriveNextStepHint({
|
|
2085
|
+
scanResult,
|
|
2086
|
+
worktreeCount: worktreeCountActive,
|
|
2087
|
+
invoked: invokedBasename,
|
|
2088
|
+
inGitRepo,
|
|
2089
|
+
});
|
|
2090
|
+
console.log(`[${TOOL_NAME}] Next: ${activeHint}`);
|
|
2091
|
+
printToolLogsSummary({ invokedBasename, compact });
|
|
1838
2092
|
|
|
1839
2093
|
process.exitCode = 0;
|
|
2094
|
+
return payload;
|
|
1840
2095
|
}
|
|
1841
2096
|
|
|
1842
2097
|
function install(rawArgs) {
|
|
@@ -3246,13 +3501,14 @@ function protect(rawArgs) {
|
|
|
3246
3501
|
throw new Error(`Unknown protect subcommand: ${subcommand}`);
|
|
3247
3502
|
}
|
|
3248
3503
|
|
|
3249
|
-
function main() {
|
|
3504
|
+
async function main() {
|
|
3250
3505
|
const args = process.argv.slice(2);
|
|
3251
3506
|
|
|
3252
3507
|
if (args.length === 0) {
|
|
3253
3508
|
toolchainModule.maybeSelfUpdateBeforeStatus();
|
|
3254
3509
|
toolchainModule.maybeOpenSpecUpdateBeforeStatus();
|
|
3255
|
-
status([]);
|
|
3510
|
+
const statusPayload = status([]);
|
|
3511
|
+
await maybeAutoRunDoctorFromDefaultStatus(statusPayload);
|
|
3256
3512
|
return;
|
|
3257
3513
|
}
|
|
3258
3514
|
|
|
@@ -3322,9 +3578,9 @@ function main() {
|
|
|
3322
3578
|
throw new Error(`Unknown command: ${command}`);
|
|
3323
3579
|
}
|
|
3324
3580
|
|
|
3325
|
-
function runFromBin() {
|
|
3581
|
+
async function runFromBin() {
|
|
3326
3582
|
try {
|
|
3327
|
-
main();
|
|
3583
|
+
await main();
|
|
3328
3584
|
} catch (error) {
|
|
3329
3585
|
console.error(`[${TOOL_NAME}] ${error.message}`);
|
|
3330
3586
|
process.exitCode = 1;
|
|
@@ -3332,7 +3588,7 @@ function runFromBin() {
|
|
|
3332
3588
|
}
|
|
3333
3589
|
|
|
3334
3590
|
if (require.main === module) {
|
|
3335
|
-
runFromBin();
|
|
3591
|
+
void runFromBin();
|
|
3336
3592
|
}
|
|
3337
3593
|
|
|
3338
3594
|
module.exports = {
|
package/src/context.js
CHANGED
|
@@ -363,27 +363,68 @@ const SUGGESTIBLE_COMMANDS = [
|
|
|
363
363
|
'print-agents-snippet',
|
|
364
364
|
'release',
|
|
365
365
|
];
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
366
|
+
// CLI_COMMAND_GROUPS is the grouped source of truth the `gx --help` /
|
|
367
|
+
// `gx` no-args renderer uses. Each group is ordered roughly by how often a
|
|
368
|
+
// user reaches for it so the help screen answers "what do I run first?"
|
|
369
|
+
// before "what else can this do?". CLI_COMMAND_DESCRIPTIONS preserves the
|
|
370
|
+
// flat export for callers that still want the ungrouped list.
|
|
371
|
+
const CLI_COMMAND_GROUPS = [
|
|
372
|
+
{
|
|
373
|
+
label: 'Setup & health',
|
|
374
|
+
description: 'Install, repair, and check a repo. Run these first on a new clone.',
|
|
375
|
+
commands: [
|
|
376
|
+
['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target, --current)'],
|
|
377
|
+
['doctor', 'Repair drift + verify (flags: --target, --current; auto-sandboxes on protected main)'],
|
|
378
|
+
['status', 'Show GitGuardex CLI + service health without modifying files'],
|
|
379
|
+
['migrate', 'Convert legacy repo-local installs to the zero-copy CLI-owned surface'],
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
label: 'Branch workflow',
|
|
384
|
+
description: 'The sandbox → commit → PR → merge loop for agent-owned branches.',
|
|
385
|
+
commands: [
|
|
386
|
+
['branch', 'CLI-owned branch workflow surface (start/finish/merge)'],
|
|
387
|
+
['finish', 'Commit + PR + merge completed agent branches (--all, --branch)'],
|
|
388
|
+
['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
|
|
389
|
+
['sync', 'Sync agent branches with origin/<base>'],
|
|
390
|
+
['cleanup', 'Prune merged/stale agent branches and worktrees'],
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
label: 'Coordination',
|
|
395
|
+
description: 'File locks, worktrees, hooks, and protected-branch policy.',
|
|
396
|
+
commands: [
|
|
397
|
+
['locks', 'CLI-owned file lock surface (claim/allow-delete/release/status/validate)'],
|
|
398
|
+
['worktree', 'CLI-owned worktree cleanup surface (prune)'],
|
|
399
|
+
['hook', 'Hook dispatch/install surface used by managed shims'],
|
|
400
|
+
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
label: 'Agents & reports',
|
|
405
|
+
description: 'Review / cleanup bots, AI setup prompts, and safety reports.',
|
|
406
|
+
commands: [
|
|
407
|
+
['agents', 'Start/stop repo-scoped review + cleanup bots'],
|
|
408
|
+
['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
|
|
409
|
+
['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
|
|
410
|
+
['report', 'Security/safety reports (e.g. OpenSSF scorecard, session severity)'],
|
|
411
|
+
['release', 'Create or update the current GitHub release with README-generated notes'],
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
label: 'Meta',
|
|
416
|
+
description: 'Version + help.',
|
|
417
|
+
commands: [
|
|
418
|
+
['help', 'Show this help output'],
|
|
419
|
+
['version', 'Print GitGuardex version'],
|
|
420
|
+
],
|
|
421
|
+
},
|
|
422
|
+
];
|
|
423
|
+
const CLI_COMMAND_DESCRIPTIONS = CLI_COMMAND_GROUPS.flatMap((group) => group.commands);
|
|
424
|
+
const CLI_QUICKSTART_STEPS = [
|
|
425
|
+
'gx setup',
|
|
426
|
+
'gx branch start "<task>" "<agent>"',
|
|
427
|
+
'gx branch finish --via-pr --wait-for-merge --cleanup',
|
|
387
428
|
];
|
|
388
429
|
const DEPRECATED_COMMAND_ALIASES = new Map([
|
|
389
430
|
['init', { target: 'setup', hint: 'gx setup' }],
|
|
@@ -686,6 +727,8 @@ module.exports = {
|
|
|
686
727
|
COMMAND_TYPO_ALIASES,
|
|
687
728
|
SUGGESTIBLE_COMMANDS,
|
|
688
729
|
CLI_COMMAND_DESCRIPTIONS,
|
|
730
|
+
CLI_COMMAND_GROUPS,
|
|
731
|
+
CLI_QUICKSTART_STEPS,
|
|
689
732
|
DEPRECATED_COMMAND_ALIASES,
|
|
690
733
|
AGENT_BOT_DESCRIPTIONS,
|
|
691
734
|
DOCTOR_AUTO_FINISH_DETAIL_LIMIT,
|