📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-06-18 16:04:36 +00:00
parent 71b421806e
commit dd4c084042
416 changed files with 35467 additions and 3065 deletions
+177
View File
@@ -0,0 +1,177 @@
/**
* Parse a brooks-lint Markdown report into structured findings.
*
* The report format is defined by the Report Template in skills/_shared/common.md:
* findings live under severity sub-headers (### 🔴 Critical / 🟡 Warning /
* 🟢 Suggestion), each finding is a bold title line `**Risk Name — title**`
* followed by Symptom / Source / Consequence / Remedy fields.
*
* Consumed by sarif.mjs (SARIF export) and ci-review.mjs (severity gates).
* Best-effort: the report is LLM-authored, so the parser tolerates bracket
* placeholders, an explicit `(R2)` code, and inline severity emoji.
*/
/** Canonical risk code → display name (decay-risks.md + test-decay-risks.md). */
export const RISK_CATALOG = {
R1: "Cognitive Overload",
R2: "Change Propagation",
R3: "Knowledge Duplication",
R4: "Accidental Complexity",
R5: "Dependency Disorder",
R6: "Domain Model Distortion",
T1: "Test Obscurity",
T2: "Test Brittleness",
T3: "Test Duplication",
T4: "Mock Abuse",
T5: "Coverage Illusion",
T6: "Architecture Mismatch",
};
const NAME_TO_CODE = Object.fromEntries(
Object.entries(RISK_CATALOG).map(([code, name]) => [name.toLowerCase(), code]),
);
// The template prescribes a bare `### 🔴 Critical`, but LLM output drifts —
// tolerate a plural and a trailing "Issues"/"Findings"/"Items" qualifier while
// still anchoring on the line so section headers like `## Findings` never match.
const SEVERITY_HEADER_RE =
/^#{2,6}\s*(?:🔴|🟡|🟢|⚠️?|❗)?\s*(Critical|Warning|Suggestion)s?(?:\s+(?:Issues?|Findings?|Items?))?\s*:?\s*$/i;
const SECTION_HEADER_RE = /^#{1,6}\s/;
const BOLD_TITLE_RE = /^\s*(?:🔴|🟡|🟢)?\s*\*\*(.+?)\*\*\s*$/;
const FIELD_RE = /^\s*(Symptom|Source|Consequence|Remedy)\s*[:]\s*(.*)$/i;
const EMOJI_SEVERITY = { "🔴": "critical", "🟡": "warning", "🟢": "suggestion" };
// A path with a directory separator, or a bare filename with a known source
// extension — optionally followed by `:line`. The extension allowlist keeps
// prose like "e.g." or "i.e." from being mistaken for a file reference.
const LOCATION_RE =
/([\w.-]*\/[\w./-]*\.\w+|[\w.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|java|go|rb|rs|cc|cpp|cxx|c|h|hpp|cs|php|kt|kts|swift|scala|vue|sql|rsx|m|mm))(?::(\d+))?/;
function splitTitle(bold) {
// Dash is the template separator; a colon is a common LLM variant. `.match`
// returns the leftmost hit, so a dash still wins when both are present.
const sep = bold.match(/\s*[—–]\s*|\s+--\s+|\s+-\s+|\s*:\s*/);
if (!sep) return { namePart: bold.trim(), title: "" };
return {
namePart: bold.slice(0, sep.index).trim(),
title: bold.slice(sep.index + sep[0].length).trim(),
};
}
function resolveCode(namePart) {
const explicit = namePart.match(/\b([RT][1-6])\b/);
if (explicit) return explicit[1].toUpperCase();
const cleaned = namePart
.replace(/\(([RT][1-6])\)/i, "")
.replace(/[[\]]/g, "")
.trim()
.toLowerCase();
if (NAME_TO_CODE[cleaned]) return NAME_TO_CODE[cleaned];
// Fallback: a missed separator can leave trailing words on the name, so match
// the longest known risk name the cleaned string starts with.
const prefix = Object.keys(NAME_TO_CODE)
.filter((name) => cleaned.startsWith(name))
.sort((a, b) => b.length - a.length)[0];
return prefix ? NAME_TO_CODE[prefix] : null;
}
/** Extract `{ file, line }` from text, or `{ file: null, line: null }`. */
export function extractLocation(text) {
const m = (text ?? "").match(LOCATION_RE);
if (!m) return { file: null, line: null };
// Group 1 is the path; group 2 is the optional `:line` (the extension list is
// a non-capturing group, so the line digits are m[2], not m[3]).
return { file: m[1], line: m[2] ? parseInt(m[2], 10) : null };
}
/**
* Parse a report into an array of findings.
* @returns {Array<{severity, riskCode, riskName, title, symptom, source,
* consequence, remedy, file, line}>}
*/
export function parseFindings(report) {
const lines = (report ?? "").split(/\r?\n/);
const findings = [];
let severity = null;
let current = null;
let field = null;
const commit = () => {
if (!current) return;
// Keep only blocks that look like real findings (a known risk or a symptom).
if (current.riskCode || current.symptom) findings.push(current);
current = null;
field = null;
};
for (const line of lines) {
const sevHeader = line.match(SEVERITY_HEADER_RE);
if (sevHeader) {
commit();
severity = sevHeader[1].toLowerCase();
continue;
}
if (SECTION_HEADER_RE.test(line)) {
// A non-severity header ends the current Findings group (e.g. ## Summary).
commit();
severity = null;
continue;
}
const bold = severity && line.match(BOLD_TITLE_RE);
if (bold) {
commit();
const emoji = line.match(/^\s*(🔴|🟡|🟢)/);
const { namePart, title } = splitTitle(bold[1]);
const riskCode = resolveCode(namePart);
current = {
severity: emoji ? EMOJI_SEVERITY[emoji[1]] : severity,
riskCode,
riskName: riskCode ? RISK_CATALOG[riskCode] : namePart.replace(/[[\]]/g, "").trim(),
title,
symptom: "",
source: "",
consequence: "",
remedy: "",
file: null,
line: null,
};
field = null;
continue;
}
if (!current) continue;
const fieldMatch = line.match(FIELD_RE);
if (fieldMatch) {
field = fieldMatch[1].toLowerCase();
current[field] = fieldMatch[2].trim();
continue;
}
// Continuation line for the field in progress.
if (field && line.trim()) {
current[field] = `${current[field]} ${line.trim()}`.trim();
}
}
commit();
for (const f of findings) {
// Location belongs in the Symptom, but fall back to Source/Consequence when
// it's absent. Remedy is excluded — it often names a destination, not the
// site of the finding.
const fromSymptom = extractLocation(f.symptom);
const loc = fromSymptom.file ? fromSymptom : extractLocation(`${f.source} ${f.consequence}`);
f.file = loc.file;
f.line = loc.line;
}
return findings;
}
/** Count findings by severity. @returns {{critical, warning, suggestion}} */
export function countFindings(report) {
const counts = { critical: 0, warning: 0, suggestion: 0 };
for (const f of parseFindings(report)) {
if (counts[f.severity] !== undefined) counts[f.severity] += 1;
}
return counts;
}