<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[I got tired of TODO comments losing context, so I built GhostMap]]></title><description><![CDATA[I got tired of TODO comments losing context, so I built GhostMap]]></description><link>https://ghostmap.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a4093b87ea301f4574c43bf/68fb28d4-dcff-4ce1-a0a7-9b585ee03cc8.png</url><title>I got tired of TODO comments losing context, so I built GhostMap</title><link>https://ghostmap.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 03:48:06 GMT</lastBuildDate><atom:link href="https://ghostmap.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I got tired of TODO comments losing context, so I built GhostMap]]></title><description><![CDATA[I opened an old TypeScript service after a few weeks away from it and found this:
// TODO: fix retry logic
async chargeCustomer(input: ChargeInput) {
  // ...
}

The comment was technically useful whe]]></description><link>https://ghostmap.hashnode.dev/i-got-tired-of-todo-comments-losing-context-so-i-built-ghostmap</link><guid isPermaLink="true">https://ghostmap.hashnode.dev/i-got-tired-of-todo-comments-losing-context-so-i-built-ghostmap</guid><category><![CDATA[vscode extensions]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[devtools]]></category><dc:creator><![CDATA[milovale]]></dc:creator><pubDate>Mon, 06 Jul 2026 10:09:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a4093b87ea301f4574c43bf/2d941af2-976b-4d91-a482-71cc8b907eb2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I opened an old TypeScript service after a few weeks away from it and found this:</p>
<pre><code class="language-ts">// TODO: fix retry logic
async chargeCustomer(input: ChargeInput) {
  // ...
}
</code></pre>
<p>The comment was technically useful when I wrote it.</p>
<p>Later, it was almost useless.</p>
<p>It did not tell me why the retry logic was risky. It did not tell me whether the issue was still open. It did not tell me if the problem was duplicate charges, provider timeouts, idempotency keys, or cleanup I never finished.</p>
<p>That is the problem I kept running into with TODO and FIXME comments.</p>
<p>They are easy to write, but they lose context fast.</p>
<p>So I built GhostMap, a VS Code extension for structured <code>@ghost</code> annotations.</p>
<p>The idea is simple:</p>
<p>Keep developer context inside the code, but make it structured and navigable instead of leaving it as scattered comments.</p>
<p>Marketplace:</p>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Ghostmap.ghostmap">https://marketplace.visualstudio.com/items?itemName=Ghostmap.ghostmap</a></p>
<p>Product site:</p>
<p><a href="https://ghostmap-liard.vercel.app/">https://ghostmap-liard.vercel.app/</a></p>
<p>Docs:</p>
<p><a href="https://ghostmap-docs.vercel.app/">https://ghostmap-docs.vercel.app/</a></p>
<h2>Why TODO comments decay</h2>
<p>A normal TODO usually captures one thing:</p>
<p>Something is unfinished.</p>
<pre><code class="language-ts">// TODO: handle timeout
</code></pre>
<p>That is fine for short-lived notes.</p>
<p>It breaks down when the comment stays in the code longer than expected.</p>
<p>After a week or two, you usually need more than the note itself:</p>
<ul>
<li><p>What is the actual risk?</p>
</li>
<li><p>Is this still open?</p>
</li>
<li><p>Who should care about it?</p>
</li>
<li><p>Does it belong to the function below it, the whole class, or a larger section?</p>
</li>
<li><p>Is it a cleanup task, production risk, migration marker, or review note?</p>
</li>
<li><p>Is it still relevant after the last refactor?</p>
</li>
</ul>
<p>The comment is close to the code, which is good.</p>
<p>The problem is that it has no structure.</p>
<p>Issue trackers solve some of this, but they are not always the right place for code-local context. Pull requests solve some of it too, but PR context is often gone from your head by the time you revisit the file.</p>
<p>I wanted something lighter than a tracker and more durable than a random TODO.</p>
<h2>A small structure upgrade</h2>
<p>Instead of this:</p>
<pre><code class="language-ts">// TODO: retry issue
</code></pre>
<p>GhostMap uses this:</p>
<pre><code class="language-ts">// @ghost #payment-retry status:in-progress description: retry can double-charge if provider timeout returns late
</code></pre>
<p>That one line carries more useful information:</p>
<ul>
<li><p><code>@ghost</code> marks it as intentional project context</p>
</li>
<li><p><code>#payment-retry</code> gives it a stable name</p>
</li>
<li><p><code>status:in-progress</code> makes the state visible</p>
</li>
<li><p><code>description:</code> keeps the reason next to the code</p>
</li>
</ul>
<p>GhostMap then turns those annotations into a navigable tree inside VS Code.</p>
<p>That is the main difference.</p>
<p>It is not just coloring comments.</p>
<p>It is turning important code-local context into something you can browse and jump to.</p>
<h2>A realistic example</h2>
<p>Here is a simplified payment service:</p>
<pre><code class="language-ts">export class PaymentService {
  // @ghost #payment-retry status:in-progress description: retry can double-charge if provider timeout returns late
  async chargeCustomer(input: ChargeInput) {
    const payment = await this.provider.charge(input);
    return payment;
  }

  // @ghost description: review idempotency behavior before enterprise rollout | status:review
  private buildIdempotencyKey(userId: string, invoiceId: string) {
    return `${userId}:${invoiceId}`;
  }

  // @ghost #legacy-refund-flow start status:todo description: remove once refunds move to provider v3
  async refundPayment(paymentId: string) {
    // old implementation
  }

  async refundPartialPayment(paymentId: string, amount: number) {
    // old implementation
  }

  // @ghost end
}
</code></pre>
<p>There are three patterns here:</p>
<ol>
<li><p>named anchors</p>
</li>
<li><p>contextual annotations</p>
</li>
<li><p>range anchors</p>
</li>
</ol>
<h2>Named anchors</h2>
<pre><code class="language-ts">// @ghost #payment-retry status:in-progress description: retry can double-charge if provider timeout returns late
</code></pre>
<p>A named anchor is useful when the note deserves its own identity.</p>
<p>Examples:</p>
<pre><code class="language-text">#payment-retry
#auth-hardening
#v2-migration
#danger-zone
#cleanup-before-release
</code></pre>
<p>This is the kind of annotation I would use for a risky area, a migration point, or something I expect to revisit later.</p>
<p>It becomes easier to talk about because it has a name.</p>
<p>Instead of saying:</p>
<p>Look at that retry TODO in the payment service.</p>
<p>You can say:</p>
<p>Check <code>#payment-retry</code>.</p>
<p>That small naming step matters more than I expected.</p>
<h2>Contextual annotations</h2>
<pre><code class="language-ts">// @ghost description: review idempotency behavior before enterprise rollout | status:review
private buildIdempotencyKey(userId: string, invoiceId: string) {
  return `${userId}:${invoiceId}`;
}
</code></pre>
<p>A contextual annotation does not need a named node.</p>
<p>It adds context to the nearby symbol.</p>
<p>That is useful when the important thing is not the annotation itself, but the function, method, or class it belongs to.</p>
<p>In this case, the note belongs to <code>buildIdempotencyKey</code>.</p>
<p>I do not need a separate named item for it. I just need the context to stay attached to the code.</p>
<h2>Range anchors</h2>
<pre><code class="language-ts">// @ghost #legacy-refund-flow start status:todo description: remove once refunds move to provider v3
async refundPayment(paymentId: string) {
  // old implementation
}

