analyzer

package
v0.0.0-...-0ff359d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2025 License: MIT Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultParallelismLevel is the default number of concurrent embedding operations
	DefaultParallelismLevel = 5

	// ParallelismEnvVar is the environment variable to set parallelism level
	ParallelismEnvVar = "EMBEDDINGS_PARALLELISM_LEVEL"
)
View Source
const (
	// Core node types (95% of usage)
	NodeTypePackage   = "package"   // Package/module level
	NodeTypeStruct    = "struct"    // Struct types
	NodeTypeInterface = "interface" // Interface types
	NodeTypeFunction  = "function"  // Standalone functions
	NodeTypeMethod    = "method"    // Type methods

	// Secondary node types (keep for completeness)
	NodeTypeField     = "field"     // Struct fields
	NodeTypeParameter = "parameter" // Function parameters
	NodeTypeConstant  = "constant"  // Package constants
	NodeTypeVariable  = "variable"  // Package variables

)

Node types - Simplified to essential entities

View Source
const (
	// Essential edges (90% of queries)
	EdgeTypeCalls      = "calls"      // Function/method calls: "What calls this?", "What does this call?"
	EdgeTypeImplements = "implements" // Interface implementation: "What implements this interface?"
	EdgeTypeUses       = "uses"       // Type usage/dependency: "Where is this type used?"
	EdgeTypeImports    = "imports"    // Package dependencies: "What does this package depend on?"
	EdgeTypeHasMethod  = "has_method" // Method ownership: "What methods does this type have?"

	// Common analysis queries (70% of use cases)
	EdgeTypeReturns    = "returns"    // Return types: "What functions return this type?"
	EdgeTypeEmbeds     = "embeds"     // Composition: "What does this struct embed?"
	EdgeTypeConstructs = "constructs" // Factory patterns: "How is this type created?"

	// Specialized but important (30% of use cases)
	EdgeTypeHandlesError    = "handles_error"    // Error handling: "Does this handle errors?"
	EdgeTypeSpawnsGoroutine = "spawns_goroutine" // Concurrency: "What spawns goroutines?"
	EdgeTypeHasParameter    = "has_parameter"    // Parameters: "What functions accept this type?"

	// Legacy edge types - kept for backward compatibility but deprecated
	// TODO: Remove these in next major version
	EdgeTypeHasField      = "has_field"      // Use has_method pattern instead
	EdgeTypeMethodOf      = "method_of"      // Redundant with has_method
	EdgeTypeParameterType = "parameter_type" // Use has_parameter
	EdgeTypeWrapsError    = "wraps_error"    // Merged into handles_error

)

Edge types - Simplified to essential relationships for 80/20 efficiency

View Source
const (
	VisibilityPublic  = "public"
	VisibilityPrivate = "private"
)

Visibility constants

View Source
const (
	KindStruct    = "struct"
	KindInterface = "interface"
)

Kind constants for TypeInfo

Variables

This section is empty.

Functions

func ExtractConstValue

func ExtractConstValue(expr ast.Expr) string

ExtractConstValue extracts the value from a constant declaration

func ExtractDocumentation

func ExtractDocumentation(cg *ast.CommentGroup) string

ExtractDocumentation extracts documentation comments from an AST node

func GenerateNodeID

func GenerateNodeID(nodeType, pkg, name string) string

GenerateNodeID creates a unique node ID GenerateNodeID creates a consistent node ID using NodeIdentifier

func GetReceiverType

func GetReceiverType(recv *ast.FieldList) string

GetReceiverType extracts the receiver type name from a method

func PrepareNodeForStorage

func PrepareNodeForStorage(node *Node)

PrepareNodeForStorage adds hash metadata to a node (V1 compatibility)

Types

type ASTVisitor

type ASTVisitor struct {
	// contains filtered or unexported fields
}

ASTVisitor traverses the AST and builds nodes/relationships

func NewASTVisitor

func NewASTVisitor(pkg *packages.Package, nodeBuilder *NodeBuilder, relAnalyzer *RelationshipAnalyzer) *ASTVisitor

NewASTVisitor creates a new AST visitor

func (*ASTVisitor) BuildNodes

func (v *ASTVisitor) BuildNodes()

BuildNodes builds all nodes for the package

