Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/packages/agent-runtime/src/__tests__/structural-review.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate qualitySignals before routing structural review

When the default structural_review skill is invoked, this change only sends kinds: ['evidence'] to Core; the runtime path just calls buildEvaluationContext(...) and coreEvaluation.evaluate(...) (agent-runtime.service.ts:269-270), while Core folds only ctx.qualitySignals (evaluation-orchestrator.service.ts:63) and has no evidence evaluator. Because buildEvaluationContext never collects or attaches the StructuralReviewProvider output, the structural-review request still evaluates with no code-quality evidence and never applies the structural gate, so the advertised review remains unreachable for this default skill.

Useful? React with 👍 / 👎.

permissions: ['read:repo'],
requiresApproval: false,
emitsTrace: true,
Expand Down
9 changes: 9 additions & 0 deletions src/packages/agent-runtime/src/application/context-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down