← All posts
#open source#developer tools#static analysis#go#tree-sitter

Finding Familiar Code with Mori

How Mori finds familiar code under different names, what structural similarity measures, and when a match is useful.

Two similar branching code structures illuminated among a dark forest of syntax trees.

Updated September 10, 2026 with worked examples and usage checked against Mori v0.32.0.

I use Mori on most of my larger projects. It helps with a question I find myself asking more often as a codebase grows: am I about to write something that already exists somewhere else?

Sometimes I remember the function name and a text search is enough. Sometimes the implementation lives in another module, uses different names, and was written long enough ago that I have forgotten about it. I might be adding another retry helper, validation routine or data converter without knowing there is already one worth reading.

Mori is a local command-line tool that looks for similar code structure. It gives me places to inspect before adding another implementation, or while reviewing one that has already been written. That is useful even when the entire project uses one language.

Different names, familiar structure

Here are two small TypeScript functions. The first collects email addresses from active customers:

type Customer = { active: boolean, email: string }

function activeCustomerEmails(customers: Customer[]) {
  const emails: string[] = []
  for (const customer of customers) {
    if (customer.active) {
      emails.push(customer.email)
    }
  }
  return emails
}

The second collects names from available products:

type Product = { available: boolean, name: string }

function availableProductNames(products: Product[]) {
  const names: string[] = []
  for (const product of products) {
    if (product.available) {
      names.push(product.name)
    }
  }
  return names
}

The names and purposes differ, but the structure is the same. Both create a collection, loop through input, check a condition, add a property to the collection and return it.

Save both examples in collections.ts and run:

mori scan --profile review --min-tokens 12 collections.ts

With Mori v0.32.0, this produces one match at 100% structural similarity. The report records 108 shared weighted feature units out of 108 combined units. The token minimum is deliberately lower here because these are small teaching examples. The normal review profile skips functions below 40 normalized tokens to reduce noise.

That result does not mean customer emails and product names are interchangeable. It means the features Mori compares are identical. I would probably keep these two functions separate unless the surrounding code gave me a good reason to share an abstraction.

This example shows what Mori detects. The useful decision comes from reading the match in context.

How the comparison works

Mori uses Tree-sitter to parse source into a syntax tree. That gives it more information than a list of words. It can distinguish a condition inside a loop from a condition elsewhere in a function, for example.

It then translates supported syntax into a shared structural vocabulary. Formatting, comments, identifier spelling and literal values do not drive the structural score. Calls, branches, loops, returns, expression relationships and selected ordering information do. Nested functions are separate comparison units.

For each function, Mori builds a collection of those features and their counts. Counts matter because four branches are different from one branch. It also has a small set of explicit operation hints for familiar patterns such as trimming, membership checks, filtering and mapping. Those hints are based on recognised syntax and names, not knowledge of what an arbitrary library or user-defined method really does.

The score measures how much of the two feature collections overlaps, using weighted Jaccard similarity. Shared weighted counts form the numerator, and combined weighted counts form the denominator. In the example above, that is 108 divided by 108.

The report includes shared features and structural differences so there is something to inspect behind the percentage. The scoring documentation explains the precise rules and limitations.

When a match earns its place

The situations where I find this useful are fairly ordinary:

  • Before adding code. A new helper resembles an existing implementation in another module. I can read that code and decide whether to reuse it or keep the new version separate.
  • During review. A new function resembles something that was copied and then changed. Comparing the two can reveal a difference worth discussing.
  • After a bug fix. Similar implementations give me other places to check for the same mistake. Mori does not establish that the bug exists there.
  • While learning a project. Related functions help me find existing patterns without already knowing the right names to search for.

The benefit is a smaller, more relevant set of places to read. Repetition alone is not a defect, and an abstraction is not automatically an improvement.

What a high score does not tell me

Consider two functions that return value < 10 and value < 1000. They have the same structure but give different answers for many inputs. Literal values do not change Mori’s structural score, although separate literal-difference evidence can flag that their values differ without including those values in the report.

The opposite can happen too. Rewriting a loop as a short filter().map() chain changes its syntax substantially. Mori has some operation hints, but it cannot reliably recognise every different implementation of the same behaviour.

A score of 80% means 80% overlap under the chosen structural representation. It does not mean an 80% probability that the functions behave identically. Even 100% cannot establish equivalence, account for side effects, resolve runtime types or prove that two functions should be merged.

Someone asked how Mori compares with a similar tool using a vector database. The first distinction is that the database stores and searches representations. It does not, by itself, determine how code is represented.

An embedding-based tool typically uses a model to turn code into numerical representations, then searches for nearby representations. Depending on the model and setup, that can support natural-language questions or find related code with different implementations. Qdrant’s code-search example illustrates both natural-language and code-to-code search using embeddings.

Mori builds explicit structural features directly from parsed code. It needs no embedding model or database, and it can explain a score through the features being compared. With the same inputs, version and options, its analysis is deterministic.

That is the approach I chose for a small local review tool. It does not establish that Mori is faster or more accurate than an embedding-based alternative. Vector-based tools can also run locally, and the approaches could complement each other. Their usefulness depends on what you need to find.