type AnalysisOptions

type AnalysisOptions struct {
	IncludeConcurrency bool     `json:"include_concurrency"`
	IncludeErrorFlow   bool     `json:"include_error_flow"`
	IncludePatterns    bool     `json:"include_patterns"`
	IncludeGenerics    bool     `json:"include_generics"`
	MaxDepth           int      `json:"max_depth"`
	ExcludePaths       []string `json:"exclude_paths,omitempty"`
	IncludeTests       bool     `json:"include_tests"`
}

AnalysisOptions represents analysis configuration

type AnalysisPerf

type AnalysisPerf struct {
	Duration      time.Duration `json:"duration"`
	FilesAnalyzed int           `json:"files_analyzed"`
	LinesOfCode   int           `json:"lines_of_code"`
	MemoryUsed    int64         `json:"memory_used"`
}

AnalysisPerf contains performance metrics

type AnalysisWarning

type AnalysisWarning struct {
	Type     string   `json:"type"`
	Message  string   `json:"message"`
	Position Position `json:"position"`
	Severity string   `json:"severity"`
}

AnalysisWarning represents analysis warnings

type ChannelOp

type ChannelOp struct {
	Position    Position `json:"position"`
	Operation   string   `json:"operation"` // send, receive, close, make
	ChannelType string   `json:"channel_type"`
	Direction   string   `json:"direction,omitempty"`
}

ChannelOp represents channel operations

type ConcurrencyInfo

type ConcurrencyInfo struct {
	GoroutineSpawns  []GoroutineSpawn  `json:"goroutine_spawns,omitempty"`
	ChannelOps       []ChannelOp       `json:"channel_ops,omitempty"`
	SelectStatements []SelectStatement `json:"select_statements,omitempty"`
	Mutexes          []MutexUsage      `json:"mutexes,omitempty"`
}

ConcurrencyInfo tracks concurrency patterns

type CouplingMetrics

type CouplingMetrics struct {
	AfferentCoupling map[string]int     `json:"afferent_coupling"` // Ca - incoming dependencies
	EfferentCoupling map[string]int     `json:"efferent_coupling"` // Ce - outgoing dependencies
	Instability      map[string]float64 `json:"instability"`       // I = Ce / (Ca + Ce)
	Abstractness     map[string]float64 `json:"abstractness"`      // A = Abstract classes / Total classes
}

CouplingMetrics represents coupling analysis

type Edge

type Edge = EnhancedEdge

Legacy support for existing code

type EmbeddingsGenerator

type EmbeddingsGenerator struct {
	// contains filtered or unexported fields
}

EmbeddingsGenerator handles embedding generation for nodes

func NewEmbeddingsGenerator

func NewEmbeddingsGenerator(client embeddings.Client, logger *slog.Logger) *EmbeddingsGenerator

NewEmbeddingsGenerator creates a new embeddings generator

func NewEmbeddingsGeneratorWithConfig

func NewEmbeddingsGeneratorWithConfig(client embeddings.Client, parallelismLevel int, logger *slog.Logger) *EmbeddingsGenerator

NewEmbeddingsGeneratorWithConfig creates a new embeddings generator with explicit parallelism level

func (*EmbeddingsGenerator) CreateEmbedding

func (e *EmbeddingsGenerator) CreateEmbedding(text string) ([]float32, error)

CreateEmbedding generates an embedding for the given text

func (*EmbeddingsGenerator) GenerateEmbeddingsForNodes

func (e *EmbeddingsGenerator) GenerateEmbeddingsForNodes(nodes []*Node) error

GenerateEmbeddingsForNodes generates embeddings for specific nodes concurrently

func (*EmbeddingsGenerator) GetClient

func (e *EmbeddingsGenerator) GetClient() embeddings.Client

GetClient returns the underlying embeddings client

func (*EmbeddingsGenerator) GetDimensions

func (e *EmbeddingsGenerator) GetDimensions() int

GetDimensions returns the embedding dimensions

func (*EmbeddingsGenerator) GetModelName

func (e *EmbeddingsGenerator) GetModelName() string

GetModelName returns the embedding model name if available

func (*EmbeddingsGenerator) GetNodeMetadata

func (e *EmbeddingsGenerator) GetNodeMetadata(node *Node) map[string]interface{}

