fb0309d357
Spec §4.1 + §4.2 — Phase 2 Task 14:
- tools/router-prehook.mjs:
- removed: ENFORCEMENT_TYPES + isEnforcementRequired (gate now uses
NON_BLOCKING_TASK_TYPES on state.classification.task_type — Task 13).
- buildStateFromClassification:
+ task_id: randomUUID() per turn (or caller-supplied taskId).
+ task_cost: {} placeholder (caller fills classifier_input/output_tokens
when available; LLM helper does not yet thread tokens through — task
17/20 will add).
+ inheritance: { inherited_from_task_id, inheritance_age_minutes } —
written only on continuation (source: 'prefilter_inherited'); copied
into the episode by observer-stop-hook in Task 16 (closes B5).
- dropped enforcementRequired field — Tool gate decides solely on
task_type + no_skill_found + skillInvokedThisTurn.
- main(): read prevState (~/.claude/runtime/router-state-<session>.json)
BEFORE overwrite; pass to classify({ prevState }); lift inheritance
from classification result into the new state when prefilter inherited.
- tools/router-prehook.test.mjs: rewritten — 9 tests covering v4 shape,
task_id randomness + override, inheritance present/absent, cost passthrough,
ENFORCEMENT_TYPES + isEnforcementRequired no longer exported, UTF-8 smoke.
Tests: 9/9 prehook PASS. Consumer regressions: router-tool-gate (25) +
router-classifier (44) = 69 PASS — no regressions.
79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { buildStateFromClassification } from './router-prehook.mjs';
|
|
|
|
describe('buildStateFromClassification — Phase 2 Task 14', () => {
|
|
it('builds full state object (v4 shape: task_id + task_cost, no enforcementRequired)', () => {
|
|
const cls = { task_type: 'feature', recommended_node: '#19', source: 'llm' };
|
|
const s = buildStateFromClassification(cls, { sessionId: 'abc', promptHash: '12345' });
|
|
expect(s.sessionId).toBe('abc');
|
|
expect(s.promptHash).toBe('12345');
|
|
expect(s.classification).toEqual(cls);
|
|
expect(s.skillInvokedThisTurn).toBe(false);
|
|
expect(s.chainProgress).toEqual([]);
|
|
expect(s.timestamp).toBeDefined();
|
|
expect(typeof s.task_id).toBe('string');
|
|
expect(s.task_cost).toEqual({});
|
|
expect(s.enforcementRequired).toBeUndefined();
|
|
});
|
|
|
|
it('emits a fresh task_id per call (random)', () => {
|
|
const cls = { task_type: 'feature' };
|
|
const a = buildStateFromClassification(cls, { sessionId: 's', promptHash: 'h' });
|
|
const b = buildStateFromClassification(cls, { sessionId: 's', promptHash: 'h' });
|
|
expect(a.task_id).not.toBe(b.task_id);
|
|
});
|
|
|
|
it('honors externally supplied taskId (caller wants determinism)', () => {
|
|
const s = buildStateFromClassification(
|
|
{ task_type: 'feature' },
|
|
{ sessionId: 's', promptHash: 'h', taskId: 'pinned-1' },
|
|
);
|
|
expect(s.task_id).toBe('pinned-1');
|
|
});
|
|
|
|
it('writes inheritance block on continuation (B5)', () => {
|
|
const s = buildStateFromClassification(
|
|
{ task_type: 'feature', source: 'prefilter_inherited' },
|
|
{ sessionId: 's', promptHash: 'h', inheritedFrom: 'prev', ageMin: 5 },
|
|
);
|
|
expect(s.inheritance.inherited_from_task_id).toBe('prev');
|
|
expect(s.inheritance.inheritance_age_minutes).toBe(5);
|
|
});
|
|
|
|
it('omits inheritance block when not a continuation', () => {
|
|
const s = buildStateFromClassification(
|
|
{ task_type: 'feature', source: 'llm' },
|
|
{ sessionId: 's', promptHash: 'h' },
|
|
);
|
|
expect(s.inheritance).toBeUndefined();
|
|
});
|
|
|
|
it('threads cost block through when caller provides it', () => {
|
|
const s = buildStateFromClassification(
|
|
{ task_type: 'feature' },
|
|
{ sessionId: 's', promptHash: 'h', cost: { classifier_input_tokens: 1234, classifier_output_tokens: 200 } },
|
|
);
|
|
expect(s.task_cost.classifier_input_tokens).toBe(1234);
|
|
expect(s.task_cost.classifier_output_tokens).toBe(200);
|
|
});
|
|
});
|
|
|
|
describe('ENFORCEMENT_TYPES legacy export removed (D1 closure)', () => {
|
|
it('does not export ENFORCEMENT_TYPES', async () => {
|
|
const mod = await import('./router-prehook.mjs');
|
|
expect(mod.ENFORCEMENT_TYPES).toBeUndefined();
|
|
});
|
|
|
|
it('does not export isEnforcementRequired', async () => {
|
|
const mod = await import('./router-prehook.mjs');
|
|
expect(mod.isEnforcementRequired).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('UTF-8 cyrillic stdin (regression — Stage 3 fix 1)', () => {
|
|
it('module loads with UTF-8 helper wired (smoke)', async () => {
|
|
const mod = await import('./router-prehook.mjs');
|
|
expect(typeof mod.buildStateFromClassification).toBe('function');
|
|
});
|
|
});
|