Skip to main content

Fensu

Keeping Python, TypeScript, JavaScript, Svelte, and Rust repos from turning into spaghetti.

Fensu means "fence" in Japanese.

Most linters catch bad code inside files. Fensu catches architectural drift: code crossing the wrong boundary, living in the wrong module, or growing into the wrong shape.

As a repository grows, code moves, lessons get forgotten, and the mental map decays. Tests preserve behavior and types preserve interfaces. Fensu makes the repository's architectural expectations executable.

Fensu enforces:

  • which layers may import which;
  • what each module or role file may contain;
  • whether orchestrator functions stay small;
  • whether dataflow and mutation are explicit;
  • whether names such as validate_* mean what they claim.

It ships a coherent default architecture rather than a blank rule framework, then lets projects disable, extend, or replace parts deliberately.

Fensu is functional and self-hosting, but remains pre-release.

Installation

pip install fensu

The authoring/API distribution is fensu. It installs the lockstep fensu-cli binary package, which exclusively owns the fensu command. Core-only check, dupes, init, rule, map, skills, and --version execution is native. Configured Python custom rules launch one compatible Python host only for the policy metadata or callbacks they require.

Built-in commands are available only through the native fensu executable; python -m fensu is retired. Installing fensu-cli from its source distribution requires Rust 1.95 or newer.

Fensu requires Python 3.12+ and includes a compiled analysis core. Prebuilt wheels cover Linux (x86_64, aarch64), macOS (Intel, Apple silicon), and Windows (x86_64). On other platforms, pip builds from source and requires a Rust toolchain at version 1.95 or newer.

Quick Start

From the directory containing an existing repository's pyproject.toml, detect the layout, write a validated configuration with the full ruleset enabled, run an initial check, and install repository-local agent guidance:

fensu init --yes

For an empty repository, provide the package name to scaffold src/ and tests/:

fensu init --yes --name my_package

Pass --no-skills only when automation explicitly requires configuration without repository-local guidance. Do not call onboarding complete until this succeeds:

fensu skills --check

Then run:

fensu check

To configure manually instead, add a minimal fensu.toml at the repository root:

roots = ["src/my_package"]

tests defaults to ["tests"], tooling is optional, caching is enabled by default, and every rule family is on by default. Install and verify agent guidance after writing configuration manually:

fensu skills
fensu skills --check
fensu check

Product roots and tooling receive structural rules; tests receive test-convention and annotation rules.

Rust workspaces

Rust rules run directly in the fensu-rust analyzer through the normal Fensu CLI. Configure a Cargo workspace in fensu.toml:

[targets.rust]
analyzer = "rust"
roots = ["crates"]
tests = []
tooling = []
rule_packs = ["rust"]
select = ["FPRS"]

Run fensu check --target rust. Cargo discovers workspace members, package names, and targets; use roots = ["src"] for a single-package repository. The Rust pack enables all 118 FPRS* rules, covering layout, imports, dependency boundaries, naming, function shape, hygiene, and test conventions. Existing structural thresholds are the defaults.

Adjust individual rules in the same file:

[targets.rust.rule_options.FPRSS010]
max_arguments = 8

Selection, warnings, ignores, file exceptions, and caching use the usual Fensu settings. Cargo manifests, the lockfile when present, Rust sources, and target configuration participate in cache identity. Analysis does not create or update a lockfile. See Rust analyzer configuration for rule options and tooling boundaries.

Python projects can opt into production dead-code enforcement with [dead_code] enabled = true. Reachability faults cover module-level definitions and modules; reasoned roots describe dynamic entry mechanisms. Tests and main/ placement are not roots.

Default Structure

The default architecture follows SQLBuild Compiler Rules v0.101.1.

Product code uses domain, optional subdomain, then role. Every leaf domain or subdomain owns meaningful behavior through a direct main/ containing at least one entry module. Branch-domain parents do not need their own main/; their leaf subdomains do. Tests mirror the code they cover; tooling uses one ownership level because scripts/ already establishes the outer boundary.

