feat(compression): RTK semantic command-output renderers (opt-in) — roadmap #10 (#5268)

roadmap #10: RTK semantic command-output renderers (opt-in). Locally validated release-green (typecheck + file-size + 22 RTK/analytics tests). Integrated into release/v3.8.40.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-28 21:58:02 -03:00
committed by GitHub
parent bc00e32b15
commit 9dc31c7e08
15 changed files with 636 additions and 110 deletions

View File

@@ -11,6 +11,7 @@ _In development — bullets added per PR; finalized at release._
### ✨ New Features
- **feat(mcp): `omniroute_tool_search` tool + one-line TS signatures** — new MCP tool that does lexical keyword search over every MCP tool's name/description and returns the top matches as compact one-line TypeScript signatures (~half the JSON-schema token cost), so agents discover tools on demand instead of carrying all ~88 schemas every turn. Search is ReDoS-safe (substring scoring, never `new RegExp` on the query) and deterministic; `tools/list` stays complete (no hidden tools). Adds the `read:tools` scope. Tier-1 item of the compression feature-extraction roadmap. ([#5269](https://github.com/diegosouzapw/OmniRoute/pull/5269))
- **feat(compression): RTK semantic command-output renderers (opt-in)** — adds a second, opt-in compaction layer to the RTK engine that rewrites structured command output into a far more compact semantic form: `git diff` → file headers + `@@` hunks + changed lines only; an all-green `pytest`/`jest`/`vitest`/`eslint` run → its one-line summary; `terraform`/`tofu plan``Plan: +N ~M -K` plus the resource list; `kubectl`/`aws` JSON arrays → a minimal table. Each renderer is conservative (no-op when the shape doesn't match) and the integration is fail-open; the test-green renderer never collapses output that carries any failure signal. Gated by `RtkConfig.enableRenderers` (default off → zero behavioral change). Eighth item of the compression feature-extraction roadmap. ([#5268](https://github.com/diegosouzapw/OmniRoute/pull/5268))
- **kilocode:** anonymous (no-auth) access to Kilo Code's free models, mirroring the `opencode`/`mimocode` pattern. With no Kilo account connected, requests now fall back to the gateway's anonymous tier (`Authorization: Bearer anonymous` on `api.kilo.ai/api/openrouter`) so the free models work without signup; a connected OAuth account is still used unchanged for the paid tier (#4019 — thanks @Theadd for the reference implementation)
### 🔧 Bug Fixes

View File

@@ -0,0 +1,117 @@
import { DEFAULT_RTK_CONFIG } from "../../types.ts";
import type { EngineConfigField, EngineValidationResult } from "../types.ts";
export const RTK_SCHEMA: EngineConfigField[] = [
{
key: "intensity",
type: "select",
label: "Intensity",
defaultValue: DEFAULT_RTK_CONFIG.intensity,
options: [
{ value: "minimal", label: "minimal" },
{ value: "standard", label: "standard" },
{ value: "aggressive", label: "aggressive" },
],
},
{
key: "applyToToolResults",
type: "boolean",
label: "Apply to tool results",
defaultValue: DEFAULT_RTK_CONFIG.applyToToolResults,
},
{
key: "applyToAssistantMessages",
type: "boolean",
label: "Apply to assistant messages",
defaultValue: DEFAULT_RTK_CONFIG.applyToAssistantMessages,
},
{
key: "applyToCodeBlocks",
type: "boolean",
label: "Apply to code blocks",
defaultValue: DEFAULT_RTK_CONFIG.applyToCodeBlocks,
},
{
key: "maxLinesPerResult",
type: "number",
label: "Max lines per result",
defaultValue: DEFAULT_RTK_CONFIG.maxLinesPerResult,
min: 0,
max: 5000,
},
{
key: "maxCharsPerResult",
type: "number",
label: "Max chars per result",
defaultValue: DEFAULT_RTK_CONFIG.maxCharsPerResult,
min: 0,
max: 500000,
},
{
key: "deduplicateThreshold",
type: "number",
label: "Deduplicate threshold",
defaultValue: DEFAULT_RTK_CONFIG.deduplicateThreshold,
min: 2,
max: 100,
},
{
key: "rawOutputRetention",
type: "select",
label: "Raw output retention",
defaultValue: DEFAULT_RTK_CONFIG.rawOutputRetention,
options: [
{ value: "never", label: "never" },
{ value: "failures", label: "failures" },
{ value: "always", label: "always" },
],
},
{
key: "enableRenderers",
type: "boolean",
label: "Semantic renderers",
defaultValue: DEFAULT_RTK_CONFIG.enableRenderers,
},
];
export function validateRtkEngineConfig(config: Record<string, unknown>): EngineValidationResult {
const errors: string[] = [];
if (
config.intensity !== undefined &&
config.intensity !== "minimal" &&
config.intensity !== "standard" &&
config.intensity !== "aggressive"
) {
errors.push("intensity must be minimal, standard, or aggressive");
}
for (const key of [
"enabled",
"applyToToolResults",
"applyToAssistantMessages",
"applyToCodeBlocks",
]) {
if (config[key] !== undefined && typeof config[key] !== "boolean") {
errors.push(`${key} must be a boolean`);
}
}
for (const key of ["maxLinesPerResult", "maxCharsPerResult", "deduplicateThreshold"]) {
if (config[key] !== undefined && (typeof config[key] !== "number" || config[key] < 0)) {
errors.push(`${key} must be a non-negative number`);
}
}
if (config.enabledFilters !== undefined && !Array.isArray(config.enabledFilters)) {
errors.push("enabledFilters must be an array");
}
if (config.disabledFilters !== undefined && !Array.isArray(config.disabledFilters)) {
errors.push("disabledFilters must be an array");
}
if (
config.rawOutputRetention !== undefined &&
config.rawOutputRetention !== "never" &&
config.rawOutputRetention !== "failures" &&
config.rawOutputRetention !== "always"
) {
errors.push("rawOutputRetention must be never, failures, or always");
}
return { valid: errors.length === 0, errors };
}

View File

@@ -1,7 +1,8 @@
import { createCompressionStats, estimateCompressionTokens } from "../../stats.ts";
import { DEFAULT_RTK_CONFIG, type CompressionResult, type RtkConfig } from "../../types.ts";
import type { CompressionEngine, EngineConfigField, EngineValidationResult } from "../types.ts";
import type { CompressionEngine } from "../types.ts";
import { detectCommandType } from "./commandDetector.ts";
import { RTK_SCHEMA, validateRtkEngineConfig } from "./configSchema.ts";
import { deduplicateRepeatedLines } from "./deduplicator.ts";
import { groupSimilarLines } from "./grouper.ts";
import { matchRtkFilter } from "./filterLoader.ts";
@@ -9,6 +10,7 @@ import { applyLineFilter } from "./lineFilter.ts";
import { smartTruncate } from "./smartTruncate.ts";
import { normalizeCodeLanguage, stripCode } from "./codeStripper.ts";
import { maybePersistRtkRawOutput, type RtkRawOutputPointer } from "./rawOutput.ts";
import { applyRenderer } from "./renderers/index.ts";
import { isTextBlock } from "../../messageContent.ts";
import { adaptBodyForCompression } from "../../bodyAdapter.ts";
import { isAnthropicToolResultBlock } from "../../toolResultCompressor.ts";
@@ -62,115 +64,6 @@ function resolveToolMeta(
return { command: null, skipFilters: true };
}
const RTK_SCHEMA: EngineConfigField[] = [
{
key: "intensity",
type: "select",
label: "Intensity",
defaultValue: DEFAULT_RTK_CONFIG.intensity,
options: [
{ value: "minimal", label: "minimal" },
{ value: "standard", label: "standard" },
{ value: "aggressive", label: "aggressive" },
],
},
{
key: "applyToToolResults",
type: "boolean",
label: "Apply to tool results",
defaultValue: DEFAULT_RTK_CONFIG.applyToToolResults,
},
{
key: "applyToAssistantMessages",
type: "boolean",
label: "Apply to assistant messages",
defaultValue: DEFAULT_RTK_CONFIG.applyToAssistantMessages,
},
{
key: "applyToCodeBlocks",
type: "boolean",
label: "Apply to code blocks",
defaultValue: DEFAULT_RTK_CONFIG.applyToCodeBlocks,
},
{
key: "maxLinesPerResult",
type: "number",
label: "Max lines per result",
defaultValue: DEFAULT_RTK_CONFIG.maxLinesPerResult,
min: 0,
max: 5000,
},
{
key: "maxCharsPerResult",
type: "number",
label: "Max chars per result",
defaultValue: DEFAULT_RTK_CONFIG.maxCharsPerResult,
min: 0,
max: 500000,
},
{
key: "deduplicateThreshold",
type: "number",
label: "Deduplicate threshold",
defaultValue: DEFAULT_RTK_CONFIG.deduplicateThreshold,
min: 2,
max: 100,
},
{
key: "rawOutputRetention",
type: "select",
label: "Raw output retention",
defaultValue: DEFAULT_RTK_CONFIG.rawOutputRetention,
options: [
{ value: "never", label: "never" },
{ value: "failures", label: "failures" },
{ value: "always", label: "always" },
],
},
];
function validateRtkEngineConfig(config: Record<string, unknown>): EngineValidationResult {
const errors: string[] = [];
if (
config.intensity !== undefined &&
config.intensity !== "minimal" &&
config.intensity !== "standard" &&
config.intensity !== "aggressive"
) {
errors.push("intensity must be minimal, standard, or aggressive");
}
for (const key of [
"enabled",
"applyToToolResults",
"applyToAssistantMessages",
"applyToCodeBlocks",
]) {
if (config[key] !== undefined && typeof config[key] !== "boolean") {
errors.push(`${key} must be a boolean`);
}
}
for (const key of ["maxLinesPerResult", "maxCharsPerResult", "deduplicateThreshold"]) {
if (config[key] !== undefined && (typeof config[key] !== "number" || config[key] < 0)) {
errors.push(`${key} must be a non-negative number`);
}
}
if (config.enabledFilters !== undefined && !Array.isArray(config.enabledFilters)) {
errors.push("enabledFilters must be an array");
}
if (config.disabledFilters !== undefined && !Array.isArray(config.disabledFilters)) {
errors.push("disabledFilters must be an array");
}
if (
config.rawOutputRetention !== undefined &&
config.rawOutputRetention !== "never" &&
config.rawOutputRetention !== "failures" &&
config.rawOutputRetention !== "always"
) {
errors.push("rawOutputRetention must be never, failures, or always");
}
return { valid: errors.length === 0, errors };
}
export interface RtkProcessResult {
text: string;
compressed: boolean;
@@ -366,6 +259,20 @@ export function processRtkText(
}
}
// #10: semantic renderers — opt-in via enableRenderers flag (default OFF), fail-open
if (config.enableRenderers) {
try {
const rendered = applyRenderer(result, detection, config);
if (rendered.changed) {
result = rendered.text;
techniquesUsed.push(`rtk-render:${rendered.renderer}`);
rulesApplied.push(`rtk:render:${rendered.renderer}`);
}
} catch {
// fail-open: renderer never brings down the request
}
}
if (config.applyToCodeBlocks) {
let strippedCodeBlocks = 0;
result = result.replace(

View File

@@ -0,0 +1,39 @@
import type { RenderResult, CommandDetectionResult } from "./types.ts";
import { NO_RENDER } from "./types.ts";
/**
* RTK semantic renderer for `git diff` / `git show` output.
*
* Keeps only:
* - `diff --git a/... b/...` file header lines
* - `@@ ... @@` hunk headers
* - change lines starting with `+` or `-` (but NOT `+++`/`---`)
*
* Everything else (context lines, index lines, mode lines, etc.) is dropped.
* If no hunk header is found, returns no-op (input is not a real diff).
*/
export function renderGitDiff(text: string, _detection: CommandDetectionResult): RenderResult {
// Guard: must have at least one hunk header
if (!text.includes("@@ ")) {
return NO_RENDER(text);
}
const kept: string[] = [];
for (const line of text.split("\n")) {
if (
line.startsWith("diff --git ") ||
line.startsWith("@@ ")
) {
kept.push(line);
} else if (/^[+-](?![+-])/.test(line)) {
// change line (+foo or -foo) but NOT +++ or ---
kept.push(line);
}
// drop: context lines (space), index lines, --- a/, +++ b/, mode lines, etc.
}
const out = kept.join("\n");
if (out === text) return NO_RENDER(text);
return { text: out, changed: true, renderer: "git-diff" };
}

View File

@@ -0,0 +1,50 @@
import type { CommandDetectionResult } from "../commandDetector.ts";
import type { RtkConfig } from "../../../types.ts";
import { type RenderResult, NO_RENDER } from "./types.ts";
import { renderGitDiff } from "./gitDiff.ts";
import { renderTestGreen } from "./testGreen.ts";
import { renderTerraformPlan } from "./terraformPlan.ts";
import { renderStructuredTable } from "./structuredTable.ts";
// preenchido nas tasks 25
const REGISTRY: Record<string, (text: string, d: CommandDetectionResult) => RenderResult> = {};
// Task 2: git-diff renderer
// Note: "git-show" is not a real detection type in commandDetector.ts DETECTORS array,
// so only "git-diff" is registered here.
REGISTRY["git-diff"] = renderGitDiff;
// Task 3: test-green renderer
// Note: "test-eslint" is not a real detection type; the real type is "build-eslint".
REGISTRY["test-pytest"] = renderTestGreen;
REGISTRY["test-jest"] = renderTestGreen;
REGISTRY["test-vitest"] = renderTestGreen;
REGISTRY["build-eslint"] = renderTestGreen;
// Task 4: terraform-plan renderer
REGISTRY["terraform-plan"] = renderTerraformPlan;
REGISTRY["tofu-plan"] = renderTerraformPlan;
// Task 5: structured-table renderer
// Note: "kubectl" is not a real detection type in commandDetector.ts DETECTORS array
// (it's in KNOWN_COMMANDS but has no DETECTOR entry with a "kubectl" type).
// Kubectl JSON output will be detected as "json-output" or "aws" depending on content.
// Registering "aws" and "json-output" which are the real types for this output shape.
REGISTRY["aws"] = renderStructuredTable;
REGISTRY["json-output"] = renderStructuredTable;
export function applyRenderer(
text: string,
detection: CommandDetectionResult,
config: RtkConfig
): RenderResult {
const r = REGISTRY[detection.type];
if (!r) return NO_RENDER(text);
if (config.renderers && config.renderers.length > 0 && !config.renderers.includes(detection.type)) {
return NO_RENDER(text);
}
return r(text, detection);
}
export { type RenderResult } from "./types.ts";
export { REGISTRY };

View File

@@ -0,0 +1,96 @@
import type { RenderResult, CommandDetectionResult } from "./types.ts";
import { NO_RENDER } from "./types.ts";
const MAX_TABLE_ROWS = 200;
const MAX_COLUMNS = 5;
// Priority column names to prefer when choosing which columns to display
const PRIORITY_KEYS = ["name", "id", "status", "type", "kind"];
/**
* RTK semantic renderer for structured JSON array output (aws, kubectl, etc.).
*
* Only renders if:
* - Input parses as JSON (or contains a JSON array substring)
* - Result is an array of ≥2 homogeneous objects (same dominant scalar keys)
*
* Output: minimal TSV-like table (header + rows). Large arrays (>200 items)
* are capped with a trailing "… (+K more)" line.
*
* All other shapes ⇒ no-op (conservative).
*/
export function renderStructuredTable(
text: string,
_detection: CommandDetectionResult,
): RenderResult {
const parsed = tryParse(text.trim());
if (!parsed) return NO_RENDER(text);
// Must be an array of ≥2 objects
if (!Array.isArray(parsed) || parsed.length < 2) return NO_RENDER(text);
const items = parsed as unknown[];
// Every item must be a non-null, non-array object
if (!items.every((item) => item !== null && typeof item === "object" && !Array.isArray(item))) {
return NO_RENDER(text);
}
const objects = items as Record<string, unknown>[];
// Collect scalar keys across all objects, count occurrences
const keyCount: Record<string, number> = {};
for (const obj of objects) {
for (const [k, v] of Object.entries(obj)) {
if (v === null || typeof v !== "object") {
keyCount[k] = (keyCount[k] ?? 0) + 1;
}
}
}
if (Object.keys(keyCount).length === 0) return NO_RENDER(text);
// Choose columns: prioritize PRIORITY_KEYS, then by frequency, cap at MAX_COLUMNS
const threshold = Math.floor(objects.length / 2);
const candidateKeys = Object.entries(keyCount)
.filter(([, count]) => count >= threshold)
.map(([k]) => k);
const priorityChosen = PRIORITY_KEYS.filter((k) => candidateKeys.includes(k));
const rest = candidateKeys
.filter((k) => !PRIORITY_KEYS.includes(k))
.sort((a, b) => (keyCount[b] ?? 0) - (keyCount[a] ?? 0));
const columns = [...priorityChosen, ...rest].slice(0, MAX_COLUMNS);
if (columns.length === 0) return NO_RENDER(text);
// Build table
const rows = objects.slice(0, MAX_TABLE_ROWS);
const extra = objects.length > MAX_TABLE_ROWS ? objects.length - MAX_TABLE_ROWS : 0;
const header = columns.join("\t");
const body = rows
.map((obj) => columns.map((k) => String(obj[k] ?? "")).join("\t"))
.join("\n");
const out = extra > 0 ? `${header}\n${body}\n… (+${extra} more)` : `${header}\n${body}`;
return { text: out, changed: true, renderer: "structured-table" };
}
function tryParse(text: string): unknown {
// Direct parse
try {
return JSON.parse(text);
} catch {
// Try to find the largest [...] substring
const start = text.indexOf("[");
const end = text.lastIndexOf("]");
if (start !== -1 && end > start) {
try {
return JSON.parse(text.slice(start, end + 1));
} catch {
return null;
}
}
return null;
}
}

View File

@@ -0,0 +1,40 @@
import type { RenderResult, CommandDetectionResult } from "./types.ts";
import { NO_RENDER } from "./types.ts";
/**
* RTK semantic renderer for `terraform plan` / `tofu plan` output.
*
* Extracts:
* - The canonical summary line `Plan: N to add, M to change, K to destroy.`
* reformatted as `Plan: +N ~M -K`
* - Resource lines `# <addr> will be <verb>`
*
* "No changes." output ⇒ no-op (already short enough).
* If no `Plan:` line is found ⇒ no-op (conservative).
*/
export function renderTerraformPlan(
text: string,
_detection: CommandDetectionResult,
): RenderResult {
// "No changes" is already compact — idempotent no-op
if (/^No changes\./m.test(text)) return NO_RENDER(text);
// Extract the canonical Plan summary line
const planMatch = text.match(/Plan:\s+(\d+)\s+to add,\s+(\d+)\s+to change,\s+(\d+)\s+to destroy/);
if (!planMatch) return NO_RENDER(text);
const [, add, change, destroy] = planMatch;
const summary = `Plan: +${add} ~${change} -${destroy}`;
// Collect resource address lines: " # <addr> will be <verb>"
const resources: string[] = [];
for (const line of text.split("\n")) {
const m = line.match(/^\s+#\s+(\S+)\s+will\s+be\s+\S+/);
if (m) {
resources.push(` # ${m[1]} will be ${line.trim().replace(/^#\s+\S+\s+will\s+be\s+/, "")}`);
}
}
const out = [summary, ...resources].join("\n");
return { text: out, changed: true, renderer: "terraform-plan" };
}

View File

@@ -0,0 +1,70 @@
import type { RenderResult, CommandDetectionResult } from "./types.ts";
import { NO_RENDER } from "./types.ts";
/**
* RTK semantic renderer for test suite output (pytest, jest, vitest, eslint).
*
* CRITICAL safety guard: only collapses when output indicates TOTAL success.
* ANY sign of failure forces a no-op to preserve full diagnostics.
*
* Failure signals (force no-op):
* - /\bFAIL\b/ in the text
* - /failed/i paired with a nonzero count (e.g. "1 failed")
* - ✖ symbol
* - "Error" anywhere
* - "Traceback" (Python)
* - "AssertionError"
*
* When green, extract the summary line and return it.
* If no recognizable summary line is found, no-op.
*/
export function renderTestGreen(text: string, _detection: CommandDetectionResult): RenderResult {
// Strip ANSI color codes before applying the failure guards. jest/vitest emit a
// colored "FAIL" status whose preceding ANSI byte ("m" of ) is a word
// character, defeating a /\bFAIL\b/ boundary on the raw string. Guards run on the
// stripped copy; the original `text` is what we return on a no-op (lossless).
const stripped = text.replace(/\[[0-9;]*m/g, "");
// Failure guard — must check FIRST; never weaken
if (/\bFAIL\b/.test(stripped)) return NO_RENDER(text);
if (/✖/.test(stripped)) return NO_RENDER(text);
if (/Error/.test(stripped)) return NO_RENDER(text);
if (/Traceback/.test(stripped)) return NO_RENDER(text);
if (/AssertionError/.test(stripped)) return NO_RENDER(text);
// "failed" with a nonzero count, e.g. "1 failed" or "failed: 3"
const failedMatch = stripped.match(/(\d+)\s+failed/i) ?? stripped.match(/failed[:\s]+(\d+)/i);
if (failedMatch && parseInt(failedMatch[1], 10) > 0) return NO_RENDER(text);
// Try to extract a recognised summary line (from the stripped copy → ANSI-free)
const summary = extractSummaryLine(stripped);
if (!summary) return NO_RENDER(text);
return { text: summary, changed: true, renderer: "test-green" };
}
function extractSummaryLine(text: string): string | null {
const lines = text.split("\n");
// pytest: === N passed in X.Xs === (also handles variants like === N passed, M warning ===)
for (const line of lines) {
if (/={3,}\s+\d+\s+passed/.test(line)) return line.trim();
}
// jest / vitest: "Tests: N passed, N total" or "Test Suites: ... Tests: ..."
for (const line of lines) {
if (/Tests:\s+\d+\s+passed/.test(line)) return line.trim();
}
// vitest / jest run summary line: "✓ N tests passed"
for (const line of lines) {
if (/\d+\s+tests?\s+passed/i.test(line)) return line.trim();
}
// eslint / build-eslint: empty output = clean; if we reach here with no failures, synthesize
if (text.trim() === "" || text.trim().startsWith("\n")) {
return "ESLint: 0 problems found";
}
return null;
}

View File

@@ -0,0 +1,13 @@
import type { CommandDetectionResult } from "../commandDetector.ts";
import type { RtkConfig } from "../../../types.ts";
export interface RenderResult {
text: string;
changed: boolean;
renderer: string; // nome do renderer aplicado (ou "" se nenhum)
}
export type Renderer = (text: string, detection: CommandDetectionResult) => RenderResult;
export const NO_RENDER = (text: string): RenderResult => ({ text, changed: false, renderer: "" });
export type { RtkConfig, CommandDetectionResult };

View File

@@ -103,6 +103,10 @@ export interface RtkConfig {
stripCodeComments?: boolean;
/** R1/N3: keep JSDoc/docstring block comments when removing comments. Default: true. */
preserveDocstrings?: boolean;
/** #10: semantic command-output renderers (default off) */
enableRenderers?: boolean;
/** #10: whitelist por command-type; vazio/undefined = todos */
renderers?: string[];
}
export interface CompressionLanguageConfig {
@@ -314,6 +318,7 @@ export const DEFAULT_RTK_CONFIG: RtkConfig = {
groupingThreshold: 3,
stripCodeComments: false,
preserveDocstrings: true,
enableRenderers: false,
};
export const DEFAULT_COMPRESSION_LANGUAGE_CONFIG: CompressionLanguageConfig = {

View File

@@ -0,0 +1,36 @@
import test from "node:test";
import assert from "node:assert/strict";
import { renderGitDiff } from "../../../open-sse/services/compression/engines/rtk/renderers/gitDiff.ts";
const det = {
type: "git-diff",
command: "git diff",
confidence: 1,
category: "git",
matchedPatterns: [],
};
test("keeps file headers, hunks and +/- changes; drops context", () => {
const input = `diff --git a/x.ts b/x.ts
index 111..222 100644
--- a/x.ts
+++ b/x.ts
@@ -1,3 +1,3 @@
-const a = 1;
+const a = 2;
const b = 3;`;
const r = renderGitDiff(input, det);
assert.equal(r.changed, true);
assert.ok(r.text.includes("diff --git a/x.ts b/x.ts"));
assert.ok(r.text.includes("@@ -1,3 +1,3 @@"));
assert.ok(r.text.includes("-const a = 1;"));
assert.ok(r.text.includes("+const a = 2;"));
assert.ok(!r.text.includes("index 111..222"));
assert.ok(!r.text.includes("--- a/x.ts"));
assert.ok(!r.text.includes(" const b = 3;")); // contexto dropado
});
test("no hunks ⇒ no-op", () => {
const r = renderGitDiff("just some text\nno diff here", det);
assert.equal(r.changed, false);
});

View File

@@ -0,0 +1,32 @@
import test from "node:test";
import assert from "node:assert/strict";
import { renderStructuredTable } from "../../../open-sse/services/compression/engines/rtk/renderers/structuredTable.ts";
const det = {
type: "kubectl",
command: "kubectl get pods -o json",
confidence: 1,
category: "cloud",
matchedPatterns: [],
};
test("homogeneous JSON array ⇒ minimal table", () => {
const input = JSON.stringify([
{ name: "pod-a", status: "Running", restarts: 0 },
{ name: "pod-b", status: "Pending", restarts: 2 },
]);
const r = renderStructuredTable(input, det);
assert.equal(r.changed, true);
assert.ok(r.text.includes("name"));
assert.ok(r.text.includes("pod-a"));
assert.ok(r.text.includes("Pending"));
assert.ok(!r.text.includes('"status":')); // não é mais JSON
});
test("malformed JSON ⇒ no-op", () => {
assert.equal(renderStructuredTable("{not json", det).changed, false);
});
test("single object (not array) ⇒ no-op", () => {
assert.equal(renderStructuredTable('{"name":"x"}', det).changed, false);
});

View File

@@ -0,0 +1,37 @@
import test from "node:test";
import assert from "node:assert/strict";
import { renderTerraformPlan } from "../../../open-sse/services/compression/engines/rtk/renderers/terraformPlan.ts";
const det = {
type: "terraform-plan",
command: "terraform plan",
confidence: 1,
category: "infra",
matchedPatterns: [],
};
test("summarizes plan into +N ~M -K plus resources", () => {
const input = `Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" { ... many lines ... }
# aws_s3_bucket.data will be updated in-place
~ resource "aws_s3_bucket" "data" { ... }
Plan: 1 to add, 1 to change, 0 to destroy.`;
const r = renderTerraformPlan(input, det);
assert.equal(r.changed, true);
assert.ok(r.text.includes("Plan: +1 ~1 -0"));
assert.ok(r.text.includes("aws_instance.web"));
assert.ok(!r.text.includes('resource "aws_instance" "web" {'));
// Regression (core review): the verb must not be duplicated ("will be will be created").
assert.ok(!r.text.includes("will be will be"));
assert.ok(r.text.includes("# aws_instance.web will be created"));
assert.ok(r.text.includes("# aws_s3_bucket.data will be updated in-place"));
});
test("no-changes ⇒ no-op", () => {
const r = renderTerraformPlan(
"No changes. Your infrastructure matches the configuration.",
det,
);
assert.equal(r.changed, false);
});

View File

@@ -0,0 +1,40 @@
import test from "node:test";
import assert from "node:assert/strict";
import { renderTestGreen } from "../../../open-sse/services/compression/engines/rtk/renderers/testGreen.ts";
const det = (t: string) => ({
type: t,
command: "",
confidence: 1,
category: "test",
matchedPatterns: [],
});
test("pytest all-green collapses to summary", () => {
const input = `============ test session starts ============
collected 142 items
tests/a.py ....................
tests/b.py ....................
============ 142 passed in 3.21s ============`;
const r = renderTestGreen(input, det("test-pytest"));
assert.equal(r.changed, true);
assert.ok(r.text.includes("142 passed"));
assert.ok(!r.text.includes("...................."));
});
test("any failure ⇒ no-op (preserve diagnostics)", () => {
const input = `tests/a.py ..F..
=== 1 failed, 4 passed in 1.0s ===
E AssertionError: nope`;
const r = renderTestGreen(input, det("test-pytest"));
assert.equal(r.changed, false);
});
test("ANSI-colored FAIL with no numeric failed-count ⇒ no-op (regression: \\bFAIL\\b defeated by ANSI)", () => {
// jest/vitest emit a colored FAIL header; the ESC[31m byte 'm' before 'F' kills the
// word boundary. With the per-test failed-count line already stripped by an upstream
// filter, the ANSI strip in the guard is the only thing preventing a collapsed failure.
const input = "FAIL src/auth.test.ts\nTests: 3 passed, 3 total";
const r = renderTestGreen(input, det("test-jest"));
assert.equal(r.changed, false);
});

View File

@@ -0,0 +1,43 @@
import test from "node:test";
import assert from "node:assert/strict";
import { processRtkText } from "../../../open-sse/services/compression/engines/rtk/index.ts";
import { applyRenderer } from "../../../open-sse/services/compression/engines/rtk/renderers/index.ts"; // sanity import
// keep unused import check quiet
void applyRenderer;
const GIT_DIFF = `diff --git a/x.ts b/x.ts
index 111..222 100644
--- a/x.ts
+++ b/x.ts
@@ -1,3 +1,3 @@
-const a = 1;
+const a = 2;
const b = 3;`;
test("enableRenderers default false ⇒ baseline unchanged", () => {
const off = processRtkText(GIT_DIFF, { command: "git diff", config: { enabled: true } });
const explicitOff = processRtkText(GIT_DIFF, {
command: "git diff",
config: { enabled: true, enableRenderers: false },
});
assert.equal(off.text, explicitOff.text); // renderer não roda por default
});
test("enableRenderers true ⇒ git diff is rendered through processRtkText", () => {
const on = processRtkText(GIT_DIFF, {
command: "git diff",
config: { enabled: true, enableRenderers: true },
});
assert.ok(on.techniquesUsed.includes("rtk-render:git-diff"));
assert.ok(!on.text.includes("index ")); // contexto/metadata dropado
});
test("fail-open: renderer error keeps prior result", () => {
// entrada que detecta git-diff mas é benigna; renderer não deve lançar nem alterar incorretamente
const r = processRtkText("not really a diff", {
command: "git diff",
config: { enabled: true, enableRenderers: true },
});
assert.ok(typeof r.text === "string"); // nunca lança
});