Cursor plan header metadata vs GH issue as plan format discrepancy #37

Description

@JPDuchesne

Cursor plan header metadata vs GH issue as plan format discrepancy

Problem

Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
plan UI owns — typically name, overview, todos, and isProject:

---
name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
- id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
---

That frontmatter is local editor state: a plan picker label, a short overview,
and a checklist Cursor ticks as it works. It is not plan prose.

Today dev plan link / push treat everything after the <!-- ai-flow -->
sync header as the issue body
(https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
GitHub, where it renders as noisy leading --- / key soup above the real
markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
have no frontmatter until Cursor invents one locally — so the two sides already
disagree about what "the body" is.

Decision

Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
canonical markdown plan only (starting at the # title / prose). Frontmatter
stays a local-only envelope around that body.

Rationale:

  • GitHub already has its own title; name / overview duplicate it poorly.
  • todos are Cursor session progress, not shared plan content — publishing them
    invites stale checklists and review noise.
  • The sync contract already says the issue body is what reviewers edit via
    /ask / /edit; frontmatter has no role there.

The <!-- ai-flow --> HTML comment remains local-only as today (never part of
the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
UI metadata.

Layers of a linked local plan file

<!-- ai-flow
issue: owner/repo#n
synced_at: …
-->
---
name: …
overview: …
todos: […]
isProject: false
---
# Plan title
…markdown body…
LayerLives on diskLives on GitHubOwned by
<!-- ai-flow --> headeryesnodev plan sync guard
YAML frontmatter (------)yes (optional)noCursor plan UI
Markdown bodyyesyes (canonical)humans + ai-flow review

How this plays with pull / push / link

All sync comparisons, merge bases, and issue PATCHes operate on the markdown
body only
— never on frontmatter.

dev plan push / link <file> (local → remote)

  1. Header.split → ai-flow header + remainder (unchanged).
  2. Split remainder into optional YAML frontmatter + markdown body (new).
  3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
  4. Merge base stores that same markdown body.
  5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
    markdown body. Frontmatter is not deleted locally; it is simply not shipped.

Title extraction (extract_title) already keys off the first # … line; it
must run on the markdown body after frontmatter is peeled off so a name: key
cannot be mistaken for content.

dev plan pull (remote → local)

  1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
  2. If a local file exists, peel and preserve its YAML frontmatter across the
    rewrite; only replace the markdown body (and refresh the ai-flow header /
    merge base as today).
  3. If there is no local frontmatter (fresh pull, or a plan that never had one),
    write header + markdown body only — do not synthesize fake todos. Cursor
    may add frontmatter the next time it opens the file; that is fine and stays
    local.

Conflict / status semantics

  • ahead / behind / diverged compare markdown bodies against the merge base.
  • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
    mark the plan dirty for sync and must not trigger a meaningful push
    (body unchanged → existing "already in sync" path).
  • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
    carried through from the local side untouched.

Edge cases

  • Draft already linked with frontmatter on GH (today's bug residue): first
    push after the fix rewrites the issue body to markdown-only (stripping the
    published frontmatter). That is an intentional one-time cleanup, not a
    conflict — remote "change" is us removing noise we incorrectly published.
  • Malformed / non-Cursor leading ---: only peel a block that looks like
    Cursor plan frontmatter (opening --- on its own line near the top,
    closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
    the body are left alone.
  • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
    header, then optional frontmatter, then markdown. If a hand-edited file has
    frontmatter above the ai-flow header, normalize on the next pull/push
    write.

Implementation sketch

Non-goals

  • Syncing Cursor todos to GitHub checklists (or the reverse).
  • Changing the <!-- ai-flow --> header format or conflict-guard rules.
  • Requiring frontmatter on every local plan — absence remains valid.

Subtasks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Cursor plan header metadata vs GH issue as plan format discrepancy #37

      Description

      @JPDuchesne

      Cursor plan header metadata vs GH issue as plan format discrepancy

      Problem

      Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
      plan UI owns — typically name, overview, todos, and isProject:

      ---
      name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
      - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
      ---

      That frontmatter is local editor state: a plan picker label, a short overview,
      and a checklist Cursor ticks as it works. It is not plan prose.

      Today dev plan link / push treat everything after the <!-- ai-flow -->
      sync header as the issue body
      (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
      https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
      Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
      frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
      GitHub, where it renders as noisy leading --- / key soup above the real
      markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
      have no frontmatter until Cursor invents one locally — so the two sides already
      disagree about what "the body" is.

      Decision

      Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
      canonical markdown plan only (starting at the # title / prose). Frontmatter
      stays a local-only envelope around that body.

      Rationale:

      • GitHub already has its own title; name / overview duplicate it poorly.
      • todos are Cursor session progress, not shared plan content — publishing them
        invites stale checklists and review noise.
      • The sync contract already says the issue body is what reviewers edit via
        /ask / /edit; frontmatter has no role there.

      The <!-- ai-flow --> HTML comment remains local-only as today (never part of
      the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
      UI metadata.

      Layers of a linked local plan file

      <!-- ai-flow
      issue: owner/repo#n
      synced_at: …
      -->
      ---
      name: …
      overview: …
      todos: […]
      isProject: false
      ---
      # Plan title
      …markdown body…
      
      LayerLives on diskLives on GitHubOwned by
      <!-- ai-flow --> headeryesnodev plan sync guard
      YAML frontmatter (------)yes (optional)noCursor plan UI
      Markdown bodyyesyes (canonical)humans + ai-flow review

      How this plays with pull / push / link

      All sync comparisons, merge bases, and issue PATCHes operate on the markdown
      body only
      — never on frontmatter.

      dev plan push / link <file> (local → remote)

      1. Header.split → ai-flow header + remainder (unchanged).
      2. Split remainder into optional YAML frontmatter + markdown body (new).
      3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
      4. Merge base stores that same markdown body.
      5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
        markdown body. Frontmatter is not deleted locally; it is simply not shipped.

      Title extraction (extract_title) already keys off the first # … line; it
      must run on the markdown body after frontmatter is peeled off so a name: key
      cannot be mistaken for content.

      dev plan pull (remote → local)

      1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
      2. If a local file exists, peel and preserve its YAML frontmatter across the
        rewrite; only replace the markdown body (and refresh the ai-flow header /
        merge base as today).
      3. If there is no local frontmatter (fresh pull, or a plan that never had one),
        write header + markdown body only — do not synthesize fake todos. Cursor
        may add frontmatter the next time it opens the file; that is fine and stays
        local.

      Conflict / status semantics

      • ahead / behind / diverged compare markdown bodies against the merge base.
      • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
        mark the plan dirty for sync and must not trigger a meaningful push
        (body unchanged → existing "already in sync" path).
      • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
        carried through from the local side untouched.

      Edge cases

      • Draft already linked with frontmatter on GH (today's bug residue): first
        push after the fix rewrites the issue body to markdown-only (stripping the
        published frontmatter). That is an intentional one-time cleanup, not a
        conflict — remote "change" is us removing noise we incorrectly published.
      • Malformed / non-Cursor leading ---: only peel a block that looks like
        Cursor plan frontmatter (opening --- on its own line near the top,
        closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
        the body are left alone.
      • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
        header, then optional frontmatter, then markdown. If a hand-edited file has
        frontmatter above the ai-flow header, normalize on the next pull/push
        write.

      Implementation sketch

      Non-goals

      • Syncing Cursor todos to GitHub checklists (or the reverse).
      • Changing the <!-- ai-flow --> header format or conflict-guard rules.
      • Requiring frontmatter on every local plan — absence remains valid.

      Subtasks

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Cursor plan header metadata vs GH issue as plan format discrepancy #37

          Description

          @JPDuchesne

          Cursor plan header metadata vs GH issue as plan format discrepancy

          Problem

          Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
          plan UI owns — typically name, overview, todos, and isProject:

          ---
          name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
          - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
          ---

          That frontmatter is local editor state: a plan picker label, a short overview,
          and a checklist Cursor ticks as it works. It is not plan prose.

          Today dev plan link / push treat everything after the <!-- ai-flow -->
          sync header as the issue body
          (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
          https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
          Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
          frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
          GitHub, where it renders as noisy leading --- / key soup above the real
          markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
          have no frontmatter until Cursor invents one locally — so the two sides already
          disagree about what "the body" is.

          Decision

          Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
          canonical markdown plan only (starting at the # title / prose). Frontmatter
          stays a local-only envelope around that body.

          Rationale:

          • GitHub already has its own title; name / overview duplicate it poorly.
          • todos are Cursor session progress, not shared plan content — publishing them
            invites stale checklists and review noise.
          • The sync contract already says the issue body is what reviewers edit via
            /ask / /edit; frontmatter has no role there.

          The <!-- ai-flow --> HTML comment remains local-only as today (never part of
          the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
          UI metadata.

          Layers of a linked local plan file

          <!-- ai-flow
          issue: owner/repo#n
          synced_at: …
          -->
          ---
          name: …
          overview: …
          todos: […]
          isProject: false
          ---
          # Plan title
          …markdown body…
          
          LayerLives on diskLives on GitHubOwned by
          <!-- ai-flow --> headeryesnodev plan sync guard
          YAML frontmatter (------)yes (optional)noCursor plan UI
          Markdown bodyyesyes (canonical)humans + ai-flow review

          How this plays with pull / push / link

          All sync comparisons, merge bases, and issue PATCHes operate on the markdown
          body only
          — never on frontmatter.

          dev plan push / link <file> (local → remote)

          1. Header.split → ai-flow header + remainder (unchanged).
          2. Split remainder into optional YAML frontmatter + markdown body (new).
          3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
          4. Merge base stores that same markdown body.
          5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
            markdown body. Frontmatter is not deleted locally; it is simply not shipped.

          Title extraction (extract_title) already keys off the first # … line; it
          must run on the markdown body after frontmatter is peeled off so a name: key
          cannot be mistaken for content.

          dev plan pull (remote → local)

          1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
          2. If a local file exists, peel and preserve its YAML frontmatter across the
            rewrite; only replace the markdown body (and refresh the ai-flow header /
            merge base as today).
          3. If there is no local frontmatter (fresh pull, or a plan that never had one),
            write header + markdown body only — do not synthesize fake todos. Cursor
            may add frontmatter the next time it opens the file; that is fine and stays
            local.

          Conflict / status semantics

          • ahead / behind / diverged compare markdown bodies against the merge base.
          • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
            mark the plan dirty for sync and must not trigger a meaningful push
            (body unchanged → existing "already in sync" path).
          • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
            carried through from the local side untouched.

          Edge cases

          • Draft already linked with frontmatter on GH (today's bug residue): first
            push after the fix rewrites the issue body to markdown-only (stripping the
            published frontmatter). That is an intentional one-time cleanup, not a
            conflict — remote "change" is us removing noise we incorrectly published.
          • Malformed / non-Cursor leading ---: only peel a block that looks like
            Cursor plan frontmatter (opening --- on its own line near the top,
            closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
            the body are left alone.
          • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
            header, then optional frontmatter, then markdown. If a hand-edited file has
            frontmatter above the ai-flow header, normalize on the next pull/push
            write.

          Implementation sketch

          Non-goals

          • Syncing Cursor todos to GitHub checklists (or the reverse).
          • Changing the <!-- ai-flow --> header format or conflict-guard rules.
          • Requiring frontmatter on every local plan — absence remains valid.

          Subtasks

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Cursor plan header metadata vs GH issue as plan format discrepancy #37

              Description

              @JPDuchesne

              Cursor plan header metadata vs GH issue as plan format discrepancy

              Problem

              Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
              plan UI owns — typically name, overview, todos, and isProject:

              ---
              name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
              - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
              ---

              That frontmatter is local editor state: a plan picker label, a short overview,
              and a checklist Cursor ticks as it works. It is not plan prose.

              Today dev plan link / push treat everything after the <!-- ai-flow -->
              sync header as the issue body
              (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
              https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
              Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
              frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
              GitHub, where it renders as noisy leading --- / key soup above the real
              markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
              have no frontmatter until Cursor invents one locally — so the two sides already
              disagree about what "the body" is.

              Decision

              Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
              canonical markdown plan only (starting at the # title / prose). Frontmatter
              stays a local-only envelope around that body.

              Rationale:

              • GitHub already has its own title; name / overview duplicate it poorly.
              • todos are Cursor session progress, not shared plan content — publishing them
                invites stale checklists and review noise.
              • The sync contract already says the issue body is what reviewers edit via
                /ask / /edit; frontmatter has no role there.

              The <!-- ai-flow --> HTML comment remains local-only as today (never part of
              the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
              UI metadata.

              Layers of a linked local plan file

              <!-- ai-flow
              issue: owner/repo#n
              synced_at: …
              -->
              ---
              name: …
              overview: …
              todos: […]
              isProject: false
              ---
              # Plan title
              …markdown body…
              
              LayerLives on diskLives on GitHubOwned by
              <!-- ai-flow --> headeryesnodev plan sync guard
              YAML frontmatter (------)yes (optional)noCursor plan UI
              Markdown bodyyesyes (canonical)humans + ai-flow review

              How this plays with pull / push / link

              All sync comparisons, merge bases, and issue PATCHes operate on the markdown
              body only
              — never on frontmatter.

              dev plan push / link <file> (local → remote)

              1. Header.split → ai-flow header + remainder (unchanged).
              2. Split remainder into optional YAML frontmatter + markdown body (new).
              3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
              4. Merge base stores that same markdown body.
              5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
                markdown body. Frontmatter is not deleted locally; it is simply not shipped.

              Title extraction (extract_title) already keys off the first # … line; it
              must run on the markdown body after frontmatter is peeled off so a name: key
              cannot be mistaken for content.

              dev plan pull (remote → local)

              1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
              2. If a local file exists, peel and preserve its YAML frontmatter across the
                rewrite; only replace the markdown body (and refresh the ai-flow header /
                merge base as today).
              3. If there is no local frontmatter (fresh pull, or a plan that never had one),
                write header + markdown body only — do not synthesize fake todos. Cursor
                may add frontmatter the next time it opens the file; that is fine and stays
                local.

              Conflict / status semantics

              • ahead / behind / diverged compare markdown bodies against the merge base.
              • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
                mark the plan dirty for sync and must not trigger a meaningful push
                (body unchanged → existing "already in sync" path).
              • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
                carried through from the local side untouched.

              Edge cases

              • Draft already linked with frontmatter on GH (today's bug residue): first
                push after the fix rewrites the issue body to markdown-only (stripping the
                published frontmatter). That is an intentional one-time cleanup, not a
                conflict — remote "change" is us removing noise we incorrectly published.
              • Malformed / non-Cursor leading ---: only peel a block that looks like
                Cursor plan frontmatter (opening --- on its own line near the top,
                closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
                the body are left alone.
              • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
                header, then optional frontmatter, then markdown. If a hand-edited file has
                frontmatter above the ai-flow header, normalize on the next pull/push
                write.

              Implementation sketch

              Non-goals

              • Syncing Cursor todos to GitHub checklists (or the reverse).
              • Changing the <!-- ai-flow --> header format or conflict-guard rules.
              • Requiring frontmatter on every local plan — absence remains valid.

              Subtasks

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Cursor plan header metadata vs GH issue as plan format discrepancy #37

                  Description

                  @JPDuchesne

                  Cursor plan header metadata vs GH issue as plan format discrepancy

                  Problem

                  Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
                  plan UI owns — typically name, overview, todos, and isProject:

                  ---
                  name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
                  - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
                  ---

                  That frontmatter is local editor state: a plan picker label, a short overview,
                  and a checklist Cursor ticks as it works. It is not plan prose.

                  Today dev plan link / push treat everything after the <!-- ai-flow -->
                  sync header as the issue body
                  (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
                  https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
                  Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
                  frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
                  GitHub, where it renders as noisy leading --- / key soup above the real
                  markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
                  have no frontmatter until Cursor invents one locally — so the two sides already
                  disagree about what "the body" is.

                  Decision

                  Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
                  canonical markdown plan only (starting at the # title / prose). Frontmatter
                  stays a local-only envelope around that body.

                  Rationale:

                  • GitHub already has its own title; name / overview duplicate it poorly.
                  • todos are Cursor session progress, not shared plan content — publishing them
                    invites stale checklists and review noise.
                  • The sync contract already says the issue body is what reviewers edit via
                    /ask / /edit; frontmatter has no role there.

                  The <!-- ai-flow --> HTML comment remains local-only as today (never part of
                  the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
                  UI metadata.

                  Layers of a linked local plan file

                  <!-- ai-flow
                  issue: owner/repo#n
                  synced_at: …
                  -->
                  ---
                  name: …
                  overview: …
                  todos: […]
                  isProject: false
                  ---
                  # Plan title
                  …markdown body…
                  
                  LayerLives on diskLives on GitHubOwned by
                  <!-- ai-flow --> headeryesnodev plan sync guard
                  YAML frontmatter (------)yes (optional)noCursor plan UI
                  Markdown bodyyesyes (canonical)humans + ai-flow review

                  How this plays with pull / push / link

                  All sync comparisons, merge bases, and issue PATCHes operate on the markdown
                  body only
                  — never on frontmatter.

                  dev plan push / link <file> (local → remote)

                  1. Header.split → ai-flow header + remainder (unchanged).
                  2. Split remainder into optional YAML frontmatter + markdown body (new).
                  3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
                  4. Merge base stores that same markdown body.
                  5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
                    markdown body. Frontmatter is not deleted locally; it is simply not shipped.

                  Title extraction (extract_title) already keys off the first # … line; it
                  must run on the markdown body after frontmatter is peeled off so a name: key
                  cannot be mistaken for content.

                  dev plan pull (remote → local)

                  1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
                  2. If a local file exists, peel and preserve its YAML frontmatter across the
                    rewrite; only replace the markdown body (and refresh the ai-flow header /
                    merge base as today).
                  3. If there is no local frontmatter (fresh pull, or a plan that never had one),
                    write header + markdown body only — do not synthesize fake todos. Cursor
                    may add frontmatter the next time it opens the file; that is fine and stays
                    local.

                  Conflict / status semantics

                  • ahead / behind / diverged compare markdown bodies against the merge base.
                  • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
                    mark the plan dirty for sync and must not trigger a meaningful push
                    (body unchanged → existing "already in sync" path).
                  • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
                    carried through from the local side untouched.

                  Edge cases

                  • Draft already linked with frontmatter on GH (today's bug residue): first
                    push after the fix rewrites the issue body to markdown-only (stripping the
                    published frontmatter). That is an intentional one-time cleanup, not a
                    conflict — remote "change" is us removing noise we incorrectly published.
                  • Malformed / non-Cursor leading ---: only peel a block that looks like
                    Cursor plan frontmatter (opening --- on its own line near the top,
                    closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
                    the body are left alone.
                  • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
                    header, then optional frontmatter, then markdown. If a hand-edited file has
                    frontmatter above the ai-flow header, normalize on the next pull/push
                    write.

                  Implementation sketch

                  Non-goals

                  • Syncing Cursor todos to GitHub checklists (or the reverse).
                  • Changing the <!-- ai-flow --> header format or conflict-guard rules.
                  • Requiring frontmatter on every local plan — absence remains valid.

                  Subtasks

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Cursor plan header metadata vs GH issue as plan format discrepancy #37

                      Description

                      @JPDuchesne

                      Cursor plan header metadata vs GH issue as plan format discrepancy

                      Problem

                      Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
                      plan UI owns — typically name, overview, todos, and isProject:

                      ---
                      name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
                      - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
                      ---

                      That frontmatter is local editor state: a plan picker label, a short overview,
                      and a checklist Cursor ticks as it works. It is not plan prose.

                      Today dev plan link / push treat everything after the <!-- ai-flow -->
                      sync header as the issue body
                      (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
                      https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
                      Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
                      frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
                      GitHub, where it renders as noisy leading --- / key soup above the real
                      markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
                      have no frontmatter until Cursor invents one locally — so the two sides already
                      disagree about what "the body" is.

                      Decision

                      Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
                      canonical markdown plan only (starting at the # title / prose). Frontmatter
                      stays a local-only envelope around that body.

                      Rationale:

                      • GitHub already has its own title; name / overview duplicate it poorly.
                      • todos are Cursor session progress, not shared plan content — publishing them
                        invites stale checklists and review noise.
                      • The sync contract already says the issue body is what reviewers edit via
                        /ask / /edit; frontmatter has no role there.

                      The <!-- ai-flow --> HTML comment remains local-only as today (never part of
                      the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
                      UI metadata.

                      Layers of a linked local plan file

                      <!-- ai-flow
                      issue: owner/repo#n
                      synced_at: …
                      -->
                      ---
                      name: …
                      overview: …
                      todos: […]
                      isProject: false
                      ---
                      # Plan title
                      …markdown body…
                      
                      LayerLives on diskLives on GitHubOwned by
                      <!-- ai-flow --> headeryesnodev plan sync guard
                      YAML frontmatter (------)yes (optional)noCursor plan UI
                      Markdown bodyyesyes (canonical)humans + ai-flow review

                      How this plays with pull / push / link

                      All sync comparisons, merge bases, and issue PATCHes operate on the markdown
                      body only
                      — never on frontmatter.

                      dev plan push / link <file> (local → remote)

                      1. Header.split → ai-flow header + remainder (unchanged).
                      2. Split remainder into optional YAML frontmatter + markdown body (new).
                      3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
                      4. Merge base stores that same markdown body.
                      5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
                        markdown body. Frontmatter is not deleted locally; it is simply not shipped.

                      Title extraction (extract_title) already keys off the first # … line; it
                      must run on the markdown body after frontmatter is peeled off so a name: key
                      cannot be mistaken for content.

                      dev plan pull (remote → local)

                      1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
                      2. If a local file exists, peel and preserve its YAML frontmatter across the
                        rewrite; only replace the markdown body (and refresh the ai-flow header /
                        merge base as today).
                      3. If there is no local frontmatter (fresh pull, or a plan that never had one),
                        write header + markdown body only — do not synthesize fake todos. Cursor
                        may add frontmatter the next time it opens the file; that is fine and stays
                        local.

                      Conflict / status semantics

                      • ahead / behind / diverged compare markdown bodies against the merge base.
                      • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
                        mark the plan dirty for sync and must not trigger a meaningful push
                        (body unchanged → existing "already in sync" path).
                      • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
                        carried through from the local side untouched.

                      Edge cases

                      • Draft already linked with frontmatter on GH (today's bug residue): first
                        push after the fix rewrites the issue body to markdown-only (stripping the
                        published frontmatter). That is an intentional one-time cleanup, not a
                        conflict — remote "change" is us removing noise we incorrectly published.
                      • Malformed / non-Cursor leading ---: only peel a block that looks like
                        Cursor plan frontmatter (opening --- on its own line near the top,
                        closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
                        the body are left alone.
                      • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
                        header, then optional frontmatter, then markdown. If a hand-edited file has
                        frontmatter above the ai-flow header, normalize on the next pull/push
                        write.

                      Implementation sketch

                      Non-goals

                      • Syncing Cursor todos to GitHub checklists (or the reverse).
                      • Changing the <!-- ai-flow --> header format or conflict-guard rules.
                      • Requiring frontmatter on every local plan — absence remains valid.

                      Subtasks

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Cursor plan header metadata vs GH issue as plan format discrepancy #37

                          Description

                          @JPDuchesne

                          Cursor plan header metadata vs GH issue as plan format discrepancy

                          Problem

                          Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
                          plan UI owns — typically name, overview, todos, and isProject:

                          ---
                          name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
                          - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
                          ---

                          That frontmatter is local editor state: a plan picker label, a short overview,
                          and a checklist Cursor ticks as it works. It is not plan prose.

                          Today dev plan link / push treat everything after the <!-- ai-flow -->
                          sync header as the issue body
                          (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
                          https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
                          Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
                          frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
                          GitHub, where it renders as noisy leading --- / key soup above the real
                          markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
                          have no frontmatter until Cursor invents one locally — so the two sides already
                          disagree about what "the body" is.

                          Decision

                          Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
                          canonical markdown plan only (starting at the # title / prose). Frontmatter
                          stays a local-only envelope around that body.

                          Rationale:

                          • GitHub already has its own title; name / overview duplicate it poorly.
                          • todos are Cursor session progress, not shared plan content — publishing them
                            invites stale checklists and review noise.
                          • The sync contract already says the issue body is what reviewers edit via
                            /ask / /edit; frontmatter has no role there.

                          The <!-- ai-flow --> HTML comment remains local-only as today (never part of
                          the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
                          UI metadata.

                          Layers of a linked local plan file

                          <!-- ai-flow
                          issue: owner/repo#n
                          synced_at: …
                          -->
                          ---
                          name: …
                          overview: …
                          todos: […]
                          isProject: false
                          ---
                          # Plan title
                          …markdown body…
                          
                          LayerLives on diskLives on GitHubOwned by
                          <!-- ai-flow --> headeryesnodev plan sync guard
                          YAML frontmatter (------)yes (optional)noCursor plan UI
                          Markdown bodyyesyes (canonical)humans + ai-flow review

                          How this plays with pull / push / link

                          All sync comparisons, merge bases, and issue PATCHes operate on the markdown
                          body only
                          — never on frontmatter.

                          dev plan push / link <file> (local → remote)

                          1. Header.split → ai-flow header + remainder (unchanged).
                          2. Split remainder into optional YAML frontmatter + markdown body (new).
                          3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
                          4. Merge base stores that same markdown body.
                          5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
                            markdown body. Frontmatter is not deleted locally; it is simply not shipped.

                          Title extraction (extract_title) already keys off the first # … line; it
                          must run on the markdown body after frontmatter is peeled off so a name: key
                          cannot be mistaken for content.

                          dev plan pull (remote → local)

                          1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
                          2. If a local file exists, peel and preserve its YAML frontmatter across the
                            rewrite; only replace the markdown body (and refresh the ai-flow header /
                            merge base as today).
                          3. If there is no local frontmatter (fresh pull, or a plan that never had one),
                            write header + markdown body only — do not synthesize fake todos. Cursor
                            may add frontmatter the next time it opens the file; that is fine and stays
                            local.

                          Conflict / status semantics

                          • ahead / behind / diverged compare markdown bodies against the merge base.
                          • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
                            mark the plan dirty for sync and must not trigger a meaningful push
                            (body unchanged → existing "already in sync" path).
                          • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
                            carried through from the local side untouched.

                          Edge cases

                          • Draft already linked with frontmatter on GH (today's bug residue): first
                            push after the fix rewrites the issue body to markdown-only (stripping the
                            published frontmatter). That is an intentional one-time cleanup, not a
                            conflict — remote "change" is us removing noise we incorrectly published.
                          • Malformed / non-Cursor leading ---: only peel a block that looks like
                            Cursor plan frontmatter (opening --- on its own line near the top,
                            closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
                            the body are left alone.
                          • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
                            header, then optional frontmatter, then markdown. If a hand-edited file has
                            frontmatter above the ai-flow header, normalize on the next pull/push
                            write.

                          Implementation sketch

                          Non-goals

                          • Syncing Cursor todos to GitHub checklists (or the reverse).
                          • Changing the <!-- ai-flow --> header format or conflict-guard rules.
                          • Requiring frontmatter on every local plan — absence remains valid.

                          Subtasks

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Cursor plan header metadata vs GH issue as plan format discrepancy #37

                              Description

                              @JPDuchesne

                              Cursor plan header metadata vs GH issue as plan format discrepancy

                              Problem

                              Cursor plan files (.plan.md) carry a YAML frontmatter block that Cursor's
                              plan UI owns — typically name, overview, todos, and isProject:

                              ---
                              name: Organic diapedesis deformationoverview: Improve the squeeze visual…todos:
                              - id: squeeze-anglecontent: Add squeezeAngle to Membrane…status: completedisProject: false
                              ---

                              That frontmatter is local editor state: a plan picker label, a short overview,
                              and a checklist Cursor ticks as it works. It is not plan prose.

                              Today dev plan link / push treat everything after the <!-- ai-flow -->
                              sync header as the issue body
                              (https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/header.rb,
                              https://github.com/d3mlabs/dev/blob/HEAD/lib/dev/plan/accessor.rb).
                              Plan.to_issue_body / from_issue_body are verbatim — they do not strip YAML
                              frontmatter. So linking a Cursor-native draft publishes the frontmatter onto
                              GitHub, where it renders as noisy leading --- / key soup above the real
                              markdown. Conversely, plans born on GitHub (dev plan new / a clean pull)
                              have no frontmatter until Cursor invents one locally — so the two sides already
                              disagree about what "the body" is.

                              Decision

                              Do not keep Cursor YAML frontmatter on GitHub. The issue body is the
                              canonical markdown plan only (starting at the # title / prose). Frontmatter
                              stays a local-only envelope around that body.

                              Rationale:

                              • GitHub already has its own title; name / overview duplicate it poorly.
                              • todos are Cursor session progress, not shared plan content — publishing them
                                invites stale checklists and review noise.
                              • The sync contract already says the issue body is what reviewers edit via
                                /ask / /edit; frontmatter has no role there.

                              The <!-- ai-flow --> HTML comment remains local-only as today (never part of
                              the issue body). It is orthogonal to YAML frontmatter: sync metadata vs Cursor
                              UI metadata.

                              Layers of a linked local plan file

                              <!-- ai-flow
                              issue: owner/repo#n
                              synced_at: …
                              -->
                              ---
                              name: …
                              overview: …
                              todos: […]
                              isProject: false
                              ---
                              # Plan title
                              …markdown body…
                              
                              LayerLives on diskLives on GitHubOwned by
                              <!-- ai-flow --> headeryesnodev plan sync guard
                              YAML frontmatter (------)yes (optional)noCursor plan UI
                              Markdown bodyyesyes (canonical)humans + ai-flow review

                              How this plays with pull / push / link

                              All sync comparisons, merge bases, and issue PATCHes operate on the markdown
                              body only
                              — never on frontmatter.

                              dev plan push / link <file> (local → remote)

                              1. Header.split → ai-flow header + remainder (unchanged).
                              2. Split remainder into optional YAML frontmatter + markdown body (new).
                              3. Publish / create the issue with Plan.to_issue_body(markdown_body) only.
                              4. Merge base stores that same markdown body.
                              5. Rewrite the local file as: ai-flow header + (preserved frontmatter, if any) +
                                markdown body. Frontmatter is not deleted locally; it is simply not shipped.

                              Title extraction (extract_title) already keys off the first # … line; it
                              must run on the markdown body after frontmatter is peeled off so a name: key
                              cannot be mistaken for content.

                              dev plan pull (remote → local)

                              1. Fetch issue body → that string is the markdown body (no frontmatter on GH).
                              2. If a local file exists, peel and preserve its YAML frontmatter across the
                                rewrite; only replace the markdown body (and refresh the ai-flow header /
                                merge base as today).
                              3. If there is no local frontmatter (fresh pull, or a plan that never had one),
                                write header + markdown body only — do not synthesize fake todos. Cursor
                                may add frontmatter the next time it opens the file; that is fine and stays
                                local.

                              Conflict / status semantics

                              • ahead / behind / diverged compare markdown bodies against the merge base.
                              • Editing only frontmatter (ticking a todo, renaming in Cursor) must not
                                mark the plan dirty for sync and must not trigger a meaningful push
                                (body unchanged → existing "already in sync" path).
                              • Three-way merge (pull --merge) merges markdown bodies only; frontmatter is
                                carried through from the local side untouched.

                              Edge cases

                              • Draft already linked with frontmatter on GH (today's bug residue): first
                                push after the fix rewrites the issue body to markdown-only (stripping the
                                published frontmatter). That is an intentional one-time cleanup, not a
                                conflict — remote "change" is us removing noise we incorrectly published.
                              • Malformed / non-Cursor leading ---: only peel a block that looks like
                                Cursor plan frontmatter (opening --- on its own line near the top,
                                closing ---, YAML mapping). Ordinary markdown horizontal rules deeper in
                                the body are left alone.
                              • ai-flow header vs frontmatter order: canonical on-disk order is ai-flow
                                header, then optional frontmatter, then markdown. If a hand-edited file has
                                frontmatter above the ai-flow header, normalize on the next pull/push
                                write.

                              Implementation sketch

                              Non-goals

                              • Syncing Cursor todos to GitHub checklists (or the reverse).
                              • Changing the <!-- ai-flow --> header format or conflict-guard rules.
                              • Requiring frontmatter on every local plan — absence remains valid.

                              Subtasks

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions