Files
ruvnet__ruflo/v3/@claude-flow/cli/__tests__/hermes-tier1.test.ts
rUv c983c0d809 feat(intelligence): Hermes-Agent Tier-1 adoptions — prompt caching, reasoning scrub, tool-loop breaker (3.10.10) (#2237)
From the deep-research map of NousResearch/Hermes-Agent vs ruflo (3 small,
high-confidence, in-repo adoptions; the rest were SKIP/ADR-gated).

- #8 Prompt caching: agent_execute's direct Anthropic call (agent-execute-core.ts
  — the path the cli actually uses) now marks the system prompt as an ephemeral
  cache breakpoint (~90% discount on cached input tokens, 5-min TTL). Also applied
  the system+trailing-message breakpoint strategy to @claude-flow/providers'
  anthropic-provider (separate package; gated by new config.promptCache, default on).
- #14 Reasoning-tag scrub: scrubReasoningBlocks() strips <think>/<thinking>/
  <reasoning>/<REASONING_SCRATCHPAD> blocks from trajectory action/result before
  DISTILL, so extended-thinking tokens don't contaminate pattern embeddings.
  Boundary-gated (leaves prose that merely mentions the tags untouched).
- #6 Tool-loop circuit breaker: new tool-loop-guardrail.ts; post-command records
  (command, success), pre-command warns at 3 / blocks at 5 consecutive failures of
  the same command with a recovery hint. Orthogonal to the security
  tool-output-guardrail (injection). Advisory by default.

Note: @claude-flow/providers is NOT consumed by the cli (separate published
package), so the cli-effective #8 fix is in agent-execute-core.ts; the providers
change ships on its own cadence.

Verification: cli + providers build clean; 29/29 targeted tests pass
(hermes-tier1 6 + bandit 8 + bug-cluster 15). Deep-research map also produced 5
corrections to ADR-113 (#1907).
2026-05-29 14:57:19 -04:00

71 lines
3.0 KiB
TypeScript

/**
* Hermes-Agent Tier-1 adoptions (see docs/reviews/intelligence-system-audit-2026-05-29.md
* → Hermes capability map):
* #14 — reasoning-tag scrubbing keeps extended-thinking blocks out of the
* learning trajectory (so DISTILL doesn't embed reasoning tokens).
* #6 — tool-loop circuit breaker warns/blocks on repeated consecutive
* failures of the same command.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { scrubReasoningBlocks } from '../src/mcp-tools/hooks-tools.js';
import {
checkCommandLoop,
recordCommandOutcome,
_resetLoopHistory,
} from '../src/mcp-tools/tool-loop-guardrail.js';
describe('#14 — reasoning-tag scrubbing', () => {
it('strips paired thinking/reasoning blocks', () => {
expect(scrubReasoningBlocks('did it <think>secret CoT</think> ok')).toBe('did it ok');
expect(scrubReasoningBlocks('a<thinking>x</thinking>b')).toBe('ab');
expect(scrubReasoningBlocks('a<reasoning>y</reasoning>b')).toBe('ab');
expect(scrubReasoningBlocks('a<REASONING_SCRATCHPAD>z</REASONING_SCRATCHPAD>b')).toBe('ab');
});
it('leaves prose that merely mentions the tag untouched (boundary-gated)', () => {
expect(scrubReasoningBlocks('We document the <think> tag here')).toBe('We document the <think> tag here');
});
it('is a no-op on tag-free text and non-strings', () => {
expect(scrubReasoningBlocks('plain output')).toBe('plain output');
expect(scrubReasoningBlocks(undefined as unknown as string)).toBe(undefined);
});
});
describe('#6 — tool-loop circuit breaker', () => {
beforeEach(() => _resetLoopHistory());
it('allows until the warn threshold, then warns, then blocks', () => {
const cmd = 'npm run flaky';
expect(checkCommandLoop(cmd).verdict).toBe('allow');
recordCommandOutcome(cmd, false);
recordCommandOutcome(cmd, false);
expect(checkCommandLoop(cmd).verdict).toBe('allow'); // 2 fails
recordCommandOutcome(cmd, false);
expect(checkCommandLoop(cmd).verdict).toBe('warn'); // 3 fails
recordCommandOutcome(cmd, false);
recordCommandOutcome(cmd, false);
const v = checkCommandLoop(cmd);
expect(v.verdict).toBe('block'); // 5 fails
expect(v.consecutiveFailures).toBe(5);
expect(v.hint).toMatch(/failed 5/);
});
it('a success resets the streak', () => {
const cmd = 'pytest';
for (let i = 0; i < 5; i++) recordCommandOutcome(cmd, false);
expect(checkCommandLoop(cmd).verdict).toBe('block');
recordCommandOutcome(cmd, true);
expect(checkCommandLoop(cmd).verdict).toBe('allow');
});
it('interleaved other commands do not break the streak (exact-match only)', () => {
recordCommandOutcome('build', false);
recordCommandOutcome('ls', true); // different command
recordCommandOutcome('build', false);
recordCommandOutcome('git status', true);
recordCommandOutcome('build', false);
expect(checkCommandLoop('build').verdict).toBe('warn'); // 3 build failures, ls/git ignored
expect(checkCommandLoop('ls').verdict).toBe('allow');
});
});