Files
ruvnet__ruflo/v3/@claude-flow/cli/__tests__/agent-logs-hive-resolution.test.ts
rUv 31f22ff9a9 fix(#1916): register agent_logs + hive worker resolution + all 20 dangling CLI↔MCP tools — published 3.7.0-alpha.26 (#1924)
* fix(#1916): register agent_logs MCP tool + resolve hive-spawned workers

`ruflo agent logs <id>` called `callMCPTool('agent_logs', …)` against a tool
that was never registered → `MCP tool not found: agent_logs`. Registered it
in agent-tools.ts. Today it returns the agent's createdAt + lastResult as
synthetic entries (per-agent activity logging lands with hive worker execution
wiring — #1916 items 3/4, a tracked follow-up) with the documented shape
`{ agentId, entries: [{timestamp,level,message,context?}], total }`.

Also: `hive-mind_spawn` writes workers to `.claude-flow/agents.json`, a
different file from the canonical `.claude-flow/agents/store.json` that
agent_status/agent_list/agent_logs read — so `agent status <hive-worker-id>`
returned `not_found`. Added `loadAllAgents()` (canonical store ∪ hive store,
canonical wins on collision) and routed those three tools through it.

NOT in this PR: the task-execution dispatch (#1916 items 3/4 — `task_assign`
transitions state but doesn't run a worker; hive metrics stay 0). That needs
a design decision (which executor) and is a separate change.

CI guard: scripts/audit-cli-mcp-tools.mjs — scans every `callMCPTool('<name>')`
in src/commands/*.ts and fails if `<name>` isn't registered in src/mcp-tools/*.ts
(the `MCP tool not found` class). Monotone-decreasing baseline at
verification/cli-mcp-tool-baseline.json — surfaces 20 *other* CLI subcommands
with the same dangling-tool bug (`memory export`, `task retry`, `workflow
validate`, `session export`, …; agent_logs is now fixed and not in the list).
Wired into v3-ci.yml `tool-descriptions-audit` job (already in witness-verify
needs[]). Behavioral guard: __tests__/agent-logs-hive-resolution.test.ts.
Witness markers for #1916 + #1916-guard.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(#1916): register the 16 missing CLI↔MCP tools — cli-mcp-tool-baseline → 0

The #1916-guard (scripts/audit-cli-mcp-tools.mjs) surfaced 20 `ruflo <cmd>`
subcommands that callMCPTool() a tool that was never registered (would die
with `MCP tool not found`). Cleared all of them:

- Renamed the coverage tools `hooks/coverage-{route,suggest,gaps}` →
  `hooks_coverage-{route,suggest,gaps}` (the handlers already existed; the
  slash names just didn't match the CLI's underscore calls).
- Pointed `ruflo swarm stop` / `ruflo stop` at the existing `swarm_shutdown`
  tool instead of a non-existent `swarm_stop`.
- Registered 16 new MCP tools (each with ADR-112 "use when … is wrong" guidance):
    task_retry, workflow_stop, workflow_validate,
    session_current, session_export, session_import,
    memory_detailed-stats, memory_cleanup, memory_compress, memory_export, memory_import,
    hive-mind_optimize-memory, hooks_teammate-idle, hooks_task-completed,
    mcp_start, mcp_stop
  Real where the operation maps cleanly to an existing store/backend
  (task_retry clones into a fresh pending task; memory_export/import dump &
  re-store entries; memory_cleanup prunes expired TTLs dry-run-by-default;
  workflow_validate structurally checks a JSON workflow; session_export/import
  round-trip the session record; memory_detailed-stats reshapes memory_stats).
  Honest no-ops with the correct return shape + a `note` where the underlying
  capability genuinely isn't wired yet (memory_compress — sql.js has no on-disk
  compression; hive-mind_optimize-memory — structural compaction only,
  pattern-quality consolidation is the intelligence pipeline's job; hooks_*
  team hooks — auto-assignment/pattern-learning delegated; mcp_start/stop — the
  MCP toolset runs in-process, there's no separate server to start/stop).

cli-mcp-tool-baseline.json is now 0. tool-descriptions-audit still passes
(all 16 new tools carry guidance, ≥80 chars, unique). Full cli test suite
green (48 files / 1977 tests). Witness #1916-cli-tools marker added →
ruflo verify 107 verified, 0 regressed. mcp-tools-deep.test.ts exception
list updated for mcp_start/mcp_stop.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(#1916): register coverageRouterTools + harden the CLI↔MCP audit; bump 3.7.0-alpha.26

Validating the alpha.25 publish caught a gap: `ruflo hooks coverage-route`
(and -suggest / -gaps) still failed with `Tool not found: hooks_coverage-route`.
Root cause: the coverage tools were defined in ruvector/coverage-tools.ts but
their array was never imported into mcp-client.ts's TOOL_REGISTRY — renaming
the slash→underscore name didn't help because the file wasn't registered at all.

- mcp-client.ts: import + `...coverageRouterTools` in registerTools([…]).
- coverage-tools.ts: category 'coverage' → 'hooks' (matches the hooks_* names).
- scripts/audit-cli-mcp-tools.mjs: now derives the "registered" set from what
  mcp-client.ts ACTUALLY spreads into registerTools([…]) (resolving each
  `...xTools` to its `import { xTools } from '<path>'` and scanning that file),
  instead of "any src file with a name: definition". So an unregistered
  tool-source file now correctly produces a dangling-reference flag — this is
  exactly the gap that let hooks_coverage-* slip through.

Version bumped to 3.7.0-alpha.26 (alpha.25 shipped the still-broken coverage
tools). Witness manifest regenerated. cli-mcp-tool-baseline.json still 0;
audit + mcp-tools-deep + mcp-client tests green.

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-12 00:40:47 -04:00

112 lines
4.3 KiB
TypeScript

/**
* Regression guard for ruvnet/ruflo#1916.
*
* (a) `agent_logs` MCP tool must be registered — the `ruflo agent logs <id>`
* CLI subcommand called `callMCPTool('agent_logs', …)` against a tool
* that didn't exist → `MCP tool not found: agent_logs`.
* (b) `agent_status` / `agent_list` / `agent_logs` must resolve hive-mind-
* spawned workers — `hive-mind_spawn` writes workers to
* `.claude-flow/agents.json`, a *different* file from the canonical
* `.claude-flow/agents/store.json` the agent tools read, so
* `agent status <hive-worker-id>` returned `not_found`.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { agentTools } from '../src/mcp-tools/agent-tools.js';
const tool = (name: string) => {
const t = agentTools.find(t => t.name === name);
if (!t) throw new Error(`MCP tool not registered: ${name}`);
return t;
};
describe('#1916 — agent_logs registered + hive-worker resolution', () => {
let dir: string;
let prevCwd: string | undefined;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'ruflo-1916-'));
prevCwd = process.env.CLAUDE_FLOW_CWD;
process.env.CLAUDE_FLOW_CWD = dir;
// hive-mind_spawn writes here:
mkdirSync(join(dir, '.claude-flow'), { recursive: true });
writeFileSync(
join(dir, '.claude-flow', 'agents.json'),
JSON.stringify({
agents: {
'hive-worker-1716-abcd': {
agentId: 'hive-worker-1716-abcd',
agentType: 'worker',
status: 'idle',
health: 1.0,
taskCount: 0,
config: { role: 'worker', hiveRole: 'worker' },
createdAt: '2026-05-11T00:00:00.000Z',
domain: 'hive-mind',
},
},
}),
);
});
afterEach(() => {
if (prevCwd === undefined) delete process.env.CLAUDE_FLOW_CWD;
else process.env.CLAUDE_FLOW_CWD = prevCwd;
rmSync(dir, { recursive: true, force: true });
});
it('(a) agent_logs is a registered MCP tool', () => {
expect(agentTools.some(t => t.name === 'agent_logs')).toBe(true);
});
it('(a) agent_logs returns the documented shape for an unknown agent (no throw)', async () => {
const res = await (tool('agent_logs').handler as any)({ agentId: 'agent-does-not-exist' });
expect(res).toMatchObject({ agentId: 'agent-does-not-exist', entries: [], total: 0, error: 'Agent not found' });
});
it('(b) agent_status resolves a hive-mind-spawned worker', async () => {
const res = await (tool('agent_status').handler as any)({ agentId: 'hive-worker-1716-abcd' });
expect(res.status).not.toBe('not_found');
expect(res).toMatchObject({ agentId: 'hive-worker-1716-abcd', agentType: 'worker', status: 'idle' });
});
it('(b) agent_list includes hive-mind-spawned workers', async () => {
const res = await (tool('agent_list').handler as any)({});
expect(res.agents.map((a: any) => a.agentId)).toContain('hive-worker-1716-abcd');
});
it('(b) agent_logs resolves a hive-mind-spawned worker and returns entries', async () => {
const res = await (tool('agent_logs').handler as any)({ agentId: 'hive-worker-1716-abcd' });
expect(res.error).toBeUndefined();
expect(res.agentId).toBe('hive-worker-1716-abcd');
expect(Array.isArray(res.entries)).toBe(true);
expect(res.entries.length).toBeGreaterThan(0); // at least the synthetic "agent created" entry
});
it('canonical store wins on id collision', async () => {
// Same id in the canonical store with a richer record:
mkdirSync(join(dir, '.claude-flow', 'agents'), { recursive: true });
writeFileSync(
join(dir, '.claude-flow', 'agents', 'store.json'),
JSON.stringify({
version: '3.0.0',
agents: {
'hive-worker-1716-abcd': {
agentId: 'hive-worker-1716-abcd',
agentType: 'coder',
status: 'busy',
health: 0.9,
taskCount: 3,
config: {},
createdAt: '2026-05-11T01:00:00.000Z',
},
},
}),
);
const res = await (tool('agent_status').handler as any)({ agentId: 'hive-worker-1716-abcd' });
expect(res).toMatchObject({ agentType: 'coder', status: 'busy', taskCount: 3 });
});
});