GetNodeMetadata returns the metadata needed for incremental updates

func (*EmbeddingsGenerator) IsEnabled

func (e *EmbeddingsGenerator) IsEnabled() bool

IsEnabled returns true if embeddings generation is enabled

func (*EmbeddingsGenerator) NodeNeedsEmbedding

func (e *EmbeddingsGenerator) NodeNeedsEmbedding(node *Node, existingMetadata map[string]interface{}) bool

NodeNeedsEmbedding checks if a node needs embedding generation or update based on comparing existing and new metadata

func (*EmbeddingsGenerator) PrepareNodeForEmbedding

func (e *EmbeddingsGenerator) PrepareNodeForEmbedding(node *Node)

PrepareNodeForEmbedding prepares a node with semantic summary for potential embedding generation

func (*EmbeddingsGenerator) SetGraph

func (e *EmbeddingsGenerator) SetGraph(graph *Graph)

SetGraph sets the graph for relationship-aware embeddings

type EnhancedEdge

type EnhancedEdge struct {
	ID          string                 `json:"id"`
	Source      string                 `json:"source"`
	Target      string                 `json:"target"`
	Type        string                 `json:"type"`
	Weight      int                    `json:"weight"`
	Position    Position               `json:"position"`
	Context     string                 `json:"context,omitempty"`
	Conditional bool                   `json:"conditional,omitempty"` // if in conditional block
	LoopDepth   int                    `json:"loop_depth,omitempty"`
	Metadata    map[string]interface{} `json:"metadata"`
	CreatedAt   time.Time              `json:"created_at"`
}

EnhancedEdge represents a relationship with rich metadata

type EnhancedNode

type EnhancedNode struct {
	ID              string                 `json:"id"`
	Label           string                 `json:"label"`
	Type            string                 `json:"type"`
	Package         string                 `json:"package"`
	FullName        string                 `json:"full_name"`
	Size            int                    `json:"size"`
	Level           int                    `json:"level"`
	Position        Position               `json:"position"`
	Signature       string                 `json:"signature,omitempty"`
	TypeInfo        TypeInfo               `json:"type_info,omitempty"`
	Visibility      string                 `json:"visibility"`
	Documentation   string                 `json:"documentation,omitempty"`
	Complexity      int                    `json:"complexity,omitempty"`
	Metadata        map[string]interface{} `json:"metadata"`
	Tags            []string               `json:"tags,omitempty"`
	Embedding       []float32              `json:"embedding,omitempty"`
	EmbeddingModel  string                 `json:"embedding_model,omitempty"`
	SemanticSummary string                 `json:"semantic_summary,omitempty"`
	CreatedAt       time.Time              `json:"created_at"`
}

EnhancedNode represents a comprehensive code entity

type ErrorHandling

type ErrorHandling struct {
	Position    Position `json:"position"`
	Pattern     string   `json:"pattern"` // check, wrap, ignore, propagate
	ErrorVar    string   `json:"error_var"`
	Wrapping    bool     `json:"wrapping"`
	WrapMessage string   `json:"wrap_message,omitempty"`
}

ErrorHandling represents error handling patterns

type ErrorInfo

type ErrorInfo struct {
	ErrorTypes    []ErrorType     `json:"error_types,omitempty"`
	ErrorHandling []ErrorHandling `json:"error_handling,omitempty"`
	PanicRecovery []PanicRecovery `json:"panic_recovery,omitempty"`
}

ErrorInfo tracks error handling patterns

type ErrorType

type ErrorType struct {
	Name     string   `json:"name"`
	Package  string   `json:"package"`
	Position Position `json:"position"`
	Methods  []string `json:"methods"`
}

ErrorType represents custom error types

type GoroutineSpawn

type GoroutineSpawn struct {
	Position   Position `json:"position"`
	TargetFunc string   `json:"target_func"`
	Context    string   `json:"context"`
}

GoroutineSpawn represents a goroutine creation

type Graph

type Graph struct {
	Nodes       []EnhancedNode  `json:"nodes"`
	Edges       []EnhancedEdge  `json:"edges"`
	Stats       GraphStats      `json:"stats"`
	Metadata    GraphMetadata   `json:"metadata"`
	Concurrency ConcurrencyInfo `json:"concurrency,omitempty"`
	ErrorInfo   ErrorInfo       `json:"error_info,omitempty"`
	CreatedAt   time.Time       `json:"created_at"`
	Version     string          `json:"version"`
}

