Code Annotations
What are annotations?
Section titled “What are annotations?”Annotations are structured comments in source code that create links to Contextia artifacts. When you write @spec SPEC-AUTH-001 in a code comment, you are declaring that this code implements (or is governed by) the authentication specification. This creates the bottom-up half of a bidirectional link between code and documentation.
# @spec SPEC-AUTH-001class AuthenticationHandler: """Handles OAuth 2.0 PKCE authentication flow."""
# @spec SPEC-AUTH-001.token-validation def validate_token(self, token: str) -> Claims: ...Annotations serve two purposes:
-
Navigation — An agent (or developer) looking at code can immediately find the governing specification. An agent looking at a specification can find all code that implements it.
-
Validation —
contextia checkverifies that annotations reference real artifacts and that specs’pathsfields match where annotations actually appear. Broken links are caught automatically.
Annotation types
Section titled “Annotation types”Contextia supports three annotation types, each linking code to a different kind of artifact:
| Annotation | Links to | Purpose |
|---|---|---|
@spec | system/specs/ | This code implements this specification |
@decision | system/rationale/ | This code is constrained by this decision |
@test | system/specs/ | This test verifies this specification |
@spec — implementation link
Section titled “@spec — implementation link”The most common annotation. Declares that a code element (function, class, module) implements or is governed by a specification.
// @spec SPEC-CACHE-001impl CacheManager { // @spec SPEC-CACHE-001.eviction fn evict_expired(&mut self) { // ... }
// @spec SPEC-CACHE-001.invalidation fn invalidate_key(&mut self, key: &str) { // ... }}@decision — constraint link
Section titled “@decision — constraint link”Declares that a code element is constrained by an architectural decision. This is useful for code where a non-obvious implementation choice was made, and the rationale is important for future maintainers.
// @decision DEC-DB-001 (PostgreSQL chosen over MongoDB)func NewDatabaseConnection(config *Config) (*sql.DB, error) { // Using pgx driver for PostgreSQL — see DEC-DB-001 return pgxpool.Connect(context.Background(), config.DSN)}@test — verification link
Section titled “@test — verification link”Declares that a test verifies a specification. This completes the traceability chain: spec defines the behavior, code implements it, tests verify it.
# @test SPEC-AUTH-001.token-validationclass TestTokenValidation: def test_rejects_expired_tokens(self): ...
def test_accepts_valid_tokens(self): ...Sub-IDs for granular linking
Section titled “Sub-IDs for granular linking”Annotations support dot-separated sub-IDs that link to specific sections within a specification. This allows fine-grained traceability without creating separate specs for every function.
@spec SPEC-AUTH-001 → links to entire spec@spec SPEC-AUTH-001.token-validation → links to token validation section@spec SPEC-AUTH-001.session-mgmt → links to session management sectionSub-IDs correspond to section anchors in the spec’s Markdown body. When an agent runs contextia find --spec SPEC-AUTH-001.token-validation, it can navigate directly to the relevant section.
Bidirectional links
Section titled “Bidirectional links”Annotations are one half of a bidirectional link. The other half lives in the spec’s frontmatter, in the paths field:
# In SPEC-AUTH-001.md frontmatterpaths: - src/auth/** - src/middleware/auth.py - tests/auth/**This creates two navigable directions:
| Direction | Mechanism | Example |
|---|---|---|
| Code to spec | @spec annotation in comment | ”This code implements SPEC-AUTH-001” |
| Spec to code | paths field in frontmatter | ”SPEC-AUTH-001 governs these files” |
Both directions are needed. Annotations let an agent looking at code find the relevant spec. The paths field lets an agent looking at a spec find the relevant code. Together, they create a complete traceability link.
Coherence validation
Section titled “Coherence validation”contextia check verifies that both directions agree:
$ contextia check
WARN src/auth/oauth.py has @spec SPEC-AUTH-001 but is not covered by SPEC-AUTH-001's paths fieldWARN SPEC-AUTH-001 lists path src/auth/legacy/** but no annotations found in matching filesOK 47 annotations checked — 2 warnings, 0 errorsA file with @spec SPEC-AUTH-001 that is not under any path listed in SPEC-AUTH-001’s frontmatter is a potential inconsistency. A path listed in a spec that contains no annotations may indicate dead documentation or missing annotations. Both are flagged as warnings.
Supported languages
Section titled “Supported languages”Contextia scans annotations in comments across 13 languages. The scanner uses each language’s comment syntax to identify annotation patterns:
| Language | Line comments | Block comments |
|---|---|---|
| Python | # | """...""" (docstrings) |
| JavaScript | // | /* ... */ |
| TypeScript | // | /* ... */ |
| Rust | // | /* ... */ |
| Go | // | /* ... */ |
| Java | // | /* ... */ |
| Kotlin | // | /* ... */ |
| Swift | // | /* ... */ |
| C | // | /* ... */ |
| C++ | // | /* ... */ |
| C# | // | /* ... */ |
| Ruby | # | =begin...=end |
| PHP | //, # | /* ... */ |
Comment syntax is configured in config.yaml and can be extended for additional languages:
languages: python: extensions: [".py"] comment_syntax: line: "#" rust: extensions: [".rs"] comment_syntax: line: "//" block: ["/*", "*/"] custom_lang: extensions: [".cst"] comment_syntax: line: "--"Tree-sitter integration
Section titled “Tree-sitter integration”By default, the annotation scanner uses regex to find annotations in comments. This works for identifying annotations but cannot determine the code context — which function or class the annotation appears in. Tree-sitter integration adds this capability.
What tree-sitter provides
Section titled “What tree-sitter provides”When tree-sitter parsing is enabled, the scanner parses the file’s AST (Abstract Syntax Tree) and resolves the enclosing code element for each annotation:
# Without tree-sitter:AnnotationResult( file="src/auth/handler.py", line=42, type="spec", id="SPEC-AUTH-001", sub_id="token-validation", context=None # Unknown)
# With tree-sitter:AnnotationResult( file="src/auth/handler.py", line=42, type="spec", id="SPEC-AUTH-001", sub_id="token-validation", context="AuthenticationHandler.validate_token" # Resolved)The context field tells the agent exactly which function implements the spec section. This is valuable when a single file contains multiple annotated elements — the agent knows which function to modify without scanning the entire file.
How it works
Section titled “How it works”Tree-sitter is a parser generator that produces fast, incremental parsers for programming languages. Contextia uses the py-tree-sitter bindings to parse source files and walk the syntax tree:
- Parse the source file into an AST
- Find all comment nodes in the AST
- Match annotations within comment text
- Walk up the AST from each comment to find the enclosing function, method, or class node
- Return the fully qualified name (e.g.,
ClassName.method_name)
Source file AST: module ├── class_definition: AuthenticationHandler │ ├── function_definition: validate_token │ │ ├── comment: "# @spec SPEC-AUTH-001.token-validation" ← found │ │ └── ... │ └── function_definition: refresh_token │ ├── comment: "# @spec SPEC-AUTH-001.token-refresh" ← found │ └── ... └── ...Parser modes
Section titled “Parser modes”The scanner supports three modes, configured per-project or per-language:
# In config.yamlscanner: parser: auto # auto | regex | tree-sitter| Mode | Behavior |
|---|---|
auto | Use tree-sitter if the grammar is installed, fall back to regex |
regex | Always use regex (no AST context, but no dependencies) |
tree-sitter | Require tree-sitter (error if grammar not installed) |
Installing grammars
Section titled “Installing grammars”Tree-sitter grammars are distributed as separate packages. Contextia does not bundle them — you install the ones you need:
# Install grammars for your languagespip install tree-sitter-python tree-sitter-javascript tree-sitter-rust
# Or with uvuv pip install tree-sitter-python tree-sitter-javascriptContextia automatically detects installed grammars and uses them when available (in auto mode).
Regex fallback
Section titled “Regex fallback”When tree-sitter is not available, the scanner uses regex patterns to find annotations. The regex approach is simpler but still effective:
# The core pattern (simplified)pattern = rf"{comment_prefix}\s*{annotation_prefix}(spec|decision|test)\s+([\w-]+(?:\.[\w-]+)?)"
# Matches:# // @spec SPEC-AUTH-001# # @spec SPEC-AUTH-001.token-validation# /* @decision DEC-DB-001 */The regex scanner also attempts best-effort context detection by looking for common patterns:
# Best-effort: look for class/function definition above the annotation# Python: def func_name( or class ClassName:# JS/TS: function funcName( or class ClassName {# Rust: fn func_name( or impl StructName {This heuristic works for most common code structures but cannot handle complex nesting, decorators that separate the definition from its body, or anonymous functions. Tree-sitter resolves all of these correctly.
Configuration
Section titled “Configuration”Annotation prefix
Section titled “Annotation prefix”The annotation prefix is configurable. The default is @, but you can change it to avoid conflicts with existing annotation systems (e.g., Java’s @Override, Python’s @decorator):
# In config.yamlannotations: prefix: "@" # Default: @spec, @decision, @test # prefix: "@ctx:" # Alternative: @ctx:spec, @ctx:decision, @ctx:testExcluding paths
Section titled “Excluding paths”Some paths should not be scanned — generated code, vendor directories, build artifacts:
# In config.yamlscanner: exclude: - "**/node_modules/**" - "**/vendor/**" - "**/*.generated.*" - "build/**" - "dist/**"Scan results
Section titled “Scan results”The scanner produces structured results that are cached in .contextia/.cache/:
{ "annotations": [ { "file": "src/auth/handler.py", "line": 42, "type": "spec", "id": "SPEC-AUTH-001", "sub_id": "token-validation", "context": "AuthenticationHandler.validate_token" } ], "scanned_files": 347, "annotations_found": 89, "timestamp": "2026-03-02T14:30:00Z"}The cache is rebuilt by contextia refresh and is always rebuildable from source. It is listed in .gitignore and should never be committed.
Practical guidance
Section titled “Practical guidance”Where to place annotations
Section titled “Where to place annotations”Place annotations on the most specific code element that implements the spec:
# Good: annotation on the function that implements the behavior# @spec SPEC-AUTH-001.token-validationdef validate_token(token: str) -> Claims: ...
# Less good: annotation on the module level when specific functions exist# @spec SPEC-AUTH-001# (entire file)Module-level annotations are appropriate when an entire file is dedicated to a single spec, but prefer function or class-level annotations when possible.
Annotation density
Section titled “Annotation density”Not every function needs an annotation. Annotate the public interface and key implementation points, not every helper function:
# @spec SPEC-AUTH-001.token-validationdef validate_token(token: str) -> Claims: """Public entry point — annotated.""" payload = _decode_jwt(token) # Helper — not annotated _check_expiry(payload) # Helper — not annotated return _extract_claims(payload) # Helper — not annotatedKeeping annotations in sync
Section titled “Keeping annotations in sync”Annotations can drift as code is refactored. contextia check catches the most common issues:
- Annotation references a spec that does not exist
- Annotation references a spec that is
deprecated - File with annotations is not covered by the spec’s
pathsfield - Spec’s
pathsfield matches files with no annotations
Run contextia check in CI to catch drift early:
# In your CI pipelinecontextia check --strict # Exit code 1 on any warning