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); });