Files
portal/tools/router-stop-gate.test.mjs
T
Дмитрий 7c8223bf72 feat(router): Stop hook — chain progress tracking (stage 3 task 7)
После каждого хода обновляет state.chainProgress по реально вызванным
скилам. chainCompleted=true когда последний шаг достигнут.
skillInvokedThisTurn флажок для PreToolUse gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:07:20 +03:00

60 lines
2.7 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { updateChainProgress, extractSkillInvocations } from './router-stop-gate.mjs';
const chains = {
L1: { name: 'brainstorming chain', sequence: ['brainstorming', 'writing-plans', 'executing-plans'] },
L13: { name: 'finance chain', sequence: ['billing-audit', 'pest', 'ru-tax-accounting'] },
};
describe('extractSkillInvocations', () => {
it('extracts skill names from Skill tool invocations', () => {
const events = [
{ tool_name: 'Skill', tool_input: { skill: 'brainstorming' } },
{ tool_name: 'Edit', tool_input: {} },
{ tool_name: 'Skill', tool_input: { skill: 'writing-plans' } },
];
expect(extractSkillInvocations(events)).toEqual(['brainstorming', 'writing-plans']);
});
it('returns empty when no Skill invocations', () => {
expect(extractSkillInvocations([{ tool_name: 'Edit' }])).toEqual([]);
});
it('strips namespace prefix (superpowers:brainstorming → brainstorming)', () => {
const events = [{ tool_name: 'Skill', tool_input: { skill: 'superpowers:brainstorming' } }];
expect(extractSkillInvocations(events)).toEqual(['brainstorming']);
});
});
describe('updateChainProgress', () => {
it('appends matched chain step to chainProgress', () => {
const state = { classification: { recommendedChain: 'L1' }, chainProgress: [] };
const updated = updateChainProgress(state, ['brainstorming'], chains);
expect(updated.chainProgress).toEqual(['brainstorming']);
});
it('appends multiple steps if multiple skills invoked', () => {
const state = { classification: { recommendedChain: 'L1' }, chainProgress: [] };
const updated = updateChainProgress(state, ['brainstorming', 'writing-plans'], chains);
expect(updated.chainProgress).toEqual(['brainstorming', 'writing-plans']);
});
it('ignores skills not in chain sequence', () => {
const state = { classification: { recommendedChain: 'L1' }, chainProgress: [] };
const updated = updateChainProgress(state, ['random-skill'], chains);
expect(updated.chainProgress).toEqual([]);
});
it('marks chainCompleted=true when last step reached', () => {
const state = { classification: { recommendedChain: 'L1' }, chainProgress: ['brainstorming', 'writing-plans'] };
const updated = updateChainProgress(state, ['executing-plans'], chains);
expect(updated.chainCompleted).toBe(true);
});
it('preserves existing chainProgress without duplicates', () => {
const state = { classification: { recommendedChain: 'L1' }, chainProgress: ['brainstorming'] };
const updated = updateChainProgress(state, ['brainstorming', 'writing-plans'], chains);
expect(updated.chainProgress).toEqual(['brainstorming', 'writing-plans']);
});
});