async refundPartialPayment(paymentId: string, amount: number) {
  // old implementation
}

// @ghost end
</code></pre>
<p>A range anchor marks a section of code.</p>
<p>That is useful for:</p>
<ul>
<li><p>migrations</p>
</li>
<li><p>risky regions</p>
</li>
<li><p>refactor zones</p>
</li>
<li><p>temporary compatibility layers</p>
</li>
<li><p>code that should disappear after a version upgrade</p>
</li>
<li><p>implementation areas that should be reviewed together</p>
</li>
</ul>
<p>This was one of the main reasons I wanted GhostMap to exist.</p>
<p>A lot of important context is not attached to one single line.</p>
<p>Sometimes the context belongs to a region.</p>
<p>Normal TODO comments are weak at that.</p>
<h2>What GhostMap does inside VS Code</h2>
<p>GhostMap is currently focused on the file you have open.</p>
<p>It builds a Ghost Tree for the active file and shows code structure like classes, methods, functions, fields, web structure, selectors, and <code>@ghost</code> anchors.</p>
<p>You can click a tree item to jump to the matching line.</p>
<p>You can also use <code>@ghost</code> annotations to keep notes, regions, and work markers next to the code they describe.</p>
<p>The current version supports multiple programming languages and common web, config, and documentation file types.</p>
<p>Support quality is not identical across every language and file type. Some languages have stronger symbol extraction than others, and some depend on the editor language server.</p>
<p>But the core idea is the same:</p>
<p>Open a file, see its structure, and keep intentional annotations close to the code.</p>
<h2>Snippets</h2>
<p>GhostMap includes snippets so you do not have to type the annotation format manually.</p>
<p>For named annotations:</p>
<pre><code class="language-ts">// @ghost #name description:  | status: todo
</code></pre>
<p>For contextual annotations:</p>
<pre><code class="language-ts">// @ghost description:  | status: todo
</code></pre>
<p>For ranges:</p>
<pre><code class="language-ts">// @ghost #name start description:  | status: todo

