From fb7ed9c70a65ef877798d8190805790f71051d41 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Tue, 5 May 2026 08:42:22 +0200 Subject: [PATCH] Reduce noisy Guardex status output for agents Agent sessions read gx status frequently, so compact mode should summarize dependency drift without dumping long npm stderr or every inactive companion name. This keeps verbose output available for human diagnosis, keeps RTK as a required command-compression dependency, and preserves the FFF MCP file-search instruction in the multiagent safety template without making fff-mcp a hard system gate. Constraint: Compact status must remain useful as a default agent startup surface Rejected: Make fff-mcp a required system tool | current clients may not expose it, and hard-gating would add noisy failures Confidence: high Scope-risk: narrow Directive: Keep compact status short; put dependency detail behind --verbose Tested: live ghx smoke: 2 repo view requests produced 1 miss, 1 hit, cache size 1 Tested: rtk test node --test test/status.test.js test/setup.test.js test/doctor.test.js test/prompt.test.js Not-tested: repo-wide node --test suite --- src/cli/main.js | 43 +++++++++++++--------- src/context.js | 7 ---- templates/AGENTS.multiagent-safety.md | 2 +- test/doctor.test.js | 5 ++- test/prompt.test.js | 1 + test/setup.test.js | 5 ++- test/status.test.js | 51 ++++++++++++++++++++++++--- 7 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/cli/main.js b/src/cli/main.js index 8db9e1a..75cc39d 100755 --- a/src/cli/main.js +++ b/src/cli/main.js @@ -2086,7 +2086,10 @@ function status(rawArgs) { console.log(`[${TOOL_NAME}] CLI: ${payload.cli.runtime}`); if (!toolchain.ok) { - console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${toolchain.error}`); + const detectionError = compact + ? String(toolchain.error || '').split(/\r?\n/).find(Boolean) || 'unknown error' + : toolchain.error; + console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${detectionError}`); } if (compact) { @@ -2107,19 +2110,25 @@ function status(rawArgs) { .filter((service) => service.status !== 'active') .map((service) => service.displayName || service.name); if (inactiveOptionalCompanions.length > 0) { - console.log( - `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.join(', ')}`, - ); - for (const warning of toolchainModule.describeMissingGlobalDependencyWarnings( - npmServices - .filter((service) => service.status === 'inactive') - .map((service) => service.packageName), - )) { - console.log(`[${TOOL_NAME}] ${warning}`); + if (compact) { + console.log( + `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.length} (run '${SHORT_TOOL_NAME} setup')`, + ); + } else { + console.log( + `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.join(', ')}`, + ); + for (const warning of toolchainModule.describeMissingGlobalDependencyWarnings( + npmServices + .filter((service) => service.status === 'inactive') + .map((service) => service.packageName), + )) { + console.log(`[${TOOL_NAME}] ${warning}`); + } + console.log( + `[${TOOL_NAME}] Run '${SHORT_TOOL_NAME} setup' to install missing companions with an explicit Y/N prompt.`, + ); } - console.log( - `[${TOOL_NAME}] Run '${SHORT_TOOL_NAME} setup' to install missing companions with an explicit Y/N prompt.`, - ); } const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active'); if (missingSystemTools.length > 0) { @@ -2127,9 +2136,11 @@ function status(rawArgs) { .map((tool) => tool.displayName || tool.name) .join(', '); console.log(`[${TOOL_NAME}] ⚠️ Missing required system tool(s): ${tools}`); - for (const tool of missingSystemTools) { - const reasonText = tool.reason ? ` (${tool.reason})` : ''; - console.log(` - install ${tool.name}: ${tool.installHint}${reasonText}`); + if (!compact) { + for (const tool of missingSystemTools) { + const reasonText = tool.reason ? ` (${tool.reason})` : ''; + console.log(` - install ${tool.name}: ${tool.installHint}${reasonText}`); + } } } diff --git a/src/context.js b/src/context.js index e4f0468..4ca4e1c 100644 --- a/src/context.js +++ b/src/context.js @@ -76,7 +76,6 @@ function resolveGithubCliBin(env = process.env) { const GH_BIN = resolveGithubCliBin(); const RTK_BIN = process.env.GUARDEX_RTK_BIN || 'rtk'; -const FFF_MCP_BIN = process.env.GUARDEX_FFF_MCP_BIN || 'fff-mcp'; const REQUIRED_SYSTEM_TOOLS = [ { name: 'gh', @@ -90,12 +89,6 @@ const REQUIRED_SYSTEM_TOOLS = [ command: RTK_BIN, installHint: 'Install RTK and ensure `rtk` is on PATH.', }, - { - name: 'fff-mcp', - displayName: 'FFF MCP (fff-mcp)', - command: FFF_MCP_BIN, - installHint: 'https://github.com/dmtrKovalenko/fff.nvim', - }, ]; const MAINTAINER_RELEASE_REPO = path.resolve( process.env.GUARDEX_RELEASE_REPO || PACKAGE_ROOT, diff --git a/templates/AGENTS.multiagent-safety.md b/templates/AGENTS.multiagent-safety.md index b9f0ea9..badc28f 100644 --- a/templates/AGENTS.multiagent-safety.md +++ b/templates/AGENTS.multiagent-safety.md @@ -152,7 +152,7 @@ When `rtk` is available, prefer it for noisy shell discovery and verification. Use the fff MCP tools for all file search operations instead of default tools. -If `fff-mcp` is missing, run `gx setup` or `gx doctor` and follow the required system tool warning before falling back to repo-local search. +If fff MCP tools are unavailable in the current client, fall back to `rtk grep`, `rtk ls`, or `rg` and keep output compact. ### Caveman style diff --git a/test/doctor.test.js b/test/doctor.test.js index c4f3ca6..8aad6d9 100644 --- a/test/doctor.test.js +++ b/test/doctor.test.js @@ -68,13 +68,12 @@ test('doctor warns when required system tool dependencies are missing', () => { const result = runNodeWithEnv(['doctor', '--target', repoDir], repoDir, { GUARDEX_RTK_BIN: 'rtk-command-not-found-for-test', - GUARDEX_FFF_MCP_BIN: 'fff-mcp-command-not-found-for-test', }); assert.equal(result.status, 0, result.stderr || result.stdout); - assert.match(result.stdout, /Missing required system tool\(s\): rtk, fff-mcp/); + assert.match(result.stdout, /Missing required system tool\(s\): rtk/); assert.match(result.stdout, /Install rtk: Install RTK and ensure `rtk` is on PATH\./); - assert.match(result.stdout, /Install fff-mcp: https:\/\/github\.com\/dmtrKovalenko\/fff\.nvim/); + assert.doesNotMatch(result.stdout, /fff-mcp/); }); test('doctor --force rewrites only the named managed shim', () => { diff --git a/test/prompt.test.js b/test/prompt.test.js index 72b9bc1..ec47d8d 100644 --- a/test/prompt.test.js +++ b/test/prompt.test.js @@ -166,6 +166,7 @@ test('prompt --snippet prints the managed AGENTS template with token budget and assert.match(result.stdout, /Do not wrap machine-readable commands with RTK/); assert.match(result.stdout, /### FFF file search/); assert.match(result.stdout, /Use the fff MCP tools for all file search operations instead of default tools\./); + assert.match(result.stdout, /If fff MCP tools are unavailable in the current client/); assert.match(result.stdout, /### Caveman style/); assert.match(result.stdout, /Answer order stays fixed: answer first, cause next, fix or next step last\./); }); diff --git a/test/setup.test.js b/test/setup.test.js index d88e7d7..442c755 100644 --- a/test/setup.test.js +++ b/test/setup.test.js @@ -1433,14 +1433,13 @@ exit 1 GUARDEX_HOME_DIR: fakeHome, GUARDEX_GH_BIN: 'gh-command-not-found-for-test', GUARDEX_RTK_BIN: 'rtk-command-not-found-for-test', - GUARDEX_FFF_MCP_BIN: 'fff-mcp-command-not-found-for-test', }); assert.equal(result.status, 0, result.stderr || result.stdout); - assert.match(result.stdout, /Missing required system tool\(s\): gh, rtk, fff-mcp/); + assert.match(result.stdout, /Missing required system tool\(s\): gh, rtk/); assert.match(result.stdout, /https:\/\/cli\.github\.com\//); assert.match(result.stdout, /Install rtk: Install RTK and ensure `rtk` is on PATH\./); - assert.match(result.stdout, /Install fff-mcp: https:\/\/github\.com\/dmtrKovalenko\/fff\.nvim/); + assert.doesNotMatch(result.stdout, /fff-mcp/); }); }); diff --git a/test/status.test.js b/test/status.test.js index cc7c562..6eb884d 100644 --- a/test/status.test.js +++ b/test/status.test.js @@ -110,6 +110,24 @@ test('status suppresses the full help tree by default and emits a Next hint', () }); +test('compact status prints only the first global-service detection error line', () => { + const repoDir = initRepo(); + const fakeNpm = createFakeNpmScript(` +echo "first npm failure line" >&2 +echo "second npm failure line" >&2 +exit 1 +`); + + const result = runNodeWithEnv(['status', '--target', repoDir], repoDir, { + GUARDEX_NPM_BIN: fakeNpm, + }); + + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /Could not detect global services: first npm failure line/); + assert.doesNotMatch(result.stdout, /second npm failure line/); +}); + + test('--verbose forces the expanded services list even when every service is active', () => { const repoDir = initRepo(); @@ -523,6 +541,32 @@ exit 1 }); +test('compact status summarizes inactive optional companions without dependency detail', () => { + const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-status-target-')); + const fakeHome = createGuardexCompanionHome({ cavekit: true, caveman: true }); + const fakeNpm = createFakeNpmScript(` +if [[ "$1" == "list" ]]; then + cat <<'JSON' +{"dependencies":{"oh-my-codex":{"version":"1.0.0"},"@fission-ai/openspec":{"version":"1.0.0"},"@imdeadpool/colony-cli":{"version":"1.0.0"},"@imdeadpool/codex-account-switcher":{"version":"1.0.0"}}} +JSON + exit 0 +fi +echo "unexpected npm args: $*" >&2 +exit 1 +`); + + const result = runNodeWithEnv(['status', '--target', targetDir], targetDir, { + GUARDEX_NPM_BIN: fakeNpm, + GUARDEX_HOME_DIR: fakeHome, + }); + + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.match(result.stdout, /Optional companion tools inactive: 1 \(run 'gx setup'\)/); + assert.doesNotMatch(result.stdout, /oh-my-claudecode: inactive/); + assert.doesNotMatch(result.stdout, /Yeachan-Heo\/oh-my-claudecode/); +}); + + test('status detects local cavekit and caveman companion installs', () => { const repoDir = initRepo(); const fakeHome = createGuardexCompanionHome({ cavekit: true, caveman: true }); @@ -562,21 +606,18 @@ test('status reports gh dependency as inactive when gh is unavailable', () => { assert.equal(ghService.status, 'inactive'); }); -test('status reports rtk and fff-mcp dependencies as inactive when unavailable', () => { +test('status reports rtk dependency as inactive when unavailable', () => { const repoDir = initRepo(); const result = runNodeWithEnv(['status', '--target', repoDir, '--json'], repoDir, { GUARDEX_RTK_BIN: 'rtk-command-not-found-for-test', - GUARDEX_FFF_MCP_BIN: 'fff-mcp-command-not-found-for-test', }); assert.equal(result.status, 0, result.stderr || result.stdout); const payload = JSON.parse(result.stdout); const rtkService = payload.services.find((service) => service.name === 'rtk'); - const fffService = payload.services.find((service) => service.name === 'fff-mcp'); assert.ok(rtkService, 'rtk service should be included in status payload'); - assert.ok(fffService, 'fff-mcp service should be included in status payload'); assert.equal(rtkService.status, 'inactive'); - assert.equal(fffService.status, 'inactive'); + assert.equal(payload.services.some((service) => service.name === 'fff-mcp'), false); });