Skip to main content

suoyin

suoyin generates a compact Markdown manifest of Python modules, classes, members, and functions.

It is designed for fast codebase inspection and for feeding a high-signal summary into LLM workflows.

suoyin is the Chinese pinyin for index.

Repository: https://github.com/alexdong/suoyin

Features

  • Scans project directories, file lists, or glob selections
  • Respects .gitignore and .ignore
  • Skips test files and conftest.py
  • Renders compact function signatures such as def func(a: int) -> str
  • Includes the first docstring paragraph for modules, classes, functions, and methods
  • Expands classes to show both members and methods

Usage

The block below is generated from the current repository by python tools/update_readme.py.

You can scan a directory or pass Python files and glob patterns such as uv run suoyin '*.py' and uv run suoyin 'src/**/*.py'.

$ uvx suoyin
# Manifest

## src/suoyin/__init__.py
  functions:
    - def _git_version() -> str @L8
    - def _installed_version() -> str @L50

## src/suoyin/__main__.py

## src/suoyin/cli.py
  Generate a compact Python symbol manifest with docstring summaries.
  classes:
    - class FunctionSymbol @L46
      members:
        - signature: str @L47
        - line_no: int @L48
        - summary: str @L49
    - class MemberSymbol @L53
      members:
        - name: str @L54
        - signature: str @L55
        - line_no: int @L56
    - class ClassSymbol @L60
      members:
        - name: str @L61
        - line_no: int @L62
        - members: list[MemberSymbol] @L63
        - functions: list[FunctionSymbol] @L64
        - summary: str @L65
    - class ModuleSymbol @L69
      members:
        - module: str @L70
        - path: str @L71
        - classes: list[ClassSymbol] @L72
        - functions: list[FunctionSymbol] @L73
        - summary: str @L74
    - class ManifestVisitor @L199
      members:
        - classes: list[ClassSymbol] @L201
        - functions: list[FunctionSymbol] @L202
        - _class_names: list[str] @L203
        - _class_stack: list[ClassSymbol] @L204
      functions:
        - def __init__(self) -> None @L200
        - def visit(self, node: ast.AST) -> None @L206
        - def visit_class_def(self, node: ast.ClassDef) -> None @L221
        - def visit_function_def(self, node: ast.FunctionDef) -> None @L231
        - def visit_async_function_def(self, node: ast.AsyncFunctionDef) -> None @L234
        - def _handle_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef, *, is_async: bool) -> None @L237
        - def _visit_class_body(self, node: ast.ClassDef, class_symbol: ClassSymbol) -> None @L250
  functions:
    - def docstring_summary(node: ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef) -> str @L77: Return the first docstring paragraph with whitespace collapsed to spaces.
    - def load_ignore_patterns(root: Path) -> list[str] @L86
    - def is_ignored(path: Path, patterns: list[str]) -> bool @L101
    - def is_test_path(path: Path) -> bool @L110
    - def format_node(node: ast.AST) -> str @L121
    - def format_function(node: ast.FunctionDef | ast.AsyncFunctionDef, *, is_async: bool) -> str @L128
    - def format_member(name: str, annotation: ast.expr | None) -> str @L137
    - def attribute_name(target: ast.expr) -> str @L143
    - def assigned_names(target: ast.expr) -> list[str] @L153
    - def remember_member(members: dict[str, MemberSymbol], name: str, annotation: ast.expr | None, line_no: int) -> None @L166
    - def merge_member(members: dict[str, MemberSymbol], member: MemberSymbol) -> None @L180
    - def method_members(node: ast.FunctionDef | ast.AsyncFunctionDef) -> list[MemberSymbol] @L190
    - def remember_method_member(members: dict[str, MemberSymbol], statement: ast.stmt | ast.expr) -> None @L261
    - def remember_assignment_members(members: dict[str, MemberSymbol], statement: ast.Assign) -> None @L281
    - def remember_attribute_member(members: dict[str, MemberSymbol], target: ast.expr, *, line_no: int, annotation: ast.expr | None=None) -> None @L288
    - def remember_class_member(members: dict[str, MemberSymbol], statement: ast.stmt) -> None @L300
    - def class_members(node: ast.ClassDef) -> list[MemberSymbol] @L316
    - def find_python_files(root: Path, ignores: list[str]) -> Iterator[Path] @L326
    - def module_name(root: Path, path: Path) -> str @L336
    - def parse_file(root: Path, path: Path) -> ModuleSymbol | None @L346
    - def render(modules: list[ModuleSymbol]) -> str @L367
    - def render_module(module: ModuleSymbol) -> list[str] @L375
    - def render_class(class_symbol: ClassSymbol) -> list[str] @L397
    - def create_parser() -> argparse.ArgumentParser @L420
    - def expand_path_spec(path_spec: str, cwd: Path) -> list[Path] @L441
    - def build_manifest(root: Path) -> str @L460
    - def build_manifest_for_paths(path_specs: list[str], cwd: Path) -> str @L474
    - def main() -> None @L523

