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
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import {
readTypeScriptQualitySnapshot,
traceTypeScriptDiagnostics,
traceTypeScriptProjectStatus,
typeScriptQualityDeltaCoordinationPayload,
} from "@evref-bl/dev-nexus-typescript";

const status = traceTypeScriptProjectStatus({ projectRoot });
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion skills/typescript-quality-feedback/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/devNexusTypeScriptPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions src/typeScriptQualityFeedback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
createTypeScriptQualityAnalyzer,
readTypeScriptQualitySnapshot,
typeScriptQualityFeedbackToolDescriptors,
typeScriptQualityDeltaCoordinationPayload,
typeScriptQualityRulePlaybooks,
type TypeScriptQualitySnapshot,
} from "./index.js";
Expand Down Expand Up @@ -262,6 +263,7 @@ describe("TypeScript quality feedback", () => {

expect(delta).toMatchObject({
operation: "typescript.qualityDelta",
producer: "typescript.qualityDelta",
readOnly: true,
status: "regressed",
summary: {
Expand All @@ -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.",
},
],
});
});
});
70 changes: 70 additions & 0 deletions src/typeScriptQualityFeedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export interface TypeScriptQualityDeltaInput {

export interface TypeScriptQualityDelta {
operation: "typescript.qualityDelta";
producer: "typescript.qualityDelta";
readOnly: true;
status: "regressed" | "improved" | "unchanged";
touchedFiles: string[];
Expand All @@ -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;
Expand Down Expand Up @@ -345,6 +370,7 @@ export function compareTypeScriptQualitySnapshots(

return {
operation: "typescript.qualityDelta",
producer: "typescript.qualityDelta",
readOnly: true,
status: deltaStatus(newFindings, resolvedFindings, qualityGateRegressed),
touchedFiles,
Expand All @@ -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 {
Expand Down Expand Up @@ -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[],
Expand Down
Loading