@imdeadpool/guardex 7.0.27 → 7.0.34
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 +171 -818
- package/package.json +1 -1
- package/src/cli/main.js +360 -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/AGENTS.multiagent-safety.md +5 -1
- package/templates/githooks/post-checkout +8 -2
- package/templates/scripts/agent-branch-finish.sh +36 -21
- package/templates/scripts/agent-branch-start.sh +15 -1
- package/templates/scripts/codex-agent.sh +16 -1
- package/templates/vscode/guardex-active-agents/extension.js +39 -4
- package/templates/vscode/guardex-active-agents/package.json +4 -4
- package/templates/vscode/guardex-active-agents/session-schema.js +23 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.34",
|
|
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/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 '';
|
|
@@ -1158,6 +1234,84 @@ function promptYesNoStrict(question) {
|
|
|
1158
1234
|
}
|
|
1159
1235
|
}
|
|
1160
1236
|
|
|
1237
|
+
const VSCODE_EXTENSION_ID = 'Recodee.gitguardex-active-agents';
|
|
1238
|
+
const VSCODE_EXTENSION_DISPLAY_NAME = 'GitGuardex Active Agents';
|
|
1239
|
+
|
|
1240
|
+
function maybePromptInstallVscodeExtension(options) {
|
|
1241
|
+
if (options.dryRun) {
|
|
1242
|
+
console.log(
|
|
1243
|
+
`[${TOOL_NAME}] (dry-run) Would offer to install VS Code extension '${VSCODE_EXTENSION_ID}'.`,
|
|
1244
|
+
);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_VSCODE_EXT_PROMPT)) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const codeProbe = cp.spawnSync('code', ['--version'], { stdio: 'ignore' });
|
|
1253
|
+
if (codeProbe.error || codeProbe.status !== 0) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const listProbe = cp.spawnSync('code', ['--list-extensions'], {
|
|
1258
|
+
encoding: 'utf8',
|
|
1259
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1260
|
+
});
|
|
1261
|
+
if (!listProbe.error && listProbe.status === 0) {
|
|
1262
|
+
const alreadyInstalled = String(listProbe.stdout || '')
|
|
1263
|
+
.split('\n')
|
|
1264
|
+
.some((line) => line.trim().toLowerCase() === VSCODE_EXTENSION_ID.toLowerCase());
|
|
1265
|
+
if (alreadyInstalled) {
|
|
1266
|
+
console.log(
|
|
1267
|
+
`[${TOOL_NAME}] ✅ VS Code extension '${VSCODE_EXTENSION_ID}' already installed.`,
|
|
1268
|
+
);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
let approved;
|
|
1274
|
+
if (options.yesGlobalInstall) {
|
|
1275
|
+
approved = true;
|
|
1276
|
+
} else if (options.noGlobalInstall) {
|
|
1277
|
+
approved = false;
|
|
1278
|
+
} else if (!isInteractiveTerminal()) {
|
|
1279
|
+
console.log(
|
|
1280
|
+
`[${TOOL_NAME}] Optional VS Code extension '${VSCODE_EXTENSION_ID}' ` +
|
|
1281
|
+
`(${VSCODE_EXTENSION_DISPLAY_NAME}) not installed. ` +
|
|
1282
|
+
`Install later: code --install-extension ${VSCODE_EXTENSION_ID}`,
|
|
1283
|
+
);
|
|
1284
|
+
return;
|
|
1285
|
+
} else {
|
|
1286
|
+
approved = promptYesNoStrict(
|
|
1287
|
+
`Install VS Code extension '${VSCODE_EXTENSION_ID}' (${VSCODE_EXTENSION_DISPLAY_NAME}) now?`,
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (!approved) {
|
|
1292
|
+
console.log(
|
|
1293
|
+
`[${TOOL_NAME}] ⚠️ VS Code extension skipped. ` +
|
|
1294
|
+
`Set GUARDEX_SKIP_VSCODE_EXT_PROMPT=1 to silence or run 'code --install-extension ${VSCODE_EXTENSION_ID}' later.`,
|
|
1295
|
+
);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const install = cp.spawnSync('code', ['--install-extension', VSCODE_EXTENSION_ID], {
|
|
1300
|
+
stdio: 'inherit',
|
|
1301
|
+
});
|
|
1302
|
+
if (install.error || install.status !== 0) {
|
|
1303
|
+
console.log(
|
|
1304
|
+
`[${TOOL_NAME}] ⚠️ VS Code extension install failed. ` +
|
|
1305
|
+
`Retry manually: code --install-extension ${VSCODE_EXTENSION_ID}`,
|
|
1306
|
+
);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
console.log(
|
|
1310
|
+
`[${TOOL_NAME}] ✅ VS Code extension '${VSCODE_EXTENSION_ID}' installed. ` +
|
|
1311
|
+
`Reload the VS Code window to activate it.`,
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1161
1315
|
function resolveGlobalInstallApproval(options) {
|
|
1162
1316
|
if (options.yesGlobalInstall && options.noGlobalInstall) {
|
|
1163
1317
|
throw new Error('Cannot use both --yes-global-install and --no-global-install');
|
|
@@ -1672,12 +1826,62 @@ function setExitCodeFromScan(scan) {
|
|
|
1672
1826
|
process.exitCode = 0;
|
|
1673
1827
|
}
|
|
1674
1828
|
|
|
1675
|
-
function
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1829
|
+
function printStatusRepairHint(scanResult) {
|
|
1830
|
+
if (!scanResult || scanResult.guardexEnabled === false) {
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
if (scanResult.errors === 0 && scanResult.warnings === 0) {
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
const scanHint = scanResult.errors === 0
|
|
1838
|
+
? `review warning details with '${SHORT_TOOL_NAME} scan'`
|
|
1839
|
+
: `inspect detailed findings with '${SHORT_TOOL_NAME} scan'`;
|
|
1840
|
+
console.log(
|
|
1841
|
+
`[${TOOL_NAME}] Quick fix: run '${SHORT_TOOL_NAME} doctor' to repair drift, or ${scanHint}.`,
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function countAgentWorktrees(repoRoot) {
|
|
1846
|
+
if (!repoRoot || typeof repoRoot !== 'string') return 0;
|
|
1847
|
+
const relPaths = ['.omc/agent-worktrees', '.omx/agent-worktrees'];
|
|
1848
|
+
let count = 0;
|
|
1849
|
+
for (const rel of relPaths) {
|
|
1850
|
+
try {
|
|
1851
|
+
const entries = fs.readdirSync(path.join(repoRoot, rel), { withFileTypes: true });
|
|
1852
|
+
count += entries.filter((entry) => entry.isDirectory()).length;
|
|
1853
|
+
} catch (_err) {
|
|
1854
|
+
// missing dir or permission error; not an active-agent signal
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return count;
|
|
1858
|
+
}
|
|
1680
1859
|
|
|
1860
|
+
function deriveNextStepHint({ scanResult, worktreeCount, invoked, inGitRepo }) {
|
|
1861
|
+
if (!inGitRepo) {
|
|
1862
|
+
return `${invoked} setup --target <path-to-git-repo> # initialize guardrails in a repo`;
|
|
1863
|
+
}
|
|
1864
|
+
if (!scanResult) {
|
|
1865
|
+
return `${invoked} setup # bootstrap repo guardrails`;
|
|
1866
|
+
}
|
|
1867
|
+
if (scanResult.guardexEnabled === false) {
|
|
1868
|
+
return `set GUARDEX_ON=1 in .env # re-enable guardrails, then '${invoked} doctor'`;
|
|
1869
|
+
}
|
|
1870
|
+
const branch = scanResult.branch || '';
|
|
1871
|
+
if (branch.startsWith('agent/')) {
|
|
1872
|
+
return `${invoked} branch finish --branch "${branch}" --via-pr --wait-for-merge --cleanup`;
|
|
1873
|
+
}
|
|
1874
|
+
if (worktreeCount > 0) {
|
|
1875
|
+
const plural = worktreeCount === 1 ? 'worktree' : 'worktrees';
|
|
1876
|
+
return `${invoked} finish --all # ${worktreeCount} active agent ${plural}`;
|
|
1877
|
+
}
|
|
1878
|
+
if (scanResult.errors > 0 || scanResult.warnings > 0) {
|
|
1879
|
+
return `${invoked} doctor # repair drift`;
|
|
1880
|
+
}
|
|
1881
|
+
return `${invoked} branch start "<task>" "<agent-name>" # start a sandboxed agent task`;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function collectServicesSnapshot() {
|
|
1681
1885
|
const toolchain = toolchainModule.detectGlobalToolchainPackages();
|
|
1682
1886
|
const npmServices = GLOBAL_TOOLCHAIN_PACKAGES.map((pkg) => {
|
|
1683
1887
|
const service = toolchainModule.getGlobalToolchainService(pkg);
|
|
@@ -1701,18 +1905,103 @@ function status(rawArgs) {
|
|
|
1701
1905
|
const localCompanionServices = toolchainModule.detectOptionalLocalCompanionTools().map((tool) => ({
|
|
1702
1906
|
name: tool.name,
|
|
1703
1907
|
displayName: tool.displayName || tool.name,
|
|
1908
|
+
installCommand: tool.installCommand,
|
|
1909
|
+
installArgs: Array.isArray(tool.installArgs) ? [...tool.installArgs] : [],
|
|
1704
1910
|
status: tool.status,
|
|
1705
1911
|
}));
|
|
1706
1912
|
const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
|
|
1707
1913
|
const services = [
|
|
1708
1914
|
...npmServices,
|
|
1709
|
-
...localCompanionServices
|
|
1915
|
+
...localCompanionServices.map((tool) => ({
|
|
1916
|
+
name: tool.name,
|
|
1917
|
+
displayName: tool.displayName,
|
|
1918
|
+
status: tool.status,
|
|
1919
|
+
})),
|
|
1710
1920
|
...requiredSystemTools.map((tool) => ({
|
|
1711
1921
|
name: tool.name,
|
|
1712
1922
|
displayName: tool.displayName || tool.name,
|
|
1713
1923
|
status: tool.status,
|
|
1714
1924
|
})),
|
|
1715
1925
|
];
|
|
1926
|
+
return { toolchain, npmServices, localCompanionServices, requiredSystemTools, services };
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function maybePromptInstallMissingCompanions(snapshot) {
|
|
1930
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_COMPANION_PROMPT)) {
|
|
1931
|
+
return { handled: false, installed: false };
|
|
1932
|
+
}
|
|
1933
|
+
const interactive = Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY);
|
|
1934
|
+
const autoApproval = toolchainModule.parseAutoApproval('GUARDEX_AUTO_COMPANION_APPROVAL');
|
|
1935
|
+
if (!interactive && autoApproval == null) {
|
|
1936
|
+
return { handled: false, installed: false };
|
|
1937
|
+
}
|
|
1938
|
+
if (!snapshot.toolchain.ok) {
|
|
1939
|
+
return { handled: false, installed: false };
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const missingPackages = snapshot.npmServices
|
|
1943
|
+
.filter((service) => service.status !== 'active')
|
|
1944
|
+
.map((service) => service.packageName);
|
|
1945
|
+
const missingLocalTools = snapshot.localCompanionServices.filter((tool) => tool.status !== 'active');
|
|
1946
|
+
if (missingPackages.length === 0 && missingLocalTools.length === 0) {
|
|
1947
|
+
return { handled: false, installed: false };
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
const missingNames = [
|
|
1951
|
+
...missingPackages.map((pkg) => toolchainModule.formatGlobalToolchainServiceName(pkg)),
|
|
1952
|
+
...missingLocalTools.map((tool) => tool.displayName || tool.name),
|
|
1953
|
+
];
|
|
1954
|
+
console.log(`[${TOOL_NAME}] Missing companion tools: ${missingNames.join(', ')}.`);
|
|
1955
|
+
|
|
1956
|
+
const promptText = toolchainModule.buildMissingCompanionInstallPrompt(missingPackages, missingLocalTools);
|
|
1957
|
+
const approved = interactive
|
|
1958
|
+
? toolchainModule.promptYesNoStrict(promptText)
|
|
1959
|
+
: autoApproval;
|
|
1960
|
+
|
|
1961
|
+
if (!approved) {
|
|
1962
|
+
console.log(
|
|
1963
|
+
`[${TOOL_NAME}] Skipped companion install. Set GUARDEX_SKIP_COMPANION_PROMPT=1 to silence this prompt, ` +
|
|
1964
|
+
`or run '${getInvokedCliName()} setup --install-only' later to install manually.`,
|
|
1965
|
+
);
|
|
1966
|
+
return { handled: true, installed: false };
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
const result = toolchainModule.performCompanionInstall(missingPackages, missingLocalTools);
|
|
1970
|
+
if (result.status === 'installed') {
|
|
1971
|
+
console.log(
|
|
1972
|
+
`[${TOOL_NAME}] ✅ Companion tools installed (${(result.packages || []).join(', ')}).`,
|
|
1973
|
+
);
|
|
1974
|
+
return { handled: true, installed: true };
|
|
1975
|
+
}
|
|
1976
|
+
if (result.status === 'failed') {
|
|
1977
|
+
console.log(
|
|
1978
|
+
`[${TOOL_NAME}] ⚠️ Companion install failed: ${result.reason}. ` +
|
|
1979
|
+
`Retry with '${getInvokedCliName()} setup --install-only'.`,
|
|
1980
|
+
);
|
|
1981
|
+
return { handled: true, installed: false };
|
|
1982
|
+
}
|
|
1983
|
+
return { handled: true, installed: false };
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
function status(rawArgs) {
|
|
1987
|
+
const { found: verboseFlag, remaining: afterVerbose } = extractFlag(rawArgs, '--verbose');
|
|
1988
|
+
const options = parseCommonArgs(afterVerbose, {
|
|
1989
|
+
target: process.cwd(),
|
|
1990
|
+
json: false,
|
|
1991
|
+
});
|
|
1992
|
+
const forceCompact = envFlagIsTruthy(process.env.GUARDEX_COMPACT_STATUS);
|
|
1993
|
+
const forceExpand = envFlagIsTruthy(process.env.GUARDEX_VERBOSE_STATUS) || verboseFlag;
|
|
1994
|
+
const interactive = Boolean(process.stdout.isTTY);
|
|
1995
|
+
const invokedBasename = getInvokedCliName();
|
|
1996
|
+
|
|
1997
|
+
let snapshot = collectServicesSnapshot();
|
|
1998
|
+
if (!options.json) {
|
|
1999
|
+
const result = maybePromptInstallMissingCompanions(snapshot);
|
|
2000
|
+
if (result.installed) {
|
|
2001
|
+
snapshot = collectServicesSnapshot();
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
let { toolchain, npmServices, localCompanionServices, requiredSystemTools, services } = snapshot;
|
|
1716
2005
|
|
|
1717
2006
|
const targetPath = path.resolve(options.target);
|
|
1718
2007
|
const inGitRepo = isGitRepo(targetPath);
|
|
@@ -1752,18 +2041,27 @@ function status(rawArgs) {
|
|
|
1752
2041
|
if (options.json) {
|
|
1753
2042
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1754
2043
|
process.exitCode = 0;
|
|
1755
|
-
return;
|
|
2044
|
+
return payload;
|
|
1756
2045
|
}
|
|
1757
2046
|
|
|
2047
|
+
const allServicesActive = toolchain.ok && services.every((service) => service.status === 'active');
|
|
2048
|
+
const compact = !forceExpand && (forceCompact || (interactive && allServicesActive));
|
|
2049
|
+
|
|
1758
2050
|
console.log(`[${TOOL_NAME}] CLI: ${payload.cli.runtime}`);
|
|
1759
2051
|
if (!toolchain.ok) {
|
|
1760
2052
|
console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${toolchain.error}`);
|
|
1761
2053
|
}
|
|
1762
2054
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2055
|
+
if (compact) {
|
|
2056
|
+
console.log(
|
|
2057
|
+
`[${TOOL_NAME}] Global services: ${services.length}/${services.length} ${statusDot('active')} active`,
|
|
2058
|
+
);
|
|
2059
|
+
} else {
|
|
2060
|
+
console.log(`[${TOOL_NAME}] Global services:`);
|
|
2061
|
+
for (const service of services) {
|
|
2062
|
+
const serviceLabel = service.displayName || service.name;
|
|
2063
|
+
console.log(` - ${statusDot(service.status)} ${serviceLabel}: ${service.status}`);
|
|
2064
|
+
}
|
|
1767
2065
|
}
|
|
1768
2066
|
const inactiveOptionalCompanions = [...npmServices, ...localCompanionServices]
|
|
1769
2067
|
.filter((service) => service.status !== 'active')
|
|
@@ -1799,8 +2097,16 @@ function status(rawArgs) {
|
|
|
1799
2097
|
console.log(
|
|
1800
2098
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('inactive')} inactive (no git repository at target).`,
|
|
1801
2099
|
);
|
|
2100
|
+
const inactiveHint = deriveNextStepHint({
|
|
2101
|
+
scanResult: null,
|
|
2102
|
+
worktreeCount: 0,
|
|
2103
|
+
invoked: invokedBasename,
|
|
2104
|
+
inGitRepo,
|
|
2105
|
+
});
|
|
2106
|
+
console.log(`[${TOOL_NAME}] Next: ${inactiveHint}`);
|
|
2107
|
+
printToolLogsSummary({ invokedBasename, compact });
|
|
1802
2108
|
process.exitCode = 0;
|
|
1803
|
-
return;
|
|
2109
|
+
return payload;
|
|
1804
2110
|
}
|
|
1805
2111
|
|
|
1806
2112
|
if (scanResult.guardexEnabled === false) {
|
|
@@ -1809,9 +2115,23 @@ function status(rawArgs) {
|
|
|
1809
2115
|
);
|
|
1810
2116
|
console.log(`[${TOOL_NAME}] Repo: ${scanResult.repoRoot}`);
|
|
1811
2117
|
console.log(`[${TOOL_NAME}] Branch: ${scanResult.branch}`);
|
|
1812
|
-
|
|
2118
|
+
const worktreeCountDisabled = countAgentWorktrees(scanResult.repoRoot);
|
|
2119
|
+
if (worktreeCountDisabled > 0) {
|
|
2120
|
+
const plural = worktreeCountDisabled === 1 ? 'worktree' : 'worktrees';
|
|
2121
|
+
console.log(
|
|
2122
|
+
`[${TOOL_NAME}] ⚠ ${worktreeCountDisabled} active agent ${plural} under .omc/agent-worktrees or .omx/agent-worktrees.`,
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
const disabledHint = deriveNextStepHint({
|
|
2126
|
+
scanResult,
|
|
2127
|
+
worktreeCount: worktreeCountDisabled,
|
|
2128
|
+
invoked: invokedBasename,
|
|
2129
|
+
inGitRepo,
|
|
2130
|
+
});
|
|
2131
|
+
console.log(`[${TOOL_NAME}] Next: ${disabledHint}`);
|
|
2132
|
+
printToolLogsSummary({ invokedBasename, compact });
|
|
1813
2133
|
process.exitCode = 0;
|
|
1814
|
-
return;
|
|
2134
|
+
return payload;
|
|
1815
2135
|
}
|
|
1816
2136
|
|
|
1817
2137
|
if (scanResult.errors === 0 && scanResult.warnings === 0) {
|
|
@@ -1820,23 +2140,36 @@ function status(rawArgs) {
|
|
|
1820
2140
|
console.log(
|
|
1821
2141
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.warnings} warning(s)).`,
|
|
1822
2142
|
);
|
|
1823
|
-
console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' to review warning details.`);
|
|
1824
2143
|
} else if (scanResult.warnings === 0) {
|
|
1825
2144
|
console.log(
|
|
1826
2145
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.errors} error(s)).`,
|
|
1827
2146
|
);
|
|
1828
|
-
console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' for detailed findings.`);
|
|
1829
2147
|
} else {
|
|
1830
2148
|
console.log(
|
|
1831
2149
|
`[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.errors} error(s), ${scanResult.warnings} warning(s)).`,
|
|
1832
2150
|
);
|
|
1833
|
-
console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' for detailed findings.`);
|
|
1834
2151
|
}
|
|
2152
|
+
printStatusRepairHint(scanResult);
|
|
1835
2153
|
console.log(`[${TOOL_NAME}] Repo: ${scanResult.repoRoot}`);
|
|
1836
2154
|
console.log(`[${TOOL_NAME}] Branch: ${scanResult.branch}`);
|
|
1837
|
-
|
|
2155
|
+
const worktreeCountActive = countAgentWorktrees(scanResult.repoRoot);
|
|
2156
|
+
if (worktreeCountActive > 0) {
|
|
2157
|
+
const plural = worktreeCountActive === 1 ? 'worktree' : 'worktrees';
|
|
2158
|
+
console.log(
|
|
2159
|
+
`[${TOOL_NAME}] ⚠ ${worktreeCountActive} active agent ${plural} → ${invokedBasename} finish --all`,
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
const activeHint = deriveNextStepHint({
|
|
2163
|
+
scanResult,
|
|
2164
|
+
worktreeCount: worktreeCountActive,
|
|
2165
|
+
invoked: invokedBasename,
|
|
2166
|
+
inGitRepo,
|
|
2167
|
+
});
|
|
2168
|
+
console.log(`[${TOOL_NAME}] Next: ${activeHint}`);
|
|
2169
|
+
printToolLogsSummary({ invokedBasename, compact });
|
|
1838
2170
|
|
|
1839
2171
|
process.exitCode = 0;
|
|
2172
|
+
return payload;
|
|
1840
2173
|
}
|
|
1841
2174
|
|
|
1842
2175
|
function install(rawArgs) {
|
|
@@ -2580,6 +2913,9 @@ function setup(rawArgs) {
|
|
|
2580
2913
|
console.log(`[${TOOL_NAME}] ⚠️ ${warning}`);
|
|
2581
2914
|
}
|
|
2582
2915
|
}
|
|
2916
|
+
|
|
2917
|
+
maybePromptInstallVscodeExtension(options);
|
|
2918
|
+
|
|
2583
2919
|
const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
|
|
2584
2920
|
const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
|
|
2585
2921
|
if (missingSystemTools.length === 0) {
|
|
@@ -3246,13 +3582,14 @@ function protect(rawArgs) {
|
|
|
3246
3582
|
throw new Error(`Unknown protect subcommand: ${subcommand}`);
|
|
3247
3583
|
}
|
|
3248
3584
|
|
|
3249
|
-
function main() {
|
|
3585
|
+
async function main() {
|
|
3250
3586
|
const args = process.argv.slice(2);
|
|
3251
3587
|
|
|
3252
3588
|
if (args.length === 0) {
|
|
3253
3589
|
toolchainModule.maybeSelfUpdateBeforeStatus();
|
|
3254
3590
|
toolchainModule.maybeOpenSpecUpdateBeforeStatus();
|
|
3255
|
-
status([]);
|
|
3591
|
+
const statusPayload = status([]);
|
|
3592
|
+
await maybeAutoRunDoctorFromDefaultStatus(statusPayload);
|
|
3256
3593
|
return;
|
|
3257
3594
|
}
|
|
3258
3595
|
|
|
@@ -3322,9 +3659,9 @@ function main() {
|
|
|
3322
3659
|
throw new Error(`Unknown command: ${command}`);
|
|
3323
3660
|
}
|
|
3324
3661
|
|
|
3325
|
-
function runFromBin() {
|
|
3662
|
+
async function runFromBin() {
|
|
3326
3663
|
try {
|
|
3327
|
-
main();
|
|
3664
|
+
await main();
|
|
3328
3665
|
} catch (error) {
|
|
3329
3666
|
console.error(`[${TOOL_NAME}] ${error.message}`);
|
|
3330
3667
|
process.exitCode = 1;
|
|
@@ -3332,7 +3669,7 @@ function runFromBin() {
|
|
|
3332
3669
|
}
|
|
3333
3670
|
|
|
3334
3671
|
if (require.main === module) {
|
|
3335
|
-
runFromBin();
|
|
3672
|
+
void runFromBin();
|
|
3336
3673
|
}
|
|
3337
3674
|
|
|
3338
3675
|
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,
|