- Voeg `hint?: string` toe aan PlanParseError type - Bereken bestand-relatief regelnummer (yamlLine + 1 voor de openings-`---`) - Detecteer markdown-patronen (numbered/bullet lijst) op de offending regel - Zet Nederlandstalige hint bij markdown-match - Render hint als "Tip: …" onder het foutbericht in IdeaMdEditor
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
// Parser voor de plan_md die make-plan-job produceert.
|
|
// Format: yaml-frontmatter (structuur, parseerbaar) + markdown-body (vrije
|
|
// reasoning). Frontmatter wordt gevalideerd via ideaPlanMdFrontmatterSchema.
|
|
//
|
|
// Wordt zowel door de server-action materializeIdeaPlanAction als door de
|
|
// MCP-tool update_idea_plan_md gebruikt. Synchroon — geen LLM-call.
|
|
//
|
|
// Zie docs/plans/M12-ideas.md "Plan-md formaat A" voor het format-voorbeeld.
|
|
|
|
import { parse as parseYaml, YAMLParseError } from 'yaml'
|
|
import {
|
|
ideaPlanMdFrontmatterSchema,
|
|
type IdeaPlanFrontmatter,
|
|
} from '@/lib/schemas/idea'
|
|
|
|
export type PlanParseError = { line?: number; message: string; hint?: string }
|
|
|
|
export type PlanParseResult =
|
|
| { ok: true; plan: IdeaPlanFrontmatter; body: string }
|
|
| { ok: false; errors: PlanParseError[] }
|
|
|
|
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/
|
|
|
|
export function parsePlanMd(md: string): PlanParseResult {
|
|
const match = md.match(FRONTMATTER_RE)
|
|
if (!match) {
|
|
return {
|
|
ok: false,
|
|
errors: [
|
|
{
|
|
line: 1,
|
|
message:
|
|
'Plan ontbreekt yaml-frontmatter. Verwacht eerste regel: ---',
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
const [, frontmatterRaw, body] = match
|
|
|
|
let parsed: unknown
|
|
try {
|
|
parsed = parseYaml(frontmatterRaw)
|
|
} catch (err) {
|
|
if (err instanceof YAMLParseError) {
|
|
const yamlLine = err.linePos?.[0]?.line
|
|
const fileLine = yamlLine != null ? yamlLine + 1 : undefined
|
|
const offendingLine =
|
|
yamlLine != null
|
|
? frontmatterRaw.split(/\r?\n/)[(yamlLine ?? 1) - 1]
|
|
: undefined
|
|
const isMarkdown =
|
|
offendingLine != null &&
|
|
(/^\s*\d+\.\s+\*\*/.test(offendingLine) ||
|
|
/^\s*[-*]\s+\*\*/.test(offendingLine) ||
|
|
/^\s*\d+\..*:/.test(offendingLine))
|
|
const hint = isMarkdown
|
|
? 'Lijkt op markdown-content (genummerde of opsommingslijst) binnen YAML-frontmatter. Verplaats deze regels naar na de afsluitende `---`, of zet ze in een `description: |` blok.'
|
|
: undefined
|
|
return {
|
|
ok: false,
|
|
errors: [{ line: fileLine, message: err.message, hint }],
|
|
}
|
|
}
|
|
return {
|
|
ok: false,
|
|
errors: [{ message: err instanceof Error ? err.message : String(err) }],
|
|
}
|
|
}
|
|
|
|
const validation = ideaPlanMdFrontmatterSchema.safeParse(parsed)
|
|
if (!validation.success) {
|
|
return {
|
|
ok: false,
|
|
errors: validation.error.issues.map((iss) => ({
|
|
message: `${iss.path.join('.') || '<root>'}: ${iss.message}`,
|
|
})),
|
|
}
|
|
}
|
|
|
|
return { ok: true, plan: validation.data, body: body.trimStart() }
|
|
}
|