{"record":{"id":"004ed7157c78a3ce","repo":"can1357/oh-my-pi","slug":"unknown-changelog-category-raw-004ed7","errorCode":null,"errorMessage":"Unknown changelog category: ${raw}","messagePattern":"Unknown changelog category: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/commit/conventional/markdown.ts","lineNumber":397,"sourceCode":"function stripCategoryPrefix(text: string): { text: string; category?: ConventionalDetail[\"changelogCategory\"] } {\n\tconst match = text.match(CATEGORY_RE);\n\tif (!match?.groups) return { text: text.trim() };\n\treturn {\n\t\ttext: (match.groups.text ?? \"\").trim(),\n\t\tcategory: changelogCategory(match.groups.bracket ?? match.groups.prefix ?? \"\"),\n\t};\n}\n\nfunction changelogCategory(raw: string): ConventionalDetail[\"changelogCategory\"] {\n\tconst normalized = raw.trim().toLowerCase();\n\tif (normalized === \"breaking\" || normalized === \"breaking changes\") return \"Breaking Changes\";\n\tif (normalized === \"added\") return \"Added\";\n\tif (normalized === \"changed\") return \"Changed\";\n\tif (normalized === \"fixed\") return \"Fixed\";\n\tif (normalized === \"deprecated\") return \"Deprecated\";\n\tif (normalized === \"removed\") return \"Removed\";\n\tif (normalized === \"security\") return \"Security\";\n\tthrow new Error(`Unknown changelog category: ${raw}`);\n}\n\nfunction stripWrappingQuotes(text: string): string {\n\tconst pairs: Record<string, string> = { '\"': '\"', \"'\": \"'\", \"`\": \"`\", \"“\": \"”\", \"‘\": \"’\" };\n\tconst stripped = text.trim();\n\treturn stripped.length >= 2 && pairs[stripped[0] ?? \"\"] === stripped.at(-1)\n\t\t? stripped.slice(1, -1).trim()\n\t\t: stripped;\n}\n\nfunction normalizeEscapedWhitespace(text: string): string {\n\tif (!text.includes(\"\\\\\")) return text;\n\tconst parts = text.split(\"`\");\n\tfor (let index = 0; index < parts.length; index += 2) {\n\t\tparts[index] = (parts[index] ?? \"\")\n\t\t\t.replaceAll(\"\\\\r\\\\n\", \"\\n\")\n\t\t\t.replaceAll(\"\\\\n\", \"\\n\")\n\t\t\t.replaceAll(\"\\\\r\", \"\\n\")","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/commit/conventional/markdown.ts#L379-L415","documentation":"normalizeChangelogCategory maps a raw category string (case-insensitively) to the canonical Keep-a-Changelog categories: Added, Changed, Fixed, Deprecated, Removed, Security. Any other value throws. This guards the changelog section generation from model-invented categories.","triggerScenarios":"Parsing analysis markdown whose detail lines carry a category like 'Features', 'Bug fixes', 'Improvements', 'Performance', or a typo — anything outside the six known categories after normalization reaches the mapper.","commonSituations":"Model output using non-keep-a-changelog headings ('New', 'Bugs'), localized categories, or prompt examples that list categories not in the canonical set; older model outputs generated under a different changelog prompt.","solutions":["Constrain the changelog prompt to exactly the six Keep-a-Changelog categories","Add the common alias to the normalization table if it appears frequently in your model's output","Normalize/alias categories before parsing (e.g. 'features'->'added', 'bug fixes'->'fixed')","Regenerate the analysis with a model that follows the changelog format"],"exampleFix":"// before: throws on 'Features'\nconst category = normalizeChangelogCategory(\"Features\");\n// after: alias first in prompt or pre-map\nconst aliases = { features: \"added\", fixes: \"fixed\", bugfixes: \"fixed\" };\nconst category = normalizeChangelogCategory(aliases[raw.toLowerCase()] ?? raw);","handlingStrategy":"validation","validationCode":"const KNOWN = [\"added\",\"changed\",\"fixed\",\"deprecated\",\"removed\",\"security\"];\nif (!KNOWN.includes(raw.trim().toLowerCase())) {\n  raw = aliasMap[raw.trim().toLowerCase()] ?? \"changed\"; // or reject early\n}","typeGuard":null,"tryCatchPattern":"try {\n  category = normalizeChangelogCategory(raw);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Unknown changelog category\")) {\n    category = \"Changed\"; // safe bucket for model-invented categories\n  } else throw err;\n}","preventionTips":["Pin the changelog prompt to exactly the six Keep-a-Changelog categories","Maintain an alias map for recurring model inventions (features->added, fixes->fixed)","Validate categories during parsing, not at render time"],"tags":["changelog","parsing","validation"],"backgroundTag":"unknown-enum-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}