Skip to content

Code 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-001
class 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:

  1. 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.

  2. Validationcontextia check verifies that annotations reference real artifacts and that specs’ paths fields match where annotations actually appear. Broken links are caught automatically.

Contextia supports three annotation types, each linking code to a different kind of artifact:

AnnotationLinks toPurpose
@specsystem/specs/This code implements this specification
@decisionsystem/rationale/This code is constrained by this decision
@testsystem/specs/This test verifies this specification

The most common annotation. Declares that a code element (function, class, module) implements or is governed by a specification.

// @spec SPEC-CACHE-001
impl CacheManager {
// @spec SPEC-CACHE-001.eviction
fn evict_expired(&mut self) {
// ...
}
// @spec SPEC-CACHE-001.invalidation
fn invalidate_key(&mut self, key: &str) {
// ...
}
}

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)
}

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-validation
class TestTokenValidation:
def test_rejects_expired_tokens(self):
...
def test_accepts_valid_tokens(self):
...

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 section

Sub-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.

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 frontmatter
paths:
- src/auth/**
- src/middleware/auth.py
- tests/auth/**

This creates two navigable directions:

DirectionMechanismExample
Code to spec@spec annotation in comment”This code implements SPEC-AUTH-001”
Spec to codepaths 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.

contextia check verifies that both directions agree:

Terminal window
$ contextia check
WARN src/auth/oauth.py has @spec SPEC-AUTH-001 but is not
covered by SPEC-AUTH-001's paths field
WARN SPEC-AUTH-001 lists path src/auth/legacy/** but no
annotations found in matching files
OK 47 annotations checked — 2 warnings, 0 errors

A 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.

Contextia scans annotations in comments across 13 languages. The scanner uses each language’s comment syntax to identify annotation patterns:

LanguageLine commentsBlock 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: "--"

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.

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.

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:

  1. Parse the source file into an AST
  2. Find all comment nodes in the AST
  3. Match annotations within comment text
  4. Walk up the AST from each comment to find the enclosing function, method, or class node
  5. 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
│ └── ...
└── ...

The scanner supports three modes, configured per-project or per-language:

# In config.yaml
scanner:
parser: auto # auto | regex | tree-sitter
ModeBehavior
autoUse tree-sitter if the grammar is installed, fall back to regex
regexAlways use regex (no AST context, but no dependencies)
tree-sitterRequire tree-sitter (error if grammar not installed)

Tree-sitter grammars are distributed as separate packages. Contextia does not bundle them — you install the ones you need:

Terminal window
# Install grammars for your languages
pip install tree-sitter-python tree-sitter-javascript tree-sitter-rust
# Or with uv
uv pip install tree-sitter-python tree-sitter-javascript

Contextia automatically detects installed grammars and uses them when available (in auto mode).

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.

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.yaml
annotations:
prefix: "@" # Default: @spec, @decision, @test
# prefix: "@ctx:" # Alternative: @ctx:spec, @ctx:decision, @ctx:test

Some paths should not be scanned — generated code, vendor directories, build artifacts:

# In config.yaml
scanner:
exclude:
- "**/node_modules/**"
- "**/vendor/**"
- "**/*.generated.*"
- "build/**"
- "dist/**"

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.

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-validation
def 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.

Not every function needs an annotation. Annotate the public interface and key implementation points, not every helper function:

# @spec SPEC-AUTH-001.token-validation
def 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 annotated

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 paths field
  • Spec’s paths field matches files with no annotations

Run contextia check in CI to catch drift early:

Terminal window
# In your CI pipeline
contextia check --strict # Exit code 1 on any warning