Source Cues
Bring project-specific meaning into your code editor. Encode team conventions,
unsafe patterns, domain terminology, and temporary code states as visual cues —
directly in source.

Why this exists
Language syntax tells you what the compiler sees. Linters tell you what the
language server thinks. But neither encodes what your team knows:
- Which functions are dangerous despite looking harmless?
- Which constants are register addresses, not ordinary numbers?
- Which log calls are debug leftovers that shouldn't reach production?
- Who owns this
TODO and when was it written?
Source Cues fills that gap. You define patterns → the editor surfaces
meaning directly in code. It's the layer between syntax highlighting
(language-defined) and diagnostics (LSP-defined) — a lightweight, regex-driven
way to make tribal knowledge visible.
Real-world use cases
| Domain |
What you'd mark |
Why |
| Embedded / firmware |
REG_[A-Z0-9_]+, 0x400[0-9A-F]+ |
Register names and memory-mapped addresses look like ordinary constants — cue them so they stand out |
| Backend / services |
strcpy, strcat, gets, sprintf |
Unsafe C functions that should be caught during code review |
| Security review |
password, secret, apiKey, token |
Hardcoded secrets or sensitive variable names that need scrutiny |
| Legacy codebases |
DEPRECATED, DO_NOT_USE, LEGACY_API |
Markers that convey tribal knowledge to new team members |
| Debug hygiene |
console\.log, DEBUG_PRINT, print\( |
Calls that should be removed before merging |
| TODO tracking |
TODO\((\w+)\), FIXME, HACK |
Owner-tagged or untagged temporary markers — with minimap indicators |
How it works
Add a :cue: marker anywhere in your source file. When the regex matches,
the decoration options are passed to the VS Code Decorator
API.
Options can be an inline JSON object, or a #name reference to a style
defined in your workspace's .source-cues.json:
// :cue: /\b(REG_[A-Z0-9_]+)\b/ -> { "color": "#d7afff", "outline": "1px solid #d7afff", "fontWeight": "bold" }
// :cue: /\b(strtok|strcat|strcpy)\b/ -> #unsafe
// :cue: /DEBUG_PRINT/ -> { "dark": { "color": "#50fa7b" }, "light": { "color": "#008800" } }
#define REG_CTRL_STAT 0x40021000
#define REG_DATA_BUF 0x40021004
void process_telemetry(const char* packet) {
char local_copy[256];
strcpy(local_copy, packet);
DEBUG_PRINT("Processing new telemetry packet header.\n");
}
More demos are in the demos/ directory.
Quick start
- "Source Cues: Insert demo rules" — inserts a commented-out block of
example inline rules into the active file. Remove the space in
: cue: to
activate the ones you want.
- "Source Cues: Open workspace rules" — opens (or creates) the workspace
config file (
.source-cues.json) for the current project, seeded from a
template with working examples.
Both commands are available in the command palette (Ctrl+Shift+P /
Cmd+Shift+P).
What you can do with it
- Spot dangerous patterns instantly — mark unsafe functions, hardcoded
secrets, or deprecated APIs with red backgrounds and gutter icons so they're
impossible to miss during review.
- Surface domain terminology — make register names, error codes, or
business-logic constants visually distinct from ordinary variables.
- Track TODO ownership — use capture groups to highlight only the owner
name in
TODO(maya) so unowned items stand out.
- See problems in the minimap —
overviewRulerColor places markers in the
scrollbar so you can navigate issues without scrolling.
- Inject context after matches — the
after option appends virtual text
(like ⚠️ UNSAFE or ← owner) without modifying your source file.
- Work in any language, any project — rules are just regex. No language
server, no AST, no configuration UI needed.
- Stay safe in untrusted workspaces — when you open a workspace you
don't trust, both global config files and inline
:cue: directives are
disabled to prevent malicious patterns from affecting your editor.
Standalone files (no workspace) always allow inline rules since you
opened them intentionally.
Rules are re-evaluated on every keystroke against the full document text to
ensure correctness, debounced (configurable via sourceCues.debounceMs, default
200ms, set to 0 to disable) so rapid typing doesn't trigger redundant
rescans.
- There is no incremental/delta matching — each pass is a full rescan.
TextEditorDecorationType instances are cached and reused across passes
to minimize allocation churn. A type is only disposed when its associated rule
is removed.
What to expect
| File size |
With up to 10 rules |
With 20+ rules |
| < 2,000 lines |
Should feel instant on any modern hardware |
Smooth; rules are cheap |
| 2,000–5,000 lines |
No noticeable lag |
Brief pause possible on slower machines |
| 5,000–10,000 lines |
Scoping rules with include globs recommended |
Expect visible delay on each keystroke |
| > 10,000 lines |
Narrow rules aggressively with include globs |
May lag noticeably — consider disabling global rules for these files |
The extension ships with a sourceCues.maxFileSizeLines setting (default:
5000 lines). Files exceeding this threshold are skipped — a message is
logged to the "Source Cues" output channel so you know the limit was hit.
Set it to 0 to disable the limit and decorate files of any size.
If you experience slowdown: reduce the number of active rules, narrow their
scope with include globs, or scope global rules to specific file patterns so
they don't run on every file.
ReDoS protection
Source Cues protects against catastrophic backtracking
(ReDoS)
with a pre-flight complexity check. Before scanning the full document,
each regex is tested against a short prefix of the text. If the test takes
longer than 10ms, the pattern is skipped and disabled for the remainder of
the session. A warning is logged to the "Source Cues" output channel:
[Warning] Skipping rule /(a+)+$/ — pre-flight regex check took 189ms
(threshold: 10ms). The pattern has been disabled for this session to
prevent catastrophic backtracking (ReDoS).
This catches patterns with nested quantifiers like (a+)+$ or
([a-zA-Z]+)*$ before they can freeze the editor. To re-enable a blocked
pattern, fix the regex and reload the VS Code window.
🛠️ Workspace Setup (.source-cues.json)
Create a file named .source-cues.json at the root of your project workspace
to apply global rules. The easiest way is to run "Source Cues: Open workspace
rules" from the command palette — it creates the file from a template if it
doesn't exist yet.
The config file is a JSON object with two sections:
{
"styles": [
{ "name": "alert", "options": { "color": "#ff5555", "fontWeight": "bold" } }
],
"rules": [
{ "pattern": "CRITICAL", "options": "#alert" },
{ "pattern": "console\\.log", "include": ["**/*.js"], "options": { "textDecoration": "line-through" } }
]
}
styles — an array of named, reusable style definitions. Each style has a
name and an options object with VS Code decoration properties.
rules — an array of rule objects. Each rule has:
pattern — regex pattern (string) or { "source": "...", "flags": "i" }
options — either an inline decoration object or a "#name" string
referencing a style defined in styles
include (optional) — glob or array of globs to limit the rule to matching
files
Using #references
When multiple rules share the same styling, define it once in styles and
reference it by name:
{
"styles": [
{
"name": "danger",
"options": {
"color": "#ffffff",
"backgroundColor": "#cc0000",
"fontWeight": "bold",
"overviewRulerColor": "#ff0000",
"overviewRulerLane": 4
}
},
{
"name": "subtle-warn",
"options": { "color": "#ff9944", "fontStyle": "italic" }
}
],
"rules": [
{ "pattern": "CRITICAL_ERROR", "options": "#danger" },
{ "pattern": "DEPRECATED_API", "options": "#danger" },
{ "pattern": "FIXME", "options": "#subtle-warn" }
]
}
Rules can still use inline options objects — references and inline styles
can coexist in the same file.
⚙️ Settings
All settings are under the sourceCues.* namespace in VS Code's settings editor
or settings.json.
| Setting |
Type |
Default |
Description |
enableGlobalRules |
boolean |
true |
Load rules from .source-cues.json (requires trusted workspace) |
enableInlineRules |
boolean |
true |
Parse :cue: directives in open files (works in Restricted Mode) |
highlightDirectiveSyntax |
boolean |
true |
Tint directive lines green (valid) or orange (broken) while editing |
configFileName |
string |
".source-cues.json" |
Custom filename for workspace-level rules |
inlineTriggerKeyword |
string |
":cue:" |
Keyword that triggers an inline rule (e.g. ":cue:", ":highlight:") |
maxFileSizeLines |
number |
5000 |
Skip decoration on files above this line count (0 = no limit) |
debounceMs |
number |
200 |
Delay before re-evaluating after a text change (0 = no debounce) |
🧩 Regex Patterns & Quoting
Regex patterns work the same everywhere, but escaping differs between inline
rules and the workspace config file — this is the #1 cause of "my rule doesn't
match."
Inline :cue: rules
Patterns are written directly in source comments. Backslashes work as in
any regex literal — one level of escaping:
// :cue: /\b(TODO|FIXME)\b/ -> { "color": "#ff9944" }
Here \b is a word boundary — one backslash, as you'd write in a regex.
Workspace .source-cues.json
The config file is JSON. JSON strings use backslash escaping, so every
regex backslash must be escaped twice — once for JSON and once for the
regex engine:
{ "pattern": "\\b(TODO|FIXME)\\b", "options": "#warning" }
\\b in JSON becomes the string \b, which the regex engine sees as a word
boundary. A single \b in JSON is the backspace character (ASCII 0x08) —
the regex would look for a backspace, not a word boundary.
Quick reference
| Pattern |
Inline :cue: |
Workspace .source-cues.json |
| Word boundary |
/\bword\b/ |
"\\bword\\b" |
| Literal dot |
/\./ |
"\\." |
| Digit class |
/\d+/ |
"\\d+" |
| Capture group |
/TODO\((\w+)\)/ |
"TODO\\((\\w+)\\)" |
| Literal backslash |
/\\\\/ |
"\\\\\\\\" |
Tip: If a workspace rule doesn't match but the same pattern works inline,
check your JSON escaping. The "Source Cues" output channel logs the compiled
regex — compare it against what you intended.
🎨 Decoration Options Reference
The options field accepts any VS Code
DecorationRenderOptions.
Below are the most commonly used properties:
| Property |
Type |
Description |
color |
string |
Text foreground color (e.g. "#ff5555") |
backgroundColor |
string |
Text background color (e.g. "#cc0000") |
fontWeight |
string |
"bold", "normal", or numeric "100"–"900" |
fontStyle |
string |
"italic" or "normal" |
border |
string |
CSS border shorthand (e.g. "1px solid red") |
borderRadius |
string |
Corner rounding (e.g. "3px") |
textDecoration |
string |
"underline", "line-through", "none" |
outline |
string |
CSS outline shorthand |
opacity |
string |
Opacity value (e.g. "0.6") |
overviewRulerColor |
string |
Minimap scrollbar mark color |
overviewRulerLane |
number |
Scrollbar lane position (1–6) |
before |
object |
Virtual text injected before the match |
after |
object |
Virtual text/content injected after the match |
gutterIconPath |
string or {light, dark} |
Icon in the editor gutter |
dark |
object |
Theme-aware overrides for dark themes |
light |
object |
Theme-aware overrides for light themes |
Content injection (before / after)
The before and after properties accept an object that renders as virtual
text adjacent to the match:
{
"after": {
"contentText": " ⚠️ UNSAFE",
"color": "#ff5555",
"fontStyle": "italic",
"margin": "0 0 0 10px"
}
}
Gutter icons
Icons are resolved relative to your project, not the extension. Both forms are
supported:
// Single icon — workspace-root-relative (leading /) or file-relative
{ "gutterIconPath": "/demos/icons/warning.svg" }
// Theme-aware pair
{ "gutterIconPath": { "light": "/demos/icons/info-light.svg", "dark": "/demos/icons/info-dark.svg" } }
Extension-specific options
| Property |
Type |
Description |
captureGroup |
number |
0-based capture group index to highlight instead of the full match (default: 0). Stripped before passing to VS Code. |
Example: Only highlight the owner name inside TODO(maya), not the
parentheses or the TODO prefix:
// :cue: /TODO\((\w+)\)/ -> { "captureGroup": 1, "color": "#ff6600", "after": { "contentText": " ← owner", "color": "#888" } }
With captureGroup: 1, only maya is highlighted. With the default 0,
the entire TODO(maya) would be styled.