Graph represents the complete enhanced code graph

func NewGraph

func NewGraph() *Graph

NewGraph creates a new enhanced graph

func (*Graph) AddEdge

func (g *Graph) AddEdge(edge EnhancedEdge)

AddEdge adds an enhanced edge to the graph

func (*Graph) AddNode

func (g *Graph) AddNode(node EnhancedNode)

AddNode adds an enhanced node to the graph

func (*Graph) GetNodeByID

func (g *Graph) GetNodeByID(id string) *EnhancedNode

GetNodeByID finds a node by its ID

type GraphMetadata

type GraphMetadata struct {
	SourcePath      string            `json:"source_path"`
	GoVersion       string            `json:"go_version"`
	ModulePath      string            `json:"module_path"`
	BuildTags       []string          `json:"build_tags,omitempty"`
	AnalysisOptions AnalysisOptions   `json:"analysis_options"`
	Performance     AnalysisPerf      `json:"performance"`
	Warnings        []AnalysisWarning `json:"warnings,omitempty"`
}

GraphMetadata contains graph-level metadata

type GraphStats

type GraphStats struct {
	TotalNodes            int              `json:"total_nodes"`
	TotalEdges            int              `json:"total_edges"`
	NodesByType           map[string]int   `json:"nodes_by_type"`
	EdgesByType           map[string]int   `json:"edges_by_type"`
	PackageCount          int              `json:"package_count"`
	MaxDepth              int              `json:"max_depth"`
	Dependencies          []string         `json:"dependencies"`
	CyclomaticComplexity  int              `json:"cyclomatic_complexity"`
	CouplingMetrics       CouplingMetrics  `json:"coupling_metrics"`
	InterfaceCompliance   []InterfaceMatch `json:"interface_compliance,omitempty"`
	ArchitecturalPatterns []Pattern        `json:"architectural_patterns,omitempty"`
}

GraphStats contains comprehensive statistics

type InterfaceMatch

type InterfaceMatch struct {
	Interface       string   `json:"interface"`
	Implementations []string `json:"implementations"`
	PartialMatches  []string `json:"partial_matches,omitempty"`
}

InterfaceMatch represents interface satisfaction

type MethodSignature

type MethodSignature struct {
	Name       string   `json:"name"`
	Params     []string `json:"params"`
	Returns    []string `json:"returns"`
	IsExported bool     `json:"is_exported"`
}

MethodSignature represents a method signature

type MutexUsage

type MutexUsage struct {
	Position  Position `json:"position"`
	Type      string   `json:"type"`      // Mutex, RWMutex
	Operation string   `json:"operation"` // Lock, Unlock, RLock, RUnlock
}

MutexUsage represents mutex usage

type Node

type Node = EnhancedNode

Legacy support for existing code

type NodeBuilder

type NodeBuilder struct {
	// contains filtered or unexported fields
}

NodeBuilder handles creation of all node types

func NewNodeBuilder

func NewNodeBuilder(graph *Graph) *NodeBuilder

NewNodeBuilder creates a new node builder

func (*NodeBuilder) CreateBuiltinTypeNode

func (nb *NodeBuilder) CreateBuiltinTypeNode(typeName string, isInterface bool) *Node

CreateBuiltinTypeNode creates a node for builtin types like string, int, error, etc.

func (*NodeBuilder) CreateFieldNode

func (nb *NodeBuilder) CreateFieldNode(structNode *Node, field *types.Var, tag string, position Position) *Node

CreateFieldNode creates a field node for a struct

func (*NodeBuilder) CreateFunctionNode

func (nb *NodeBuilder) CreateFunctionNode(pkg *packages.Package, fn *ast.FuncDecl) *Node

CreateFunctionNode creates a function or method node

func (*NodeBuilder) CreateGenericNode

func (nb *NodeBuilder) CreateGenericNode(pkg *packages.Package, funcNode *Node, name string) *Node

CreateGenericNode - generic type parameter support removed

func (*NodeBuilder) CreatePackageNode

func (nb *NodeBuilder) CreatePackageNode(pkg *packages.Package) *Node

