From 3ba7447ddb3ffd59994e5aaba13f764f45dc4cda Mon Sep 17 00:00:00 2001 From: "devnexus-automation[bot]" <286661136+devnexus-automation[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 11:19:14 +0200 Subject: [PATCH] Add TypeScript quality delta handoff payloads --- README.md | 13 +++- skills/typescript-quality-feedback/SKILL.md | 6 +- src/devNexusTypeScriptPlugin.ts | 2 + src/typeScriptQualityFeedback.test.ts | 66 +++++++++++++++++++ src/typeScriptQualityFeedback.ts | 70 +++++++++++++++++++++ 5 files changed, 153 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a67843..046436d 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ import { readTypeScriptQualitySnapshot, traceTypeScriptDiagnostics, traceTypeScriptProjectStatus, + typeScriptQualityDeltaCoordinationPayload, } from "@evref-bl/dev-nexus-typescript"; const status = traceTypeScriptProjectStatus({ projectRoot }); @@ -132,6 +133,9 @@ const delta = compareTypeScriptQualitySnapshots({ after, touchedFiles: ["src/index.ts"], }); +const handoffDelta = typeScriptQualityDeltaCoordinationPayload(delta, { + sourcePath: ".quality/sonar/delta.json", +}); ``` The project-status operation reports setup inventory, available scripts, @@ -164,9 +168,12 @@ attention to new bugs, vulnerabilities, security hotspots, and critical or blocker findings. Rule playbooks cover `typescript:S3776`, `typescript:S5852`, and `typescript:S4036`; their references point to SonarSource guidance on cognitive complexity, regex backtracking, quality gates, and PATH trust review. -The plugin advertises these operations through its MCP capability list and -projects the `typescript-quality-feedback` skill so agents know when to use the -snapshot, delta, and rule playbook workflow. +The delta output marks its producer as `typescript.qualityDelta`, and +`typeScriptQualityDeltaCoordinationPayload` compacts the plugin-specific result +into the generic DevNexus coordination `qualityDelta` shape. The plugin +advertises these operations through its MCP capability list and projects the +`typescript-quality-feedback` skill so agents know when to use the snapshot, +delta, handoff payload, and rule playbook workflow. ## Boundaries diff --git a/skills/typescript-quality-feedback/SKILL.md b/skills/typescript-quality-feedback/SKILL.md index 5fb0c5e..d99d9b5 100644 --- a/skills/typescript-quality-feedback/SKILL.md +++ b/skills/typescript-quality-feedback/SKILL.md @@ -23,7 +23,11 @@ Use this skill when working on TypeScript or JavaScript source quality. 6. Use focused verification first, then the nearest package check. Keep Sonar or Docker-backed scans local unless the project has an explicit CI policy. 7. In the handoff, report the touched-file quality delta, serious remaining - findings, verification commands, and any deferred debt. + findings, verification commands, and any deferred debt. When using DevNexus + coordination handoffs, pass the raw `typescript.qualityDelta` result or the + compact `typeScriptQualityDeltaCoordinationPayload` output as the generic + `qualityDelta` payload so core records producer, counts, and attention + findings without TypeScript-specific core behavior. ## Rule Playbooks diff --git a/src/devNexusTypeScriptPlugin.ts b/src/devNexusTypeScriptPlugin.ts index 2a4e981..12b4e17 100644 --- a/src/devNexusTypeScriptPlugin.ts +++ b/src/devNexusTypeScriptPlugin.ts @@ -55,6 +55,7 @@ export function devNexusTypeScriptDevNexusPluginConfig( "It contributes TypeScript and JavaScript setup policy through DevNexus plugin capabilities.", "Use projected dependencies when available, and report missing dependency context as a setup blocker instead of silently fetching packages.", "For quality work, use read-only TypeScript diagnostics, import graph, quality snapshot, and quality delta tools before changing source.", + "When handing off quality results through DevNexus coordination, keep TypeScript details in the plugin-owned quality delta payload and use the generic core qualityDelta field.", ].join(" "), targetAgents: ["codex", "claude"], ...targetComponents, @@ -70,6 +71,7 @@ export function devNexusTypeScriptDevNexusPluginConfig( "Do not run npm, pnpm, yarn, or bun install commands unless the project policy or user explicitly allows dependency installation.", "If package binaries are unavailable, report the setup blocker instead of using npx package fetches as a substitute.", "When touching quality findings, compare before/after quality snapshots for touched files and call out new bugs, vulnerabilities, security hotspots, and critical/blocker findings.", + "Use the TypeScript quality delta coordination payload when a DevNexus handoff should carry compact quality counts and attention findings.", ].join(" "), targetAgents: ["codex", "claude"], ...targetComponents, diff --git a/src/typeScriptQualityFeedback.test.ts b/src/typeScriptQualityFeedback.test.ts index 898c014..3640f2c 100644 --- a/src/typeScriptQualityFeedback.test.ts +++ b/src/typeScriptQualityFeedback.test.ts @@ -7,6 +7,7 @@ import { createTypeScriptQualityAnalyzer, readTypeScriptQualitySnapshot, typeScriptQualityFeedbackToolDescriptors, + typeScriptQualityDeltaCoordinationPayload, typeScriptQualityRulePlaybooks, type TypeScriptQualitySnapshot, } from "./index.js"; @@ -262,6 +263,7 @@ describe("TypeScript quality feedback", () => { expect(delta).toMatchObject({ operation: "typescript.qualityDelta", + producer: "typescript.qualityDelta", readOnly: true, status: "regressed", summary: { @@ -274,4 +276,68 @@ describe("TypeScript quality feedback", () => { "typescript:S3776", ]); }); + + it("compacts TypeScript quality deltas for generic coordination handoffs", () => { + const projectRoot = makeTempProject("coordination-delta"); + const after = { + ...emptySnapshot(projectRoot), + status: "findings", + summary: { + ...emptySnapshot(projectRoot).summary, + findingCount: 2, + criticalOrBlockerCount: 2, + }, + findings: [ + { + id: "sonar-issue:complexity", + source: "sonar_issue", + category: "code_smell", + severity: "critical", + filePath: "src/index.ts", + line: 12, + rule: "typescript:S3776", + message: "Reduce cognitive complexity.", + }, + { + id: "sonar-issue:path", + source: "sonar_issue", + category: "vulnerability", + severity: "blocker", + filePath: "src/runner.ts", + line: 3, + rule: "typescript:S4036", + message: "Review PATH trust.", + }, + ], + } satisfies TypeScriptQualitySnapshot; + + const delta = compareTypeScriptQualitySnapshots({ + before: emptySnapshot(projectRoot), + after, + touchedFiles: ["./src/index.ts", "src/runner.ts"], + }); + const payload = typeScriptQualityDeltaCoordinationPayload(delta, { + sourcePath: "./quality/delta.json", + attentionLimit: 1, + }); + + expect(payload).toEqual({ + producer: "typescript.qualityDelta", + sourcePath: "quality/delta.json", + status: "regressed", + touchedFiles: ["src/index.ts", "src/runner.ts"], + summary: delta.summary, + attention: [ + { + source: "sonar_issue", + category: "code_smell", + severity: "critical", + rule: "typescript:S3776", + filePath: "src/index.ts", + line: 12, + message: "Reduce cognitive complexity.", + }, + ], + }); + }); }); diff --git a/src/typeScriptQualityFeedback.ts b/src/typeScriptQualityFeedback.ts index 43cccc0..47b0d6c 100644 --- a/src/typeScriptQualityFeedback.ts +++ b/src/typeScriptQualityFeedback.ts @@ -148,6 +148,7 @@ export interface TypeScriptQualityDeltaInput { export interface TypeScriptQualityDelta { operation: "typescript.qualityDelta"; + producer: "typescript.qualityDelta"; readOnly: true; status: "regressed" | "improved" | "unchanged"; touchedFiles: string[]; @@ -169,6 +170,30 @@ export interface TypeScriptQualityDelta { attention: TypeScriptQualityFinding[]; } +export interface TypeScriptQualityDeltaCoordinationPayload { + producer: "typescript.qualityDelta"; + sourcePath: string | null; + status: TypeScriptQualityDelta["status"]; + touchedFiles: string[]; + summary: TypeScriptQualityDelta["summary"]; + attention: TypeScriptQualityDeltaCoordinationFinding[]; +} + +export interface TypeScriptQualityDeltaCoordinationFinding { + source: TypeScriptQualityFinding["source"] | null; + category: TypeScriptQualityFinding["category"] | null; + severity: TypeScriptQualityFinding["severity"] | null; + rule: string | null; + filePath: string | null; + line: number | null; + message: string | null; +} + +export interface TypeScriptQualityDeltaCoordinationPayloadOptions { + sourcePath?: string | null; + attentionLimit?: number; +} + export interface TypeScriptQualityRulePlaybook { rule: string; title: string; @@ -345,6 +370,7 @@ export function compareTypeScriptQualitySnapshots( return { operation: "typescript.qualityDelta", + producer: "typescript.qualityDelta", readOnly: true, status: deltaStatus(newFindings, resolvedFindings, qualityGateRegressed), touchedFiles, @@ -371,6 +397,27 @@ export function compareTypeScriptQualitySnapshots( }; } +export function typeScriptQualityDeltaCoordinationPayload( + delta: TypeScriptQualityDelta, + options: TypeScriptQualityDeltaCoordinationPayloadOptions = {}, +): TypeScriptQualityDeltaCoordinationPayload { + const attentionLimit = + typeof options.attentionLimit === "number" && options.attentionLimit >= 0 + ? Math.trunc(options.attentionLimit) + : delta.attention.length; + + return { + producer: "typescript.qualityDelta", + sourcePath: normalizedOptionalPath(options.sourcePath), + status: delta.status, + touchedFiles: [...delta.touchedFiles], + summary: { ...delta.summary }, + attention: delta.attention + .slice(0, attentionLimit) + .map((finding) => qualityDeltaCoordinationFinding(finding)), + }; +} + function importGraphInput( input: TypeScriptQualitySnapshotInput, ): TypeScriptImportGraphInput { @@ -817,6 +864,29 @@ function normalizeTouchedFiles(files: string[] | undefined): string[] { return [...new Set(files.map(normalizePath))].sort(); } +function normalizedOptionalPath(value: string | null | undefined): string | null { + if (!value) { + return null; + } + + const normalized = normalizePath(value); + return normalized.length > 0 ? normalized : null; +} + +function qualityDeltaCoordinationFinding( + finding: TypeScriptQualityFinding, +): TypeScriptQualityDeltaCoordinationFinding { + return { + source: finding.source, + category: finding.category, + severity: finding.severity, + rule: finding.rule, + filePath: finding.filePath, + line: finding.line, + message: finding.message, + }; +} + function snapshotStatus( blockers: TypeScriptSetupFinding[], findings: TypeScriptQualityFinding[],