src/my_package/
└── domain/
    └── subdomain/
        ├── main/
        │   └── run.py
        ├── _helpers/
        ├── classes/
        ├── models.py
        ├── types.py
        ├── constants.py
        └── exceptions.py
tests/unit/src/my_package/domain/subdomain/
├── _test_types.py
└── test_run.py
scripts/
├── run_tool.py
└── tool_name/
    ├── main/
    ├── _helpers/
    └── classes/

Do not create an empty or initializer-only main/ to satisfy the layout. If a package contains only passive models, types, constants, exceptions, or classes, move those declarations into the closest domain or subdomain whose main/ behavior owns and uses them. Fixed role files are siblings of _helpers/, never descendants such as _helpers/entry/models.py.

Direct scripts/*.py files are thin command adapters. Supporting logic belongs under scripts/<tool>/<role>/.

Targets may declare path-scoped ownership roots when domains begin at different physical depths. The first package below each matched ownership root is a domain; the next non-role package is its optional subdomain. Without this setting, each Python source root, Cargo source root, or web lib/ directory is the ownership root:

[targets.app]
analyzer = "python"
roots = ["src/example"]
ownership_roots = [
  "src/example/sources/*",
  "src/example/runtime",
]
src/example/
├── sources/
│   ├── region_a/
│   │   ├── orders/main/process.py
│   │   └── partners/importing/main/load.py
│   └── region_b/
│       └── inventory/main/refresh.py
└── runtime/
    ├── scraping/main/collect.py
    └── observability/main/report.py

Here each directory matched by sources/* and the explicit runtime/ directory establishes an ownership root. orders, partners, inventory, scraping, and observability are domains; importing is a subdomain. The most-specific matching ownership root wins. Structural directories above and including an ownership root cannot contain role files or role directories directly. Mirrored tests inherit the production ownership root they cover. Rust, TypeScript, and Svelte use the same path-scoped model. Declarations must resolve beneath a configured runtime source, select analyzable files, and own at least one effective path after more-specific matches are applied.

Core Commands

fensu init
fensu check
fensu rule FFS131
fensu map run_plan --depth 3
fensu dupes --since origin/main

fensu init detects and validates an onboarding configuration, fensu check enforces the configured architecture, fensu rule explains one rule and its remediation, fensu dupes reports duplicated code as advisory review input, and fensu map renders a conservative project call tree. Downstream mapping is the default; --direction upstream shows proven callers. Mapping follows project functions and class methods when imports, annotations, constructors, or return types prove the receiver. Unique protocol dispatch can resolve to one concrete implementation; ambiguous protocols and untyped parameters remain visible as unresolved dispatch seams rather than guessed implementations. Mapping does not require Fensu configuration or rule adoption.

fensu check stores disposable evaluation results in a repository-local SQLite database under .fensu/cache/ and reuses them only after validating source, configuration, rule, implementation, and project-query inputs. Caching is enabled by default; set cache.enabled = false in configuration or pass --no-cache for an explicit uncached check. --cache overrides a disabled project preference for one invocation. Deleting .fensu/cache/ is always safe; ignore that directory rather than the complete .fensu/ namespace, which is reserved for other Fensu-owned state.

Duplicated Code

fensu dupes reports concrete duplicated code across the configured targets: ranked clusters of exact, renamed, and near-miss function-level copies in Python, Rust, TypeScript, JavaScript, and Svelte. It is advisory. It exits 0 whenever analysis succeeds, whatever it finds, and exits 2 only for usage, configuration, or IO errors.

$ fensu dupes --since origin/main --diff
fensu dupes: 1 duplicated-code cluster changed since origin/main (advisory; duplicated-code findings to review, not fensu check failures)
analysed python 212 units; 0 allowlisted pairs hidden; 0 contract-exempt members hidden
  1. near-miss sim 0.98, ~111 duplicated tokens, 2 members
     src/shop/orders/summary.py:1-15 summarize_orders (113 tokens)
     src/shop/reports/summary.py:1-16 summarize_orders (118 tokens) [changed]
     diff src/shop/orders/summary.py:1-15 vs src/shop/reports/summary.py:1-16
       + 9: log_progress(count)

See duplicated-code detection for options, [dupes] configuration, and contract exemptions.

Enforce It, Then See It

Because Fensu enforces the structure, it can also render it. fensu map produces a deterministic call tree with clickable path:line locations, class-qualified method names, and explicit protocol seams while marking unresolved dynamic calls, depth limits, and cycles. Downstream walks follow proven callees; upstream walks invert those proven edges and omit callers Fensu cannot resolve.

$ fensu map run_map --depth 4

run_map(...)  src/fensu/cli/main/map.py:21
├── _parser(...)  src/fensu/cli/main/map.py:53
├── resolve_mapping_project(...)  src/fensu/mapping/main/resolve_project.py:11
│   └── resolve_mapping_project(...)  src/fensu/mapping/_helpers/project.py:15
│       ├── _find_project_root(...)  src/fensu/mapping/_helpers/project.py:73
│       ├── _explicit_source(...)  src/fensu/mapping/_helpers/project.py:65
│       ├── _optional_config_source(...)  src/fensu/mapping/_helpers/project.py:38
│       │   └── find_config_source(...)  src/fensu/config/main/find_config.py:12  (depth limit)
│       └── _configured_project(...)  src/fensu/mapping/_helpers/project.py:45
│           ├── load_config(...)  src/fensu/config/main/load_config.py:15  (depth limit)
│           └── _configured_source(...)  src/fensu/mapping/_helpers/project.py:57
└── build_call_map(...)  src/fensu/mapping/main/build.py:12
    ├── provider(...)  src/fensu/mapping/main/build.py:24  (unresolved parameter call)
    └── render_tree(...)  src/fensu/mapping/_helpers/render.py:19
        ├── _child_lines(...)  src/fensu/mapping/_helpers/render.py:41
        │   └── _child_lines(...)  src/fensu/mapping/_helpers/render.py:41  (cycle)
        └── _label(...)  src/fensu/mapping/_helpers/render.py:88

The map is useful precisely because it is not guessing. fensu check enforces layers, roles, and public surfaces first, and fensu map then renders the structure the code is required to expose.

Philosophy

Fensu is strict by default wherever it can make an honest deterministic claim. Following the rules should remove repeated architectural decisions from everyday work. Deliberate differences belong in selection, configuration, or custom rules, where they remain visible, rather than in scattered inline suppressions.

Unavoidable external calling conventions can use exact symbol-scoped exceptions:

[[rule_exceptions]]
rule = "FFS120"
path = "src/my_package/integrations/_helpers/callbacks.py"
symbols = ["ProgressCollector.update"]
reason = "The external API invokes this callback positionally."

Python and web exceptions accept one exact rule code, repository-relative source file, and one or more qualified symbols. Rust exceptions are file-level and may target an exact .rs or Cargo.toml path. Globs, directories, line numbers, and inline suppression comments are not supported. fensu check rejects stale exceptions that no longer suppress a fault.

For a justified rule/path intersection that is broader than one exact finding, keep the rules and project context active with [[rule_ignores]]:

[[rule_ignores]]
rules = ["FFA", "XGENERATED"]
paths = ["src/my_package/generated/**"]
reason = "Generated interfaces are checked by their source schema."

One declaration must match both the finding's rule code and its reported path. Unlike exact exceptions, path-scoped ignores are not stale-checked.

Agent Skills

Generate repository-aware guidance from the active ruleset:

fensu skills
fensu skills --global

The generated skill includes Fensu usage, rule-supported architecture examples, navigation and work-handoff guidance, and every enabled core and custom rule. Existing user-authored skill files are preserved unless --force is supplied.

Custom Rules

Custom checks use X... codes and the same RuleContext as core rules. Once configured, they participate in fensu check, fensu rule, and generated agent skills. Rules can declare typed RuleOption values, repositories can override them under [rule_options.<CODE>], and checks read the validated current value through ctx.option(). Rules can also use ctx.facts, ctx.project, ctx.text, ctx.syntax, and ctx.relations; these are the same backend-neutral analysis zones used by Fensu's built-in rules. Project and filesystem reads made through ctx.project are tracked for cache invalidation. Raw ast.Module access remains available for checks that need unrestricted Python syntax traversal. Semantic fact contracts and author-facing models are public Python APIs, while their production extraction has one native Rust owner; raw AST, syntax, and relation artifacts remain lazy CPython capabilities. Keep project-owned checks in the canonical scripts/fensu_policy/rules/ tooling role and load them explicitly:

tooling = ["scripts"]
rule_paths = ["scripts/fensu_policy/rules"]

See the custom-rule guide for the complete API and configuration.

Documentation

The quickstart, architecture model, configuration reference, adoption guide, and CLI reference live at docs.fensu.dev.

Metadata

Release files for fensu 0.25.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fensu 0.25.0
File Size Uploaded Provenance
fensu-0.25.0.tar.gz 538.4 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for fensu 0.25.0
File
fensu-0.25.0-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details
fensu-0.25.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 abi3 Linux glibc 2.17+ x86-64 Details
fensu-0.25.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 abi3 Linux glibc 2.17+ ARM64 Details
fensu-0.25.0-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details
fensu-0.25.0-cp312-abi3-macosx_10_12_x86_64.whl CPython 3.12 abi3 macOS 10.12+ x86-64 Details

Total release size: 21.2 MB

Release files / fensu-0.25.0.tar.gz

Download URL fensu-0.25.0.tar.gz
Size 538.4 kB
Tags Source
SHA-256 checksum
How to use checksums
563c61f32e9840c44f6450d51955ca96314760edb93c65d0efd9fd93cd29e483
BLAKE2b-256 checksum
How to use checksums
b6da01e62ab809b674ca62e7f821b71a8e3c330c96ede9e100c52d09b205e660
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / fensu-0.25.0-cp312-abi3-win_amd64.whl

Download URL fensu-0.25.0-cp312-abi3-win_amd64.whl
Size 4.0 MB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
929925e21d8360cf2d75f391f9b90cd2e57745b1d094cbdfdf93ac58679e332e
BLAKE2b-256 checksum
How to use checksums
86313910634ff704b294335cf07509ca82c8ce2dc280c2c5a62732f4e800b106
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / fensu-0.25.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL fensu-0.25.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 4.3 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
6397d41faddeb77fa335484c863e57ee055768169e06da3c47c3e82b994ac145
BLAKE2b-256 checksum
How to use checksums
807816294da4ef422e48f7d9a374e0a880f3d18d92f48e40794a830ec747fbdb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / fensu-0.25.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL fensu-0.25.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 4.2 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
f79c677e474c51b9462ab6e968c2daf3ada42ee9135ef52c63df7828118629f6
BLAKE2b-256 checksum
How to use checksums
a60b84c1243dbe42aaf98c2117f4570ab1f670e73f573dd73bc00c89a1e801d1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / fensu-0.25.0-cp312-abi3-macosx_11_0_arm64.whl

Download URL fensu-0.25.0-cp312-abi3-macosx_11_0_arm64.whl
Size 4.0 MB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
0a2da7f96d997afafdd794f01637c0d99fef35d6c31a6b4d3ac2f602db32f283
BLAKE2b-256 checksum
How to use checksums
6b6e9bc736822504da9e210da86982a308dd9e76d0853c34e44a8e4a0c8c1881
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / fensu-0.25.0-cp312-abi3-macosx_10_12_x86_64.whl

Download URL fensu-0.25.0-cp312-abi3-macosx_10_12_x86_64.whl
Size 4.2 MB
Tags CPython 3.12 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
96a62e94012b85e4a63caf266c1f4dfb11f9d28842237624a03d54f08800ff66
BLAKE2b-256 checksum
How to use checksums
5bb5b378b2acd7b3873eaf1da36aff0635b802fbedf0d398dc8cede3ed1e3480
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Security for fensu 0.25.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for fensu-0.25.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.25.0 This release

6 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page