CreatePackageNode creates a package node

func (*NodeBuilder) CreateParameterNode

func (nb *NodeBuilder) CreateParameterNode(funcNode *Node, paramName string, paramType string,
	index int, position Position,
) *Node

CreateParameterNode creates a parameter node for a function

func (*NodeBuilder) CreateTypeNode

func (nb *NodeBuilder) CreateTypeNode(pkg *packages.Package, typeName *types.TypeName, typeSpec *ast.TypeSpec) *Node

CreateTypeNode creates a struct or interface node

func (*NodeBuilder) EnsureBuiltinTypes

func (nb *NodeBuilder) EnsureBuiltinTypes()

EnsureBuiltinTypes creates nodes for common builtin types

func (*NodeBuilder) NodeExists

func (nb *NodeBuilder) NodeExists(nodeID string) bool

NodeExists checks if a node already exists

type NodeIdentifier

type NodeIdentifier struct {
	Type    string
	Package string
	Name    string
}

NodeIdentifier provides standardized node ID generation

func (NodeIdentifier) ID

func (n NodeIdentifier) ID() string

ID generates a consistent node ID

func (NodeIdentifier) String

func (n NodeIdentifier) String() string

String returns a human-readable representation

type NodeRelationships

type NodeRelationships struct {
	Calls            []string // Functions this node calls
	CalledBy         []string // Functions that call this node
	Uses             []string // Types this node uses
	UsedBy           []string // Where this node is used
	Implements       []string // Interfaces implemented
	Returns          []string // Types returned
	Imports          []string // Packages imported
	HandlesError     bool     // Whether this node handles errors
	SpawnsGoroutines bool     // Whether this spawns goroutines
}

NodeRelationships holds the relationships for a node

type PanicRecovery

type PanicRecovery struct {
	Position Position `json:"position"`
	Type     string   `json:"type"` // panic, recover
	InDefer  bool     `json:"in_defer"`
	Message  string   `json:"message,omitempty"`
}

PanicRecovery represents panic/recover usage

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser orchestrates the v2 code analysis

func NewParser

func NewParser(embeddingsGenerator *EmbeddingsGenerator, logger *slog.Logger) *Parser

NewParser is the actual V2 parser constructor We rename the original NewParser to avoid conflicts

func (*Parser) AnalyzeCodebase

func (p *Parser) AnalyzeCodebase(rootPath string) (*Graph, error)

AnalyzeCodebase analyzes a Go codebase and builds a graph

func (*Parser) GenerateEmbeddingsForNodes

func (p *Parser) GenerateEmbeddingsForNodes(nodes []*EnhancedNode) error

GenerateEmbeddingsForNodes generates embeddings for specific nodes

func (*Parser) GetGraph

func (p *Parser) GetGraph() *Graph

GetGraph returns the current graph

func (*Parser) SetAllowedPackages

func (p *Parser) SetAllowedPackages(packages []string)

SetAllowedPackages sets the list of external packages to include in analysis

type ParserInterface

type ParserInterface interface {
	// AnalyzeCodebase analyzes a Go codebase and builds a graph
	AnalyzeCodebase(rootPath string) (*Graph, error)

	// GenerateEmbeddingsForNodes generates embeddings for specific nodes
	GenerateEmbeddingsForNodes(nodes []*EnhancedNode) error

	// GetGraph returns the current graph
	GetGraph() *Graph
}

ParserInterface defines the contract for all parser implementations

type Pattern

type Pattern struct {
	Name        string                 `json:"name"`
	Type        string                 `json:"type"`       // singleton, factory, observer, etc.
	Confidence  float64                `json:"confidence"` // 0.0 - 1.0
	Components  []string               `json:"components"` // node IDs involved
	Description string                 `json:"description"`
	Position    Position               `json:"position"`
	Metadata    map[string]interface{} `json:"metadata"`
}

Pattern represents detected architectural patterns

type Position

type Position struct {
	Filename string `json:"filename"`
	Line     int    `json:"line"`
	Column   int    `json:"column"`
	Offset   int    `json:"offset"`
}

Position represents source code location

func PositionFromToken

func PositionFromToken(fset *token.FileSet, pos token.Pos) Position

PositionFromToken converts token.Position to our Position

