{"record":{"id":"7bac77d37bbe5d47","repo":"nexu-io/open-design","slug":"proposal-patch-after-markdown-is-required","errorCode":null,"errorMessage":"proposal patch.after markdown is required","messagePattern":"proposal patch\\.after markdown is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-proposals.ts","lineNumber":231,"sourceCode":"  return proposal.metadata && typeof proposal.metadata === 'object' && !Array.isArray(proposal.metadata)\n    ? proposal.metadata as Record<string, unknown>\n    : {};\n}\n\nfunction targetSlugFor(proposal: AutomationEvolutionProposal, kind: 'design-system' | 'skill'): string {\n  const expected = kind === 'design-system'\n    ? /^design-systems\\/([^/]+)\\/DESIGN\\.md$/\n    : /^skills\\/([^/]+)\\/SKILL\\.md$/;\n  const fromRef = typeof proposal.targetRef === 'string' ? expected.exec(proposal.targetRef)?.[1] : '';\n  if (fromRef && SAFE_SLUG.test(fromRef)) return fromRef;\n  const metadata = metadataRecord(proposal);\n  if (typeof metadata.slug === 'string' && SAFE_SLUG.test(metadata.slug)) return metadata.slug;\n  return slugifyTarget(proposal.title);\n}\n\nfunction proposalAfterMarkdown(proposal: AutomationEvolutionProposal): string {\n  if (typeof proposal.patch.after !== 'string' || !proposal.patch.after.trim()) {\n    throw new Error('proposal patch.after markdown is required');\n  }\n  return proposal.patch.after.trimEnd() + '\\n';\n}\n\nasync function applyDesignSystemProposal(dataDir: string, proposal: AutomationEvolutionProposal) {\n  const slug = targetSlugFor(proposal, 'design-system');\n  const dir = path.join(dataDir, 'design-systems', slug);\n  const file = path.join(dir, 'DESIGN.md');\n  if (proposal.action === 'delete') {\n    await fsp.rm(dir, { recursive: true, force: true });\n    return { designSystemId: slug, action: 'delete', path: `design-systems/${slug}/DESIGN.md` };\n  }\n  await fsp.mkdir(dir, { recursive: true });\n  await fsp.writeFile(file, proposalAfterMarkdown(proposal), 'utf8');\n  return { designSystemId: slug, action: proposal.action, path: `design-systems/${slug}/DESIGN.md` };\n}\n\nasync function applySkillProposal(dataDir: string, proposal: AutomationEvolutionProposal) {","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-proposals.ts#L213-L249","documentation":"Thrown by proposalAfterMarkdown() when proposal.patch.after is not a non-empty string. This helper backs applyDesignSystemProposal and applySkillProposal, both of which write the after value verbatim as DESIGN.md / SKILL.md content. An empty markdown body would create an empty file, so it is rejected.","triggerScenarios":"Applying a design-system or skill create/update proposal whose patch.format is 'markdown' but patch.after is missing, empty, whitespace-only, or not a string. (For these target kinds, format is expected to be 'markdown'.)","commonSituations":"Agent created the proposal with a placeholder after it intended to fill later; patch.after was cleared during an edit; proposal built from a template whose body field was empty.","solutions":["Set patch.after to the full markdown body for DESIGN.md / SKILL.md before applying.","Reject the proposal if no real content exists yet.","Mirror the markdown shape produced by buildDesignSystemProposalMarkdown / buildSkillProposalMarkdown in automation-ingestions.ts."],"exampleFix":"// before\n{ targetKind: 'skill', action: 'create', patch: { format: 'markdown', after: '' } }\n// after\n{ targetKind: 'skill', action: 'create', patch: { format: 'markdown', after: '# My Skill\\n\\n...body...' } }","handlingStrategy":"validation","validationCode":"if (typeof proposal.patch.after !== 'string' || !proposal.patch.after.trim()) {\n  throw new Error('proposal patch.after markdown is required');\n}","typeGuard":"function hasMarkdownAfter(patch: unknown): patch is { after: string } {\n  return !!patch && typeof (patch as any).after === 'string' && (patch as any).after.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Build markdown patches with buildDesignSystemProposalMarkdown / buildSkillProposalMarkdown.","Reject proposals that have placeholder/empty after content instead of applying.","Validate after.trim() before persisting skill/design-system proposals."],"tags":["automation","proposals","design-system","skill","validation","daemon"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}