Using it in a real project

For a first look at a repository:

mori scan --profile review .

I check the coverage and warnings before interpreting the matches. Generated files, framework scaffolding, tiny callbacks and deliberately repetitive code can dominate a report. Unsupported files and files without comparison fragments also matter. A quiet report is not proof that every part of the project was examined.

I record deliberate scope choices in .mori.json or .moriignore. I do not exclude product code simply because it produces an inconvenient result.

For work on a branch, I can focus the review on changes relative to a local base reference:

mori scan \
  --changed-since origin/main \
  --profile review \
  .

This assumes origin/main exists locally. It still compares against the rest of the selected repository, including untouched files. That matters because the existing implementation I want to discover is often outside the diff.

For an agent-assisted review, I can retain a bounded JSON report and get a shorter reading guide:

MORI_REPORT="$(mktemp)"
mori scan \
  --changed-since origin/main \
  --profile review \
  --format agent \
  --output "$MORI_REPORT" \
  .

The temporary file holds the report so I can return to the evidence without repeating the scan. The agent summary provides a shortlist, but any unreviewed remainder stays unreviewed. Reports can contain paths and function names, so I treat them as private project information.

Mori also supports immutable staged review for commit workflows. Existing gates remain strict by default. Advisory policy and authenticated analysis caching are explicit choices, described in the review-policy guide and cache guide.

Once an intentional match has been reviewed, a scoped baseline can record that decision. It records accepted similarity, not equivalent behaviour. I keep those decisions separate from whether a scan had sufficient coverage or encountered parser warnings.

Across languages, and entirely local

The same structural vocabulary also allows comparisons across supported languages. For example, a JavaScript function using trim() and includes() can resemble a Go function using TrimSpace() and Contains(). This is an additional way to find familiar patterns, especially in projects that have grown across several languages.

Language support has expanded since I first wrote this post. Run mori languages for the installed version’s supported languages and comparison units, or see the project documentation. SQL queries have their own comparison domain rather than being mixed into function results.

Mori reads source locally and does not execute it. Scanning does not upload source or make network requests. Optional local feedback is disabled by default and requires explicit consent for a project. It records coarse usage information, has no automatic submission, and is separate from the analysis report. The feedback guide explains what is retained and how to disable or clear it.

Mori is open source under the MIT licence. Verified release archives for macOS, Linux and Windows are available from the releases page. The README also covers installation from source.

The reason I keep reaching for it is still modest. When a project has become too large to remember where everything lives, Mori gives me a better place to begin reading. What the similarity means, and what to do about it, still needs judgement.

Finding Familiar Code with Mori

How Mori finds familiar code under different names, what structural similarity measures, and when a match is useful.

Two similar branching code structures illuminated among a dark forest of syntax trees.

Updated September 10, 2026 with worked examples and usage checked against Mori v0.32.0.

I use Mori on most of my larger projects. It helps with a question I find myself asking more often as a codebase grows: am I about to write something that already exists somewhere else?

Sometimes I remember the function name and a text search is enough. Sometimes the implementation lives in another module, uses different names, and was written long enough ago that I have forgotten about it. I might be adding another retry helper, validation routine or data converter without knowing there is already one worth reading.

Mori is a local command-line tool that looks for similar code structure. It gives me places to inspect before adding another implementation, or while reviewing one that has already been written. That is useful even when the entire project uses one language.

Different names, familiar structure

Here are two small TypeScript functions. The first collects email addresses from active customers:

type Customer = { active: boolean, email: string }

function activeCustomerEmails(customers: Customer[]) {
  const emails: string[] = []
  for (const customer of customers) {
    if (customer.active) {
      emails.push(customer.email)
    }
  }
  return emails
}

The second collects names from available products:

type Product = { available: boolean, name: string }

function availableProductNames(products: Product[]) {
  const names: string[] = []
  for (const product of products) {
    if (product.available) {
      names.push(product.name)
    }
  }
  return names
}

The names and purposes differ, but the structure is the same. Both create a collection, loop through input, check a condition, add a property to the collection and return it.

Save both examples in collections.ts and run:

mori scan --profile review --min-tokens 12 collections.ts

With Mori v0.32.0, this produces one match at 100% structural similarity. The report records 108 shared weighted feature units out of 108 combined units. The token minimum is deliberately lower here because these are small teaching examples. The normal review profile skips functions below 40 normalized tokens to reduce noise.

That result does not mean customer emails and product names are interchangeable. It means the features Mori compares are identical. I would probably keep these two functions separate unless the surrounding code gave me a good reason to share an abstraction.

This example shows what Mori detects. The useful decision comes from reading the match in context.

How the comparison works

Mori uses Tree-sitter to parse source into a syntax tree. That gives it more information than a list of words. It can distinguish a condition inside a loop from a condition elsewhere in a function, for example.

It then translates supported syntax into a shared structural vocabulary. Formatting, comments, identifier spelling and literal values do not drive the structural score. Calls, branches, loops, returns, expression relationships and selected ordering information do. Nested functions are separate comparison units.