type RelationshipAnalyzer

type RelationshipAnalyzer struct {
	// contains filtered or unexported fields
}

RelationshipAnalyzer handles all relationship analysis for the code graph

func NewRelationshipAnalyzer

func NewRelationshipAnalyzer(graph *Graph, nodeBuilder *NodeBuilder) *RelationshipAnalyzer

NewRelationshipAnalyzer creates a new relationship analyzer

func (*RelationshipAnalyzer) AddPackage

func (r *RelationshipAnalyzer) AddPackage(pkg *packages.Package)

AddPackage adds a package for relationship analysis

func (*RelationshipAnalyzer) AnalyzeAllRelationships

func (r *RelationshipAnalyzer) AnalyzeAllRelationships()

AnalyzeAllRelationships analyzes all relationships in the registered packages

func (*RelationshipAnalyzer) AnalyzeConcurrencyPatterns

func (r *RelationshipAnalyzer) AnalyzeConcurrencyPatterns(pkg *packages.Package, fn *ast.FuncDecl)

AnalyzeConcurrencyPatterns analyzes concurrency patterns in a function (public method for AST visitor)

func (*RelationshipAnalyzer) AnalyzeFunctionRelationships

func (r *RelationshipAnalyzer) AnalyzeFunctionRelationships(pkg *packages.Package, fn *ast.FuncDecl, funcNode *Node)

AnalyzeFunctionRelationships analyzes relationships for a function declaration

func (*RelationshipAnalyzer) AnalyzeTypeRelationships

func (r *RelationshipAnalyzer) AnalyzeTypeRelationships(pkg *packages.Package, typeName *types.TypeName)

AnalyzeTypeRelationships analyzes relationships for a specific type (public method for AST visitor)

func (*RelationshipAnalyzer) AnalyzeTypeUsage

func (r *RelationshipAnalyzer) AnalyzeTypeUsage(pkg *packages.Package, sourceID string, typ types.Type, edgeType string)

AnalyzeTypeUsage analyzes type usage relationships

func (*RelationshipAnalyzer) CreateImportEdges

func (r *RelationshipAnalyzer) CreateImportEdges(pkg *packages.Package)

CreateImportEdges creates import relationship edges for a package

func (*RelationshipAnalyzer) SetNodeIndex

func (r *RelationshipAnalyzer) SetNodeIndex(index map[string]bool)

SetNodeIndex sets the node index for checking node existence

type SelectCase

type SelectCase struct {
	Position  Position `json:"position"`
	Operation string   `json:"operation"` // send, receive
	Channel   string   `json:"channel"`
}

SelectCase represents a case in select statement

type SelectStatement

type SelectStatement struct {
	Position   Position     `json:"position"`
	Cases      []SelectCase `json:"cases"`
	HasDefault bool         `json:"has_default"`
}

SelectStatement represents select statement

type Stats

type Stats = GraphStats

Legacy support for existing code

type TypeInfo

type TypeInfo struct {
	Kind           string            `json:"kind"` // struct, interface, func, etc.
	IsPointer      bool              `json:"is_pointer"`
	IsSlice        bool              `json:"is_slice"`
	IsArray        bool              `json:"is_array"`
	IsChannel      bool              `json:"is_channel"`
	ChannelDir     string            `json:"channel_dir,omitempty"` // send, recv, both
	IsGeneric      bool              `json:"is_generic"`
	TypeParams     []TypeParam       `json:"type_params,omitempty"`
	Constraints    []string          `json:"constraints,omitempty"`
	UnderlyingType string            `json:"underlying_type,omitempty"`
	MethodSet      []MethodSignature `json:"method_set,omitempty"`
	FieldCount     int               `json:"field_count,omitempty"`
	IsExported     bool              `json:"is_exported"`
	IsAlias        bool              `json:"is_alias"`
	EmbeddedTypes  []string          `json:"embedded_types,omitempty"`
}

TypeInfo contains Go type system information

func TypeInfoFromGoTypes

func TypeInfoFromGoTypes(t types.Type) TypeInfo

TypeInfoFromGoTypes extracts type information from go/types

type TypeParam

type TypeParam struct {
	Name       string `json:"name"`
	Constraint string `json:"constraint"`
}

TypeParam represents a generic type parameter

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL