- lib/idea-code.ts: pure formatIdeaCode helper (client-safe, no prisma) - lib/idea-code-server.ts: atomic nextIdeaCode via Prisma row-lock, accepts optional TransactionClient for combining with idea.create - lib/idea-plan-parser.ts: parsePlanMd extracts ---yaml---/body, runs yaml.parse + ideaPlanMdFrontmatterSchema, returns line-info on failure; CRLF-tolerant - adds yaml@^2.8.4 dependency - 8 unit tests (parser happy/missing/yaml-error/zod-error/empty-stories/CRLF; formatIdeaCode pad-3 + overflow) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
1.9 KiB
TypeScript
73 lines
1.9 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 }
|
|
|
|
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) {
|
|
return {
|
|
ok: false,
|
|
errors: [
|
|
{
|
|
line: err.linePos?.[0]?.line,
|
|
message: err.message,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
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() }
|
|
}
|