Skip to main content

ff-lntools

English | 한국어

ln-structure, a layout rule for Python projects built with AI coding agents (Claude Code and others), and lnt, the tool that checks the rule mechanically and generates the navigation docs.

Standard library only. Python 3.10+. Windows / macOS / Linux.

1. What ln-structure is

Modules are placed into layers named l0, l1, ... by dependency depth alone. Layer names carry no meaning. Where a module lives is decided only by what it imports.

src/shop/
  l0/                       # depends on nothing (standard library only)
    candle/
      candle.py             # class Candle
      __init__.py           # from .candle import Candle
  l1/                       # depends on l0 only
    order/
      order.py              # class Order  (from shop.l0.candle import Candle)
      __init__.py
  l2/                       # depends on l0, l1 only
    report/
      report.py
      __init__.py
  for-agent-layerinfo.md    # module map (generated)
tests/
  l1/order/test_order.py    # mirrors src

Four rules.

Code Rule
C1 Import only from lower layers. Same layer is forbidden too
C2 Import only the module surface (shop.l1.order). Never reach into its files or into a nested module's internals
C3 A module's layer = max(layer of its dependencies) + 1. No dependencies means l0. Any third-party package means at least l1
C4 No cycles between modules

Why.

  • Dependencies written by agents cannot tangle. Cycles are impossible by construction
  • The blast radius of a change is bounded to "layers above", and a tool can compute it exactly
  • Layers have no semantics, so there is no "is this a service or a util" debate. Add a dependency and the module moves up
  • Agents start from the module map (one line of responsibility per module), query signatures with lnt sig, and read the code for detail

Full rules (nested modules, mutual calls, dependency inversion, __init__ patterns): src/lntools/protocol/for-agent-codingprotocol-ln-structure.md (Korean).

2. What the tool does

Enforcement does not rely on people remembering rules. Wired into Claude Code hooks, every edit the agent makes is checked and violations come back as errors.

Command Role
lnt init Install protocol docs, CLAUDE.md and Claude Code hooks into a project
lnt check C1-C4. Exit 1 on violation
lnt blast MODULE Modules affected when this one changes, including consumers through inherited interfaces
lnt map Modules, layers, dependencies at a glance
lnt sig [TARGET] Public signatures of a layer or module, computed on the spot (not stored)
lnt review Things worth a look, not violations: bypassing dependencies, modules nothing uses, files with more than one class. Always exit 0
lnt doc Generate the module map (for-agent-layerinfo.md)
lnt doc --check Exit 1 if docs drifted from code
lnt move MODULE lK Relocate a module and rewrite imports, tests mirror, layer __init__, docs

3. Getting started

Install

pip install ff-lntools

Install into every Python environment (venv, conda env) a project uses; the hook calls python -m lntools.

Install the Claude Code skill (once)

lnt init --skill

Writes ~/.claude/skills/init-protocol/SKILL.md. Inside Claude Code, /init-protocol then does the same as lnt init below. Skip if you do not use Claude Code.

Set up a project

cd my-project
lnt init

Creates:

my-project/
  CLAUDE.md                                   # only if absent; points to the protocol doc
  .claude/
    for-agent-codingprotocol-ln-structure.md  # the rules; read when needed, not injected every session
    settings.json                             # Claude Code hooks; only "hooks" is merged if the file exists

Write code, check it

Create module folders under src/<package>/l0/, l1/, ... One module = one folder = one file (by default) = one class.

lnt check          # rules
lnt doc            # docs
lnt map            # overview

When lnt check reports C3 ("declared l2, computed l1"), run lnt move <module> l1.

4. How the Claude Code hooks work

lnt init registers two hooks in .claude/settings.json.

{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup|resume|clear|compact",
      "hooks": [{"type": "command", "command": "python -m lntools hook session-start"}]
    }],
    "PostToolUse": [{
      "matcher": "Edit|Write|MultiEdit",
      "hooks": [{"type": "command", "command": "python -m lntools hook post-edit", "timeout": 30}]
    }]
  }
}
  • SessionStart: injects for-agent-layerinfo.md (the module map) into the agent's context, so it starts oriented instead of grepping
  • PostToolUse: runs after every edit to src/**/*.py
    • Violations: exit 2 + stderr. The agent receives an error, with how to resolve each kind of violation, and cannot proceed until fixed
    • No violations: the blast radius, bypassing dependencies of the edited module, and module map drift (fix with lnt doc) are returned as context

Hooks are executed by Claude Code itself, not by the agent. The check happens even if the agent "forgets" the rules.

5. Docs

One stored doc: for-agent-layerinfo.md at the package root (and one inside each nested module). It lists modules per layer with one line of responsibility each. lnt doc keeps the list in sync between the <!-- lnt:generated:start --> and end markers; the responsibility lines are written by people and survive regeneration. Anything outside the markers is preserved as Notes.

Signatures are not stored. lnt sig computes them from the code on the spot.

$ lnt sig l1.order
## l1.order
Order.__init__(symbol: str, qty: float)  # order.py
Order.fill(qty: float) -> None  # order.py

There is no per-module doc; read the code. What code cannot tell (design reasons, contracts outside Python) goes briefly into the Notes area.

6. FAQ

A lower module needs a higher one. Move it up. Use dependency inversion only when moving cannot work (mutual calls, plugins, code outside the package). See the rules doc, section on calling upward.

Two classes call each other. If they are one unit of behavior, keep them as two files in one module folder. Cycles inside a module are out of scope.

Too many layers. Nest. l3/portfolio/ can contain its own l0/, l1/. From outside only the portfolio surface is visible.

Applying to an existing project. lnt init, then lnt check. Fix violations one at a time with lnt move. If hand-written layerinfo docs exist, lnt doc keeps the one-line descriptions and replaces the rest with the generated block (the old text is in git).

7. Development

git clone https://github.com/gatesplan/ff-lntools
cd ff-lntools
pip install -e .[dev]
python -m pytest -q
python -m lntools check       # this project is itself ln-structure and checks itself

Research protocols (experiment folders, paper notes) live in a separate repository: ff_coding_agent_protocol_md.

Metadata

Release files for ff-lntools 0.2.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 ff-lntools 0.2.0
File Size Uploaded Provenance Actions
ff_lntools-0.2.0.tar.gz 36.0 kB Without provenance Details for ff_lntools-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ff-lntools 0.2.0
File Interpreter ABI Platform Actions
ff_lntools-0.2.0-py3-none-any.whl Python 3 none any Details for ff_lntools-0.2.0-py3-none-any.whl

Total release size: 84.9 kB

Release files / ff_lntools-0.2.0.tar.gz

Download URL ff_lntools-0.2.0.tar.gz
Size 36.0 kB
Tags Source
SHA-256 checksum
How to use checksums
0407efdb7fa8c7d9387311868505c8928f612634304f83ba50871c577be61c77
BLAKE2b-256 checksum
How to use checksums
c661a6a9fae8b307aa0e0fef1be3735449a6266ed4a5926f1003c5363087e566
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ff_lntools-0.2.0-py3-none-any.whl

Download URL ff_lntools-0.2.0-py3-none-any.whl
Size 48.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3ec74e60f78c58d6bfe9c30a2b9504dcb5bdea16c231d82782159f58ce16f73a
BLAKE2b-256 checksum
How to use checksums
a3859033cc03457b22dffb435a7a55f50304e01a233ed0d2ad3502e024b6b061
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for ff-lntools 0.2.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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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