Fuse
Internals & ExtendingExtending

Adding a Language Plugin

How to build an assembly that registers reduction and analysis capabilities for a set of file extensions.

Language support has two independent extension seams. ILanguageSyntaxProvider adds files to the persistent search index. Reduction capabilities control how selected files are compacted and presented. Neither seam adds compiler-backed semantics; that tier remains C#-only.

This page is written for an engineer who wants to teach Fuse about a language it does not yet handle.

Language-provider policy (frozen). First-party semantic support for another language requires its real compiler service, for example the TypeScript language service. Community syntax providers are welcome. The existing Python and JavaScript syntax providers are maintained but not extended.

Persistent Syntax Index

Implement ILanguageSyntaxProvider when files must participate in fuse_find, task localization, and context seeding. A provider declares a language id and extensions, then returns symbols and searchable chunks without invoking a compiler.

public sealed class MyLangSyntaxProvider : ILanguageSyntaxProvider
{
    public string Language => "mylang";
    public IReadOnlyCollection<string> Extensions { get; } = [".mylang"];

    public SyntaxExtractionResult Extract(string normalizedPath, string content)
    {
        // Return bounded syntax-tier symbols and chunks.
        return SyntaxExtractionResult.Empty;
    }
}

Every indexed file receives the provider's language tag. Symbols and chunks inherit language through their file record. Keep extraction bounded and deterministic. A syntax provider does not create typed call, DI, request-handler, route, options, or test edges.

The current built-in syntax providers are C# (.cs), Python (.py), and JavaScript (.js, .jsx, .mjs, .cjs, .ts, .tsx, .mts, .cts). C# uses Roslyn syntax. Python and JavaScript use bounded lexical extraction.

Reduction Capability Context

Every capability a language plugin provides extends the same base contract, ILanguageCapability, which declares a single member, SupportedExtensions. That property returns the extensions the capability handles, each with its leading dot, for example .cs. The core pipeline holds a registry per capability type and builds an extension-to-implementation map from everything registered. At resolution time it looks up the extension and calls the matching implementation. Matching is case-insensitive, and one class may declare several extensions when a language uses more than one.

A language can supply any subset of the reduction capabilities below. IContentReducer is required only for extension-specific reduction. It is not required for syntax indexing.

InterfaceWhat it providesRequired
IContentReducerExtension-specific content reductionYes
ISkeletonExtractorSignatures-only structural skeletonNo
ISemanticMarkerGeneratorType-level annotation commentsNo
IDependencyExtractorBest-effort references for the legacy fusion graphNo
ITypeNameLocatorResolution of a type name to its defining fileNo
IRouteMapGeneratorEndpoint table prepended to outputNo
IProjectGraphGeneratorProject reference graph prepended to outputNo
IPatternDetectorCross-codebase convention detectionNo

The capability model is described in Capability and Plugin Model. C# behavior is split between the text reducer and Roslyn structural implementations; no single plugin type defines every index tier.

Implement the Content Reducer

A content reducer applies extension-specific transformations to a file's text and returns the reduced result. The pipeline normalizes whitespace before calling a reducer, so a reducer should not trim lines or collapse blank lines itself; it removes only what is specific to the language, such as comments. A reducer must be stateless and pure, because reduction runs concurrently across files on several threads.

The following is a minimal reducer for a hypothetical language whose files end in .mylang.

using Fuse.Plugins.Abstractions.Options;
using Fuse.Plugins.Abstractions.Reducers;

namespace Fuse.Plugins.Languages.MyLang.Reducers;

/// <summary>
///     Reduces MyLang source files.
/// </summary>
public sealed class MyLangReducer : IContentReducer
{
    /// <inheritdoc />
    public IReadOnlyCollection<string> SupportedExtensions { get; } = [".mylang"];

    /// <inheritdoc />
    public string Reduce(string content, ReductionOptions options)
    {
        // Apply only language-specific transformations here.
        // Whitespace normalization already ran in the pipeline.
        return content;
    }
}

The options parameter carries the reduction settings for the current run, such as whether comments are removed. Read the flags that apply to your language and return the input unchanged when none of them is set.

Add Optional Capabilities

Implement the further capabilities your language warrants. Each follows the same shape: implement the interface, declare SupportedExtensions, and keep the type stateless unless its contract says otherwise. A dependency extractor, for example, reports the type or module names a file references so that focus, query, and change scoping can expand through the graph.

using Fuse.Plugins.Abstractions.Dependencies;

namespace Fuse.Plugins.Languages.MyLang.Dependencies;

/// <summary>
///     Extracts referenced module names from MyLang source.
/// </summary>
public sealed class MyLangDependencyExtractor : IDependencyExtractor
{
    /// <inheritdoc />
    public IReadOnlyCollection<string> SupportedExtensions { get; } = [".mylang"];

    /// <inheritdoc />
    public IReadOnlyList<string> ExtractDependencies(string content, string filePath)
    {
        // Return referenced type or module names. Best-effort.
        return [];
    }
}

Dependency extraction here is text-based and best-effort. It does not populate the compiler-backed typed graph.

Registration

A plugin exposes its capabilities through a dependency injection extension method that registers each one as a singleton. The C# plugin uses AddCSharpLanguage; follow the same naming for your language.

using Fuse.Plugins.Abstractions.Dependencies;
using Fuse.Plugins.Abstractions.Reducers;
using Fuse.Plugins.Languages.MyLang.Dependencies;
using Fuse.Plugins.Languages.MyLang.Reducers;
using Microsoft.Extensions.DependencyInjection;

namespace Fuse.Plugins.Languages.MyLang.Extensions;

/// <summary>
///     Registers MyLang language capabilities with dependency injection.
/// </summary>
public static class MyLangLanguageServiceCollectionExtensions
{
    /// <summary>
    ///     Registers every MyLang capability as a singleton.
    /// </summary>
    public static IServiceCollection AddMyLangLanguage(this IServiceCollection services)
    {
        services.AddSingleton<IContentReducer, MyLangReducer>();
        services.AddSingleton<IDependencyExtractor, MyLangDependencyExtractor>();
        return services;
    }
}

Call the extension method where the host composes reduction services, alongside AddCSharpLanguage and AddFormatReducers. The registry uses last-registration-wins semantics for a contested extension. Register the syntax provider separately with the persistent index composition.

What This Does Not Cover

This page covers the capability contracts and registration. It does not cover the internals of any single capability, such as how the C# reducer compresses syntax or how the dependency graph expands a seed set; those belong to the reference pages. It also does not cover non-language format reducers, which are addressed in Adding a Format Reducer, or project templates, in Adding a Template.

Next

Read the Capability and Plugin Model for the design behind the registries, then the Templates reference if your language also needs its own discovery defaults. The contributing guide covers coding standards and the pull request checklist.

On this page