## tools/update_readme.py
  Update the README usage block from the current repository manifest.
  functions:
    - def parse_args() -> argparse.Namespace @L23
    - def render_usage() -> str @L35
    - def replace_usage_block(readme: str, usage_block: str) -> str @L49
    - def main() -> None @L57

AGENTS.md

You can add the following instructions to an AGENTS.md file to force reuse-first behavior before the model writes new code:

## Reuse First With `suoyin`

Before writing any new code, generate a symbol manifest for the repo or the relevant subtree:

```bash
uvx suoyin .
```

If the repo is large, scan the relevant area first:

```bash
uvx suoyin src
uvx suoyin app
uvx suoyin package_name
```

### Required workflow

1. Run `uvx suoyin` before creating any new module, class, function, or helper.
2. Read the manifest and look for existing symbols with similar names, signatures, or responsibilities.
3. Open and inspect the most relevant existing files before deciding to add new code.
4. Prefer extending, refactoring, or reusing existing code over creating parallel implementations.
5. Do not create near-duplicate helpers, wrappers, adapters, formatters, parsers, validators, or utility modules unless you can clearly justify why reuse is impossible.
6. If similar code already exists, consolidate toward one implementation instead of adding another.
7. When you choose to add a new symbol anyway, state explicitly why the existing symbols are not the right place.

### Output expectations

In your final response, include a short `Reuse audit`:
- which existing symbols or files you checked
- what you reused or modified
- if you added something new, why duplication was avoided

### Anti-duplication rule

Treat duplicated or highly similar code as a design bug. If a new function or class overlaps heavily with an existing one, stop and refactor the existing implementation instead of adding a second version.

Never create a new file until you have checked `uvx suoyin` output and confirmed there is no appropriate existing home for the change.

Any code change that introduces a new top-level symbol without a preceding `Reuse audit` is incomplete.

Build And Publish

uv build
uv publish

If you have not published with uv before, configure a PyPI token first.

Versioning

suoyin uses VCS-derived versions.

  • The 0.1.0 baseline tag anchors version history without triggering a publish.
  • Later commits automatically become 0.1.1.devN.
  • The dev suffix increments with commit distance from the latest release tag.

GitHub Actions Publishing

This repository also includes an automated publishing workflow in publish.yml.

It publishes to PyPI when you push a tag like v0.1.1:

git tag v0.1.1
git push origin v0.1.1

The workflow uses a GitHub Actions environment secret named PYPI_API_TOKEN on the pypi environment.

Before the first automated release:

  1. Create or reuse a PyPI API token.
  2. Store it as the PYPI_API_TOKEN secret on the pypi GitHub environment.

If you use tag-triggered releases, protect tags matching v* in GitHub so only trusted maintainers can create release tags.

Metadata

Release files for suoyin 0.1.4

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

Source distribution (sdist)

Source distribution for suoyin 0.1.4
File Size Uploaded
suoyin-0.1.4.tar.gz 12.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for suoyin 0.1.4
File Interpreter ABI Platform
suoyin-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 22.6 kB

Release files / suoyin-0.1.4.tar.gz

Download URL suoyin-0.1.4.tar.gz
Size 12.6 kB
Tags Source
SHA-256 checksum
How to use checksums
411a5debb78508a5bcfdf941c53e56ca96d7d45bdfaab6381370af33832ee339
BLAKE2b-256 checksum
How to use checksums
d740a30a522bb855ad35d3cd579f6f8dba47279ec33a72943d60c19e3489fe8f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / suoyin-0.1.4-py3-none-any.whl

Download URL suoyin-0.1.4-py3-none-any.whl
Size 10.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
deb0561ba734b7867708b39214468dab183b87cd36a32e37e7d6f8408f9b6101
BLAKE2b-256 checksum
How to use checksums
6a1727e3c6836e0924e218229ad517c0fa7c91d29bb2a671c6b3bda9c03bce48
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

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