// @ghost end
</code></pre>
<p>The syntax is intentionally plain text.</p>
<p>I wanted the annotations to stay readable even without the extension installed.</p>
<p>That was important to me.</p>
<p>If a teammate opens the file without GhostMap, the comment should still make sense.</p>
<h2>What GhostMap is not</h2>
<p>GhostMap is not trying to replace your issue tracker.</p>
<p>Some work belongs in GitHub Issues, Linear, Jira, or a project board. If something needs prioritization, assignment, discussion, sprint planning, or cross-team visibility, it probably belongs there.</p>
<p>GhostMap is for context that is useful because it lives next to the code.</p>
<p>It is also not a workspace-wide map yet.</p>
<p>V1 maps the active file. Workspace-wide indexing is something I want to explore later, but I wanted the active-file experience to be useful first.</p>
<p>It is not an AI tool either.</p>
<p>That said, I think structured annotations can become useful when working with AI coding agents, because vague comments are bad context for both humans and tools.</p>
<p>A plain TODO says almost nothing.</p>
<p>A structured annotation gives the code a small amount of memory.</p>
<h2>Privacy and trust</h2>
<p>GhostMap runs locally.</p>
<p>The extension is designed so your source code does not get sent to GhostMap maintainers or GhostMap servers.</p>
<p>That matters because code annotation tools have to earn trust.</p>
<p>If a tool is reading your code structure, the default should be clear:</p>
<p>Your project stays on your machine.</p>
<h2>Current limitations</h2>
<p>This is still pre-1.0.</p>
<p>Known limitations today:</p>
<ul>
<li><p>GhostMap maps the active file, not the whole workspace</p>
</li>
<li><p>Block and JSDoc comments are not the main <code>@ghost</code> syntax in V1</p>
</li>
<li><p>Some language-specific symbol edge cases are still being cleaned up</p>
</li>
<li><p>Support quality varies by language and file type</p>
</li>
<li><p>GhostMap is source-available under a non-commercial license, not OSI open source</p>
</li>
<li><p>Personal, educational, evaluation, testing, and other non-commercial use are allowed</p>
</li>
<li><p>Company, business, production, revenue-generating, client, resale, white-label, marketplace-republish, and competing-product use require written authorization</p>
</li>
</ul>
<p>I am being upfront about this because developer tools only earn trust when the rough edges are visible.</p>
<p>I would rather say what is missing now than pretend the MVP is more mature than it is.</p>
<h2>Why I built it this way</h2>
<p>I did not want another SaaS dashboard.</p>
<p>I did not want another place where code context goes to die.</p>
<p>The code already has the most important location context.</p>
<p>The missing part is structure.</p>
<p>A plain comment says:</p>
<p>remember this</p>
<p>A structured annotation says:</p>
<p>remember this, here is what it is called, here is its state, here is why it matters, and here is where it lives</p>
<p>That small difference makes old code easier to re-enter.</p>
<p>It also makes TODO-style comments feel less disposable.</p>
<h2>Try it</h2>
<p>GhostMap is available on the VS Code Marketplace:</p>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Ghostmap.ghostmap">https://marketplace.visualstudio.com/items?itemName=Ghostmap.ghostmap</a></p>
<p>Docs:</p>
<p><a href="https://ghostmap-docs.vercel.app/">https://ghostmap-docs.vercel.app/</a></p>
<p>Product site:</p>
<p><a href="https://ghostmap-liard.vercel.app/">https://ghostmap-liard.vercel.app/</a></p>
<p>If you already use TODO Tree, Better Comments, VS Code Outline, or your own TODO convention, I would especially like feedback on one question:</p>
<p>Would structured annotations fit your workflow, or should this stay closer to normal TODO comments?</p>
]]></content:encoded></item></channel></rss>