From 991d46dd9c0881e3a0f5bba5fd70804f0fa27e6f Mon Sep 17 00:00:00 2001 From: "d3mlabs-ai-flow[bot]" <305891656+d3mlabs-ai-flow[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:33:44 -0400 Subject: [PATCH] ai-flow /build: capture learnings from the build pass Co-authored-by: JPDuchesne <2636122+JPDuchesne@users.noreply.github.com> --- .cursor/rules/learnings-index.mdc | 4 ++ .../learnings/plan-parse-normalizes/SKILL.md | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 .cursor/skills/learnings/plan-parse-normalizes/SKILL.md diff --git a/.cursor/rules/learnings-index.mdc b/.cursor/rules/learnings-index.mdc index 7d30d34..23afd27 100644 --- a/.cursor/rules/learnings-index.mdc +++ b/.cursor/rules/learnings-index.mdc @@ -50,6 +50,10 @@ propose a retirement, a consolidation, or a glob-scoped sub-index split. `cmd.execute` in Runner#run runs for them — post-command work needs a builtin or CommandRunner wait mode (dev#85). → .cursor/skills/learnings/command-runner-exec/ +- [architecture/plan-parse-normalizes] Plan `Content.parse` is the only + normalization gate: it must recognize every layout Cursor writes, since + render re-serializes its output and any mis-split compounds on the next + pull. → .cursor/skills/learnings/plan-parse-normalizes/ ## toolchain diff --git a/.cursor/skills/learnings/plan-parse-normalizes/SKILL.md b/.cursor/skills/learnings/plan-parse-normalizes/SKILL.md new file mode 100644 index 0000000..11dfaf6 --- /dev/null +++ b/.cursor/skills/learnings/plan-parse-normalizes/SKILL.md @@ -0,0 +1,39 @@ +--- +name: plan-parse-normalizes +description: >- + MUST be used when changing how plan files are parsed or rendered + (Dev::Plan::Content, Frontmatter, Header) or diagnosing a plan file + that renders as raw text / loses its ai-flow header after a pull. +--- + +# Plan Content.parse is the normalization gate — mis-splits compound + +Cursor rewrites plan files freely (frontmatter above the header, blank +lines after fences, a fresh empty frontmatter block stacked on top), so +`Dev::Plan::Content.parse` must recognize the layers in any of those +layouts and `render` restores canonical order (header, frontmatter, +body). There is no other repair point: every pull re-serializes what +parse produced, so anything parse lumps into the body is re-rendered +that way and the mangling compounds on each sync instead of healing — +that is how dev#60's double-frontmatter file grew. + +Wrong — handle only the canonical layout and let the rest fall through: + + frontmatter, body = Frontmatter.split(remainder) + # a second stacked block stays in body; next render ships it there + +Right — peel leniently, collapse duplicates, stop at real content: + + frontmatter, body = split_stacked_frontmatter(remainder) + # drops empty blocks above the content-carrying one; never consumes + # a block after a non-empty one, so bodies starting with `---` YAML + # are safe + +When adding tolerance for a new Cursor layout, keep both invariants: +parse never destroys content (unclaimed text stays byte-exact in the +body), and render of a parsed mangled file must round-trip to canonical +form — assert that round-trip in the regression test. + +learned-from: dev#60 (empty frontmatter stacked above the real one; +second layout bug in Content.parse after the misordered-header case) +date: 2026-08-03