Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions src/cli/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -2107,29 +2110,37 @@ 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) {
const tools = missingSystemTools
.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}`);
}
}
}

Expand Down
7 changes: 0 additions & 7 deletions src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion templates/AGENTS.multiagent-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 2 additions & 3 deletions test/doctor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <managed-path> rewrites only the named managed shim', () => {
Expand Down
1 change: 1 addition & 0 deletions test/prompt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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\./);
});
Expand Down
5 changes: 2 additions & 3 deletions test/setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});

});
51 changes: 46 additions & 5 deletions test/status.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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);
});


Expand Down
Loading