For each function, Mori builds a collection of those features and their counts. Counts matter because four branches are different from one branch. It also has a small set of explicit operation hints for familiar patterns such as trimming, membership checks, filtering and mapping. Those hints are based on recognised syntax and names, not knowledge of what an arbitrary library or user-defined method really does.

The score measures how much of the two feature collections overlaps, using weighted Jaccard similarity. Shared weighted counts form the numerator, and combined weighted counts form the denominator. In the example above, that is 108 divided by 108.

The report includes shared features and structural differences so there is something to inspect behind the percentage. The scoring documentation explains the precise rules and limitations.

When a match earns its place

The situations where I find this useful are fairly ordinary:

  • Before adding code. A new helper resembles an existing implementation in another module. I can read that code and decide whether to reuse it or keep the new version separate.
  • During review. A new function resembles something that was copied and then changed. Comparing the two can reveal a difference worth discussing.
  • After a bug fix. Similar implementations give me other places to check for the same mistake. Mori does not establish that the bug exists there.
  • While learning a project. Related functions help me find existing patterns without already knowing the right names to search for.

The benefit is a smaller, more relevant set of places to read. Repetition alone is not a defect, and an abstraction is not automatically an improvement.

What a high score does not tell me

Consider two functions that return value < 10 and value < 1000. They have the same structure but give different answers for many inputs. Literal values do not change Mori’s structural score, although separate literal-difference evidence can flag that their values differ without including those values in the report.

The opposite can happen too. Rewriting a loop as a short filter().map() chain changes its syntax substantially. Mori has some operation hints, but it cannot reliably recognise every different implementation of the same behaviour.

A score of 80% means 80% overlap under the chosen structural representation. It does not mean an 80% probability that the functions behave identically. Even 100% cannot establish equivalence, account for side effects, resolve runtime types or prove that two functions should be merged.

Someone asked how Mori compares with a similar tool using a vector database. The first distinction is that the database stores and searches representations. It does not, by itself, determine how code is represented.

An embedding-based tool typically uses a model to turn code into numerical representations, then searches for nearby representations. Depending on the model and setup, that can support natural-language questions or find related code with different implementations. Qdrant’s code-search example illustrates both natural-language and code-to-code search using embeddings.

Mori builds explicit structural features directly from parsed code. It needs no embedding model or database, and it can explain a score through the features being compared. With the same inputs, version and options, its analysis is deterministic.

That is the approach I chose for a small local review tool. It does not establish that Mori is faster or more accurate than an embedding-based alternative. Vector-based tools can also run locally, and the approaches could complement each other. Their usefulness depends on what you need to find.

Using it in a real project

For a first look at a repository:

mori scan --profile review .

I check the coverage and warnings before interpreting the matches. Generated files, framework scaffolding, tiny callbacks and deliberately repetitive code can dominate a report. Unsupported files and files without comparison fragments also matter. A quiet report is not proof that every part of the project was examined.

I record deliberate scope choices in .mori.json or .moriignore. I do not exclude product code simply because it produces an inconvenient result.

For work on a branch, I can focus the review on changes relative to a local base reference:

mori scan \
  --changed-since origin/main \
  --profile review \
  .

This assumes origin/main exists locally. It still compares against the rest of the selected repository, including untouched files. That matters because the existing implementation I want to discover is often outside the diff.

For an agent-assisted review, I can retain a bounded JSON report and get a shorter reading guide:

MORI_REPORT="$(mktemp)"
mori scan \
  --changed-since origin/main \
  --profile review \
  --format agent \
  --output "$MORI_REPORT" \
  .

The temporary file holds the report so I can return to the evidence without repeating the scan. The agent summary provides a shortlist, but any unreviewed remainder stays unreviewed. Reports can contain paths and function names, so I treat them as private project information.

Mori also supports immutable staged review for commit workflows. Existing gates remain strict by default. Advisory policy and authenticated analysis caching are explicit choices, described in the review-policy guide and cache guide.

Once an intentional match has been reviewed, a scoped baseline can record that decision. It records accepted similarity, not equivalent behaviour. I keep those decisions separate from whether a scan had sufficient coverage or encountered parser warnings.

Across languages, and entirely local

The same structural vocabulary also allows comparisons across supported languages. For example, a JavaScript function using trim() and includes() can resemble a Go function using TrimSpace() and Contains(). This is an additional way to find familiar patterns, especially in projects that have grown across several languages.

Language support has expanded since I first wrote this post. Run mori languages for the installed version’s supported languages and comparison units, or see the project documentation. SQL queries have their own comparison domain rather than being mixed into function results.

Mori reads source locally and does not execute it. Scanning does not upload source or make network requests. Optional local feedback is disabled by default and requires explicit consent for a project. It records coarse usage information, has no automatic submission, and is separate from the analysis report. The feedback guide explains what is retained and how to disable or clear it.

Mori is open source under the MIT licence. Verified release archives for macOS, Linux and Windows are available from the releases page. The README also covers installation from source.

The reason I keep reaching for it is still modest. When a project has become too large to remember where everything lives, Mori gives me a better place to begin reading. What the similarity means, and what to do about it, still needs judgement.