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 when I wrote it.
Later, it was almost useless.
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.
That is the problem I kept running into with TODO and FIXME comments.
They are easy to write, but they lose context fast.
So I built GhostMap, a VS Code extension for structured @ghost annotations.
The idea is simple:
Keep developer context inside the code, but make it structured and navigable instead of leaving it as scattered comments.
Marketplace:
https://marketplace.visualstudio.com/items?itemName=Ghostmap.ghostmap
The problem with normal TODO comments
A normal TODO usually captures one thing:
Something is unfinished.
// TODO: handle timeout
That is fine for short-lived notes.
It breaks down when the comment stays in the code longer than expected.
After a week or two, you usually need more than the note itself:
- What is the actual risk?
- Is this still open?
- Who should care about it?
- Does it belong to the function below it, the whole class, or a larger section?
- Is it a cleanup task, production risk, migration marker, or review note?
- Is it still relevant after the last refactor?
The comment is close to the code, which is good.
The problem is that it has no structure.
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.
I wanted something lighter than a tracker and more durable than a random TODO.
The smallest useful upgrade: structured comments
Instead of this:
// TODO: retry issue
GhostMap uses this:
// @ghost #payment-retry status:in-progress description: retry can double-charge if provider timeout returns late
That one line carries more useful information:
- @ghost marks it as intentional project context
- #payment-retry gives it a stable name
- status:in-progress makes the state visible
- description: keeps the reason next to the code
GhostMap then turns those annotations into a navigable tree inside VS Code.
That is the main difference.
It is not just coloring comments.
It is turning important code-local context into something you can browse and jump to.
A more realistic example
Here is a simplified payment service:
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
}
There are three patterns here.
1. Named anchors
// @ghost #payment-retry status:in-progress description: retry can double-charge if provider timeout returns late
A named anchor is useful when the note deserves its own identity.
Examples:
- #payment-retry
- #auth-hardening
- #v2-migration
- #danger-zone
- #cleanup-before-release
This is the kind of annotation I would use for a risky area, a migration point, or something I expect to revisit later.
It becomes easier to talk about because it has a name.
Instead of saying:
Look at that retry TODO in the payment service.
You can say:
Check #payment-retry.
That small naming step matters more than I expected.
2. Contextual annotations
// @ghost description: review idempotency behavior before enterprise rollout | status:review
private buildIdempotencyKey(userId: string, invoiceId: string) {
return `${userId}:${invoiceId}`;
}
A contextual annotation does not need a named node.
It adds context to the nearby symbol.
That is useful when the important thing is not the annotation itself, but the function, method, or class it belongs to.
In this case, the note belongs to buildIdempotencyKey.
I do not need a separate named item for it. I just need the context to stay attached to the code.
3. Range anchors
// @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
A range anchor marks a section of code.
That is useful for:
- migrations
- risky regions
- refactor zones
- temporary compatibility layers
- code that should disappear after a version upgrade
- implementation areas that should be reviewed together
This was one of the main reasons I wanted GhostMap to exist.
A lot of important context is not attached to one single line.
Sometimes the context belongs to a region.
Normal TODO comments are weak at that.
What GhostMap does inside VS Code
GhostMap is currently focused on the file you have open.
It builds a Ghost Tree for the active file and shows code structure like classes, methods, functions, fields, web structure, selectors, and @ghost anchors.
You can click a tree item to jump to the matching line.
You can also use @ghost annotations to keep notes, regions, and work markers next to the code they describe.
The current Marketplace version supports TypeScript, TSX, JavaScript, JSX, C#, Java, C++, C, Go, Rust, PHP, Python, Ruby, Dart, Scala, Groovy, Solidity, Julia, Elixir, Objective-C, Lua, and SQL.
It also supports web templates and styles like HTML, CSS, SCSS, Less, Vue, Svelte, and Astro, plus config and docs formats like JSON, YAML, TOML, XML, Markdown, Dockerfile, Makefile, and GraphQL.
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.
But the core idea is the same:
Open a file, see its structure, and keep intentional annotations close to the code.
Snippets
GhostMap includes snippets so you do not have to type the annotation format manually.
For named annotations:
// @ghost #name description: | status: todo
For contextual annotations:
// @ghost description: | status: todo
For ranges:
// @ghost #name start description: | status: todo
// @ghost end
The syntax is intentionally plain text.
I wanted the annotations to stay readable even without the extension installed.
That was important to me.
If a teammate opens the file without GhostMap, the comment should still make sense.
What GhostMap is not
GhostMap is not trying to replace your issue tracker.
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.
GhostMap is for context that is useful because it lives next to the code.
It is also not a workspace-wide map yet.
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.
It is not an AI tool either.
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.
A plain TODO says almost nothing.
A structured annotation gives the code a small amount of memory.
Privacy and trust
GhostMap runs locally.
The extension is designed so your source code does not get sent to GhostMap maintainers or GhostMap servers.
That matters because code annotation tools have to earn trust.
If a tool is reading your code structure, the default should be clear:
Your project stays on your machine.
Current limitations
This is still pre-1.0.
Known limitations today:
- GhostMap maps the active file, not the whole workspace
- Block and JSDoc comments are not the main @ghost syntax in V1
- Some language-specific symbol edge cases are still being cleaned up
- Support quality varies by language and file type
- GhostMap is source-available under a non-commercial license, not OSI open source
- Personal, educational, evaluation, testing, and other non-commercial use are allowed
- Company, business, production, revenue-generating, client, resale, white-label, marketplace-republish, and competing-product use require written authorization
I am being upfront about this because developer tools only earn trust when the rough edges are visible.
I would rather say what is missing now than pretend the MVP is more mature than it is.
Why I built it this way
I did not want another SaaS dashboard.
I did not want another place where code context goes to die.
The code already has the most important location context.
The missing part is structure.
A plain comment says:
remember this
A structured annotation says:
remember this, here is what it is called, here is its state, here is why it matters, and here is where it lives
That small difference makes old code easier to re-enter.
It also makes TODO-style comments feel less disposable.
Try it
GhostMap is available on the VS Code Marketplace:
https://marketplace.visualstudio.com/items?itemName=Ghostmap.ghostmap
Docs:
https://ghostmap-docs.vercel.app/
Product site:
https://ghostmap-liard.vercel.app/
If you already use TODO Tree, Better Comments, VS Code Outline, or your own TODO convention, I would especially like feedback on one question:
Would structured annotations fit your workflow, or should this stay closer to normal TODO comments?
Top comments (0)