Adding a New Analyzer
All pipeline steps (extractors, preprocessors, analyzers) implement the same interface:
public interface IPipelineStep
{
string Name { get; }
PipelineContext Run(PipelineContext context);
}
The PipelineContext
public sealed class PipelineContext
{
public CodeFile FileA { get; }
public CodeFile FileB { get; }
public ILanguageProfile Language { get; }
// Extracted data (populated by extractors, transformed by preprocessors)
public Dictionary<string, List<string>> Metadata { get; }
// Results (populated by analyzers)
public List<SimilarityReason> Reasons { get; }
// Safe accessor
public List<string> GetMetadata(string key);
public void SetMetadata(string key, List<string> value);
}
Available Metadata Keys
After the default extractors run, these keys are available:
| Key | Content |
|---|---|
code.a, code.b |
Clean code lines (comments stripped) |
comment.a, comment.b |
Extracted comments |
functions.a, functions.b |
Function/method names |
imports.a, imports.b |
Import/include statements |
Example: Variable Count Analyzer
public sealed class VariableCountAnalyzer : IPipelineStep
{
public string Name => "VariableCount";
public PipelineContext Run(PipelineContext ctx)
{
var codeA = ctx.GetMetadata("code.a");
var codeB = ctx.GetMetadata("code.b");
// Count unique tokens (rough proxy for variable count)
var tokensA = codeA.SelectMany(line => line.Split(' ')).Distinct().Count();
var tokensB = codeB.SelectMany(line => line.Split(' ')).Distinct().Count();
if (tokensA == 0 && tokensB == 0) return ctx;
double ratio = 1.0 - (double)Math.Abs(tokensA - tokensB) / Math.Max(tokensA, tokensB);
if (ratio > 0.9)
{
ctx.Reasons.Add(new SimilarityReason(
Name,
$"Vocabulary size ratio: {ratio:P0} ({tokensA} vs {tokensB} unique tokens)",
ratio,
Weight: 0.2));
}
return ctx;
}
}
Adding to the Pipeline
var steps = AnalysisEngine.DefaultPipeline();
steps.Add(new VariableCountAnalyzer());
var engine = new AnalysisEngine(steps: steps);
Or insert at a specific position:
steps.Insert(steps.Count - 1, new VariableCountAnalyzer());
Weight Guidelines
| Weight | When to use |
|---|---|
| 1.0 | Primary signal (cosine similarity) |
| 0.8–0.9 | Strong complementary signal (winnowing) |
| 0.4–0.5 | Structural/semantic signal |
| 0.2–0.3 | Weak/supporting signal (file metrics) |
Higher weight means the analyzer has more influence on the final score. A weak signal with high weight causes false positives; a strong signal with low weight gets drowned out.