diff --git a/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts b/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts index af50dbb2..4d360b10 100644 --- a/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts +++ b/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts @@ -32,6 +32,10 @@ import { compareSeverity, } from '../domain/rubrics/structural-review-rubric'; import type { CollectionContext, CollectionTarget } from '../domain/ports/quality-signal-provider.port'; +import { DEFAULT_SKILLS } from '../adapters/skills/default-skills'; +import { buildEvaluationContext } from '../application/context-mapper'; +import { parseAgentRuntimeRequest } from '../domain/contracts/agent-runtime-request'; +import type { SkillDescriptor } from '../domain/contracts/capability'; /** A deterministic stub reviewer standing in for the probabilistic LLM/agent. */ function stubReviewer(findings: readonly RawStructuralFinding[]): IStructuralReviewer { @@ -208,3 +212,38 @@ describe('Structural Quality Gate (deterministic severity → decision)', () => expect(decideForSeverity('medium', DEFAULT_STRUCTURAL_GATE_POLICY)).toBe('warn'); }); }); + +/** + * Kind routing (GT-535) — the structural-review skill must forward a canonical + * EvaluationKind through buildEvaluationContext, NOT be dropped to the 'gate' + * fallback. Regression guard: 'code-quality' is a quality-signal DIMENSION, not a + * kind; the skill declares the canonical 'evidence' kind so the provider stays + * reachable when the IStructuralReviewer adapter lands. + */ +describe('structural-review skill kind routing (GT-535)', () => { + const skill = DEFAULT_SKILLS.find((s) => s.id === 'code-quality-structural-review'); + const req = parseAgentRuntimeRequest({ tenant: 't-1', intent: 'structural_review' }); + + it('declares a canonical evaluation kind (not the code-quality dimension)', () => { + expect(skill).toBeDefined(); + expect(skill?.evaluationKinds).toEqual(['evidence']); + // 'code-quality' is the Evidence DIMENSION, never an EvaluationKind. + expect(skill?.evaluationKinds).not.toContain('code-quality'); + }); + + it('forwards the declared kind through buildEvaluationContext (not the gate fallback)', () => { + const ctx = buildEvaluationContext(req, skill as SkillDescriptor); + expect(ctx.kinds).toEqual(['evidence']); + // Guard the regression: an unknown/dimension-shaped kind would collapse to ['gate']. + expect(ctx.kinds).not.toEqual(['gate']); + }); + + it('drops a non-canonical kind to the gate fallback (documents the filter)', () => { + const bogus: SkillDescriptor = { + ...(skill as SkillDescriptor), + evaluationKinds: ['code-quality'], + }; + const ctx = buildEvaluationContext(req, bogus); + expect(ctx.kinds).toEqual(['gate']); + }); +}); diff --git a/src/packages/agent-runtime/src/adapters/skills/default-skills.ts b/src/packages/agent-runtime/src/adapters/skills/default-skills.ts index 8f96bbc1..40c192f7 100644 --- a/src/packages/agent-runtime/src/adapters/skills/default-skills.ts +++ b/src/packages/agent-runtime/src/adapters/skills/default-skills.ts @@ -93,8 +93,14 @@ export const DEFAULT_SKILLS: readonly SkillDescriptor[] = [ intents: ['structural_review', 'code_quality_review', 'review_structure'], // Orchestration runs the probabilistic reviewer behind IStructuralReviewer and // hands the normalized Evidence to the Core as a code-quality signal (GT-535). + // NOTE: 'code-quality' is the quality-signal DIMENSION the emitted Evidence is + // tagged with (StructuralReviewProvider.DEFAULT_DIMENSION), NOT an EvaluationKind. + // The canonical EvaluationKind for declared quality-signal Evidence is 'evidence' + // (ADR-0111 / GT-533). Declaring it here keeps the kind routed by + // buildEvaluationContext instead of being dropped to the 'gate' fallback, so the + // StructuralReviewProvider stays reachable when the IStructuralReviewer adapter lands. kind: 'evaluation', - evaluationKinds: ['code-quality'], + evaluationKinds: ['evidence'], permissions: ['read:repo'], requiresApproval: false, emitsTrace: true, diff --git a/src/packages/agent-runtime/src/application/context-mapper.ts b/src/packages/agent-runtime/src/application/context-mapper.ts index e0da69dd..b8b55718 100644 --- a/src/packages/agent-runtime/src/application/context-mapper.ts +++ b/src/packages/agent-runtime/src/application/context-mapper.ts @@ -35,9 +35,18 @@ export function toPhaseId(phase?: string): PhaseIdT | undefined { return (CANONICAL_PHASES as readonly string[]).includes(v) ? (v as PhaseIdT) : undefined; } +/** + * Allowlist of canonical evaluation kinds a skill may forward to the Core. It MUST + * mirror the core-domain `EvaluationKind` union (evaluation-context.ts) — a skill + * that declares a kind absent from this list is silently dropped to the 'gate' + * fallback (that is the bug GT-535 hit: 'code-quality' is a quality-signal + * DIMENSION, not a kind). Kept as a literal (not imported) because this file is a + * type-only consumer of core-domain — the hexagon carries no runtime dependency. + */ const KNOWN_KINDS: readonly string[] = [ 'gate', 'artifact', 'evidence', 'architecture', 'blueprint', 'topology', 'checkpoint', 'deployment', 'rule', 'compliance', + 'design', 'phase-artifacts', ]; /** Build a canonical EvaluationContext from a runtime request + resolved skill. */