wayfind 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wayfind-0.1.0/.github/workflows/release.yml +38 -0
- wayfind-0.1.0/.github/workflows/tests.yml +13 -0
- wayfind-0.1.0/.gitignore +4 -0
- wayfind-0.1.0/AGENTS.md +37 -0
- wayfind-0.1.0/CHANGELOG.md +7 -0
- wayfind-0.1.0/DESIGN.md +27 -0
- wayfind-0.1.0/LICENSE +21 -0
- wayfind-0.1.0/Makefile +15 -0
- wayfind-0.1.0/PKG-INFO +144 -0
- wayfind-0.1.0/README.md +131 -0
- wayfind-0.1.0/pyproject.toml +35 -0
- wayfind-0.1.0/src/wayfind/__init__.py +32 -0
- wayfind-0.1.0/src/wayfind/cli.py +234 -0
- wayfind-0.1.0/src/wayfind/index.py +597 -0
- wayfind-0.1.0/src/wayfind/measures.py +463 -0
- wayfind-0.1.0/src/wayfind/parse.py +658 -0
- wayfind-0.1.0/src/wayfind/specs.py +147 -0
- wayfind-0.1.0/src/wayfind/views.py +280 -0
- wayfind-0.1.0/tests/test_wayfind.py +383 -0
- wayfind-0.1.0/uv.lock +252 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags:
|
|
5
|
+
- 'v*'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
# Trusted publishing requires `id-token: write`; no token is stored in the repo.
|
|
9
|
+
# See https://docs.pypi.org/trusted-publishers/
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-and-publish:
|
|
16
|
+
name: Build and publish to PyPI
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
environment:
|
|
19
|
+
name: pypi
|
|
20
|
+
url: https://pypi.org/project/wayfind/
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v6
|
|
24
|
+
|
|
25
|
+
- name: The tag matches the pyproject version
|
|
26
|
+
run: |
|
|
27
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
28
|
+
PYVER="$(grep -E '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')"
|
|
29
|
+
if [ "$TAG" != "$PYVER" ]; then
|
|
30
|
+
echo "::error::tag $TAG does not match pyproject version $PYVER"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
- run: uv sync --locked
|
|
35
|
+
- run: make check
|
|
36
|
+
- run: uv build
|
|
37
|
+
- name: Publish to PyPI (trusted publishing via uv)
|
|
38
|
+
run: uv publish --trusted-publishing always
|
wayfind-0.1.0/.gitignore
ADDED
wayfind-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# wayfind
|
|
2
|
+
|
|
3
|
+
A tree-sitter map of a git repository: layout, outlines, callers, impact, and
|
|
4
|
+
numbers (metrics, cycles, unused, diff) that report and never judge. Agents use
|
|
5
|
+
it to read a codebase; CI reads its numbers and holds the thresholds itself.
|
|
6
|
+
aegis depends on it from PyPI and mounts its CLI as `aegis map`.
|
|
7
|
+
|
|
8
|
+
## What done means
|
|
9
|
+
|
|
10
|
+
- `make check` passes: format, lint, typecheck and the tests. CI runs it on
|
|
11
|
+
every push and PR.
|
|
12
|
+
- New behaviour ships with a test that builds a small git repository and runs
|
|
13
|
+
the CLI on it, as `tests/test_wayfind.py` does, watched failing first.
|
|
14
|
+
- A change to a command or its output updates `README.md`, which is the user
|
|
15
|
+
reference. aegis's help page `code-map` describes the same commands; a change
|
|
16
|
+
users of `aegis map` would notice gets a line there when aegis next bumps its
|
|
17
|
+
pin.
|
|
18
|
+
- English everywhere.
|
|
19
|
+
|
|
20
|
+
## Where everything lives
|
|
21
|
+
|
|
22
|
+
| What | Where |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Every command and what it prints | `README.md` |
|
|
25
|
+
| The design rules and their reasons | `DESIGN.md`, and the package docstring in `src/wayfind/__init__.py` |
|
|
26
|
+
| Open work | GitHub issues |
|
|
27
|
+
| Releases | `CHANGELOG.md`; a `vX.Y.Z` tag publishes to PyPI (`.github/workflows/release.yml`) |
|
|
28
|
+
|
|
29
|
+
## Working here
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv sync
|
|
33
|
+
make check
|
|
34
|
+
uv run wayfind repo # the map of this repository
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Work lands through issue, branch, PR and green CI.
|
wayfind-0.1.0/DESIGN.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Design — wayfind
|
|
2
|
+
|
|
3
|
+
The rules and their reasons are in the package docstring
|
|
4
|
+
(`src/wayfind/__init__.py`); this file says how the modules divide the work.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
src/wayfind/parse.py one source file into definitions, references, facts and imports
|
|
8
|
+
src/wayfind/specs.py per-language query patterns: receiver types, bases, branches
|
|
9
|
+
src/wayfind/index.py the shared, mtime-refreshed index and name resolution
|
|
10
|
+
src/wayfind/measures.py metrics, cycles, unused and diff over the index
|
|
11
|
+
src/wayfind/views.py the plain-text views
|
|
12
|
+
src/wayfind/cli.py one typer subcommand per view; `app` is what aegis mounts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The index is one pickle per repository under the user's cache directory
|
|
16
|
+
(`$XDG_CACHE_HOME/wayfind`), refreshed by mtime and size and replaced
|
|
17
|
+
atomically, so every process of the user shares it. A cold index parses in a
|
|
18
|
+
pool of processes.
|
|
19
|
+
|
|
20
|
+
It needs no language server. An edge found by name only is printed with `?`;
|
|
21
|
+
deeper levels of `callers` and `impact` follow sure edges only. What a language
|
|
22
|
+
adds over name matching, receiver types, base classes and an exact branch
|
|
23
|
+
count, is one `Spec` of query patterns in `specs.py`, so a language is data,
|
|
24
|
+
not code.
|
|
25
|
+
|
|
26
|
+
The numbers views report and never judge. A threshold belongs to whoever reads
|
|
27
|
+
them: a `jq` filter, a review, or a CI linter reading `--json`.
|
wayfind-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alejandro Piad Morffis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
wayfind-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.PHONY: check test lint format-check typecheck
|
|
2
|
+
|
|
3
|
+
check: format-check lint typecheck test
|
|
4
|
+
|
|
5
|
+
test:
|
|
6
|
+
uv run pytest -q
|
|
7
|
+
|
|
8
|
+
lint:
|
|
9
|
+
uv run ruff check src/ tests/
|
|
10
|
+
|
|
11
|
+
format-check:
|
|
12
|
+
uv run ruff format --check src/ tests/
|
|
13
|
+
|
|
14
|
+
typecheck:
|
|
15
|
+
uv run ty check src/
|
wayfind-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: wayfind
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A tree-sitter map of a git repository: layout, outlines, callers, impact and numbers, for agents and CI
|
|
5
|
+
Project-URL: Repository, https://github.com/apiad/wayfind
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Requires-Dist: tree-sitter-language-pack>=1.22
|
|
10
|
+
Requires-Dist: tree-sitter>=0.25
|
|
11
|
+
Requires-Dist: typer>=0.24.1
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# wayfind
|
|
15
|
+
|
|
16
|
+
A tree-sitter map of a git repository, for agents and for CI. It started as
|
|
17
|
+
`aegis map` in [aegis](https://github.com/apiad/aegis), which now uses it as a
|
|
18
|
+
library and mounts the same commands under that name.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv tool install wayfind # or: uvx wayfind repo
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`wayfind` reads a git repository with tree-sitter and answers what grep
|
|
27
|
+
cannot say it answered completely: the repository's layout with each module's
|
|
28
|
+
first doc line, a file's outline, every caller of a definition, and everything
|
|
29
|
+
that depends on it. It runs in the shell, needs no server and works in any
|
|
30
|
+
language the pack of tree-sitter grammars has, from Python and TypeScript to
|
|
31
|
+
Go, Rust, Java and C.
|
|
32
|
+
|
|
33
|
+
## Start with the layout
|
|
34
|
+
|
|
35
|
+
Run `wayfind repo` in the repository for its top folders, with how many
|
|
36
|
+
classes, functions and methods each holds. Give a folder and `--depth 2` to
|
|
37
|
+
open it further: each file then shows its first doc line, which is often
|
|
38
|
+
enough to know where to look. `wayfind outline <path>` lists a file's or a
|
|
39
|
+
folder's definitions with their line, signature and first doc line, and
|
|
40
|
+
`wayfind deps <path>` says which files use which.
|
|
41
|
+
|
|
42
|
+
Read a whole file only once the map has said it is the one you need.
|
|
43
|
+
`wayfind show <symbol>` prints one definition's source without the rest of
|
|
44
|
+
its file.
|
|
45
|
+
|
|
46
|
+
## Name a definition
|
|
47
|
+
|
|
48
|
+
Every command that takes a symbol accepts a bare name (`save`), a class and
|
|
49
|
+
method (`Store.put`), or a path and a name (`pkg/store.py:helper`), where the
|
|
50
|
+
path may be any ending of the file's path. A name defined in several places
|
|
51
|
+
answers for each; `wayfind find <name>` lists them.
|
|
52
|
+
|
|
53
|
+
## Callers and impact
|
|
54
|
+
|
|
55
|
+
`wayfind callers <symbol>` lists every call of a definition, grouped by
|
|
56
|
+
file, as the enclosing definition and its line. `--depth 2` also lists the
|
|
57
|
+
callers of those callers, indented. `wayfind impact <symbol>` counts every
|
|
58
|
+
use, not only calls, and follows three levels by default: what you would have
|
|
59
|
+
to check if you changed it, or what would break. `wayfind callees <symbol>` goes the other way.
|
|
60
|
+
|
|
61
|
+
A call on a receiver, such as `self.queues.turn_ended()`, resolves through the
|
|
62
|
+
receiver's type wherever the source states one: a parameter or variable
|
|
63
|
+
annotation, a constructor call assigned to it, a class field, an import under
|
|
64
|
+
another name. When nothing in the receiver's class types an attribute (it is
|
|
65
|
+
set to `None` there and assigned from outside), the map takes the one class
|
|
66
|
+
every assignment to an attribute of that name agrees on, preferring
|
|
67
|
+
assignments through a receiver of the same class, and leaving out tests' fakes
|
|
68
|
+
when it answers for code outside the tests. A call through a base class or
|
|
69
|
+
protocol method also reaches every override in a subclass, since that is what
|
|
70
|
+
may run: `callers` of an implementation lists the calls made through its
|
|
71
|
+
protocol.
|
|
72
|
+
|
|
73
|
+
An edge without a mark is sure. An edge ending in `?` is not: either the
|
|
74
|
+
receiver's type is stated nowhere, so the call matched every method of that
|
|
75
|
+
name, or the receiver may hold more than one class. A call on a receiver never
|
|
76
|
+
matches a module-level function by name, and a call on a type from outside the
|
|
77
|
+
repository (`Path`, `dict`, `json`) matches nothing. Deeper levels follow sure
|
|
78
|
+
edges only.
|
|
79
|
+
|
|
80
|
+
The receiver types work in Python, JavaScript and TypeScript. Other languages
|
|
81
|
+
get the rest of the map with calls matched by name; another language is
|
|
82
|
+
one more `Spec` in `src/wayfind/specs.py` (its docstring says how).
|
|
83
|
+
|
|
84
|
+
## Numbers for a review
|
|
85
|
+
|
|
86
|
+
Four views give numbers and no verdicts. Which numbers matter, and where a
|
|
87
|
+
line falls, is yours to decide: each takes `--json`, and a gate or a review is
|
|
88
|
+
a filter over it.
|
|
89
|
+
|
|
90
|
+
- `wayfind metrics [path]` has one row per definition, file and
|
|
91
|
+
folder: lines, complexity (counted as ruff's C901 counts it for Python, and
|
|
92
|
+
as decision points for other languages), whether it has a doc, parameters
|
|
93
|
+
with a type out of all, a stated return type, uses of `Any`, calls on a
|
|
94
|
+
receiver of unknown type (the missing types that cost navigation), and
|
|
95
|
+
fan-in and fan-out. Folders carry which folders they use. The text view
|
|
96
|
+
ranks the top rows; `--sort complexity` or `fan-in` and `--top` change it.
|
|
97
|
+
- `wayfind cycles [path]` lists the files, or with `--level package` the
|
|
98
|
+
folders, that use each other in a cycle, with one cycle spelled out. In
|
|
99
|
+
Python a use in an annotation counts.
|
|
100
|
+
- `wayfind unused [path]` lists definitions no reference names outside
|
|
101
|
+
their own body, and those only tests name. It goes by name, so a function
|
|
102
|
+
reached through a string (a `getattr` dispatch, a route table, HTML) is
|
|
103
|
+
listed; dunders, decorated registrations, overrides, handlers assigned to
|
|
104
|
+
`on…` properties and members of object literals are left out.
|
|
105
|
+
- `wayfind diff [base]` reads a branch: every definition the branch touched
|
|
106
|
+
since `base` (by default the merge-base with `origin/main`, uncommitted work
|
|
107
|
+
included), whether it was added, removed, moved or modified, its lines and
|
|
108
|
+
complexity before and after, the sure users outside the branch, and for a
|
|
109
|
+
removed one every place that still names it.
|
|
110
|
+
|
|
111
|
+
For example, the definitions over 50 lines and those ruff's C901 would flag:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
wayfind metrics src --json | jq '[.functions[] | select(.lines > 50)] | length'
|
|
115
|
+
wayfind metrics src --json | jq -r '.functions[] | select(.complexity > 10) | .id'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## The index
|
|
119
|
+
|
|
120
|
+
The first command in a repository parses every file `git ls-files` lists in
|
|
121
|
+
a known language, in a pool of processes. That takes a few seconds on a
|
|
122
|
+
thousand files, and stderr says how many it parsed. Ignored files are never
|
|
123
|
+
indexed; `--exclude <path or glob>` (or `-x`, repeatable) leaves out more,
|
|
124
|
+
such as a `legacy/` folder or generated code, from the views and from what a
|
|
125
|
+
name may match. An excluded file keeps its place in the index, so changing
|
|
126
|
+
the exclusions re-parses nothing. After that each command re-parses only the
|
|
127
|
+
files whose modification time or size changed, so a query costs about a
|
|
128
|
+
second. The index lives in the user's cache directory, one file per
|
|
129
|
+
repository, and every session of the same user shares it.
|
|
130
|
+
|
|
131
|
+
Each grammar downloads the first time the map meets its language. On a host
|
|
132
|
+
without internet those files are skipped, and stderr names how many of which
|
|
133
|
+
language.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv sync
|
|
139
|
+
make check # format, lint, typecheck, tests
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
wayfind-0.1.0/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# wayfind
|
|
2
|
+
|
|
3
|
+
A tree-sitter map of a git repository, for agents and for CI. It started as
|
|
4
|
+
`aegis map` in [aegis](https://github.com/apiad/aegis), which now uses it as a
|
|
5
|
+
library and mounts the same commands under that name.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv tool install wayfind # or: uvx wayfind repo
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`wayfind` reads a git repository with tree-sitter and answers what grep
|
|
14
|
+
cannot say it answered completely: the repository's layout with each module's
|
|
15
|
+
first doc line, a file's outline, every caller of a definition, and everything
|
|
16
|
+
that depends on it. It runs in the shell, needs no server and works in any
|
|
17
|
+
language the pack of tree-sitter grammars has, from Python and TypeScript to
|
|
18
|
+
Go, Rust, Java and C.
|
|
19
|
+
|
|
20
|
+
## Start with the layout
|
|
21
|
+
|
|
22
|
+
Run `wayfind repo` in the repository for its top folders, with how many
|
|
23
|
+
classes, functions and methods each holds. Give a folder and `--depth 2` to
|
|
24
|
+
open it further: each file then shows its first doc line, which is often
|
|
25
|
+
enough to know where to look. `wayfind outline <path>` lists a file's or a
|
|
26
|
+
folder's definitions with their line, signature and first doc line, and
|
|
27
|
+
`wayfind deps <path>` says which files use which.
|
|
28
|
+
|
|
29
|
+
Read a whole file only once the map has said it is the one you need.
|
|
30
|
+
`wayfind show <symbol>` prints one definition's source without the rest of
|
|
31
|
+
its file.
|
|
32
|
+
|
|
33
|
+
## Name a definition
|
|
34
|
+
|
|
35
|
+
Every command that takes a symbol accepts a bare name (`save`), a class and
|
|
36
|
+
method (`Store.put`), or a path and a name (`pkg/store.py:helper`), where the
|
|
37
|
+
path may be any ending of the file's path. A name defined in several places
|
|
38
|
+
answers for each; `wayfind find <name>` lists them.
|
|
39
|
+
|
|
40
|
+
## Callers and impact
|
|
41
|
+
|
|
42
|
+
`wayfind callers <symbol>` lists every call of a definition, grouped by
|
|
43
|
+
file, as the enclosing definition and its line. `--depth 2` also lists the
|
|
44
|
+
callers of those callers, indented. `wayfind impact <symbol>` counts every
|
|
45
|
+
use, not only calls, and follows three levels by default: what you would have
|
|
46
|
+
to check if you changed it, or what would break. `wayfind callees <symbol>` goes the other way.
|
|
47
|
+
|
|
48
|
+
A call on a receiver, such as `self.queues.turn_ended()`, resolves through the
|
|
49
|
+
receiver's type wherever the source states one: a parameter or variable
|
|
50
|
+
annotation, a constructor call assigned to it, a class field, an import under
|
|
51
|
+
another name. When nothing in the receiver's class types an attribute (it is
|
|
52
|
+
set to `None` there and assigned from outside), the map takes the one class
|
|
53
|
+
every assignment to an attribute of that name agrees on, preferring
|
|
54
|
+
assignments through a receiver of the same class, and leaving out tests' fakes
|
|
55
|
+
when it answers for code outside the tests. A call through a base class or
|
|
56
|
+
protocol method also reaches every override in a subclass, since that is what
|
|
57
|
+
may run: `callers` of an implementation lists the calls made through its
|
|
58
|
+
protocol.
|
|
59
|
+
|
|
60
|
+
An edge without a mark is sure. An edge ending in `?` is not: either the
|
|
61
|
+
receiver's type is stated nowhere, so the call matched every method of that
|
|
62
|
+
name, or the receiver may hold more than one class. A call on a receiver never
|
|
63
|
+
matches a module-level function by name, and a call on a type from outside the
|
|
64
|
+
repository (`Path`, `dict`, `json`) matches nothing. Deeper levels follow sure
|
|
65
|
+
edges only.
|
|
66
|
+
|
|
67
|
+
The receiver types work in Python, JavaScript and TypeScript. Other languages
|
|
68
|
+
get the rest of the map with calls matched by name; another language is
|
|
69
|
+
one more `Spec` in `src/wayfind/specs.py` (its docstring says how).
|
|
70
|
+
|
|
71
|
+
## Numbers for a review
|
|
72
|
+
|
|
73
|
+
Four views give numbers and no verdicts. Which numbers matter, and where a
|
|
74
|
+
line falls, is yours to decide: each takes `--json`, and a gate or a review is
|
|
75
|
+
a filter over it.
|
|
76
|
+
|
|
77
|
+
- `wayfind metrics [path]` has one row per definition, file and
|
|
78
|
+
folder: lines, complexity (counted as ruff's C901 counts it for Python, and
|
|
79
|
+
as decision points for other languages), whether it has a doc, parameters
|
|
80
|
+
with a type out of all, a stated return type, uses of `Any`, calls on a
|
|
81
|
+
receiver of unknown type (the missing types that cost navigation), and
|
|
82
|
+
fan-in and fan-out. Folders carry which folders they use. The text view
|
|
83
|
+
ranks the top rows; `--sort complexity` or `fan-in` and `--top` change it.
|
|
84
|
+
- `wayfind cycles [path]` lists the files, or with `--level package` the
|
|
85
|
+
folders, that use each other in a cycle, with one cycle spelled out. In
|
|
86
|
+
Python a use in an annotation counts.
|
|
87
|
+
- `wayfind unused [path]` lists definitions no reference names outside
|
|
88
|
+
their own body, and those only tests name. It goes by name, so a function
|
|
89
|
+
reached through a string (a `getattr` dispatch, a route table, HTML) is
|
|
90
|
+
listed; dunders, decorated registrations, overrides, handlers assigned to
|
|
91
|
+
`on…` properties and members of object literals are left out.
|
|
92
|
+
- `wayfind diff [base]` reads a branch: every definition the branch touched
|
|
93
|
+
since `base` (by default the merge-base with `origin/main`, uncommitted work
|
|
94
|
+
included), whether it was added, removed, moved or modified, its lines and
|
|
95
|
+
complexity before and after, the sure users outside the branch, and for a
|
|
96
|
+
removed one every place that still names it.
|
|
97
|
+
|
|
98
|
+
For example, the definitions over 50 lines and those ruff's C901 would flag:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
wayfind metrics src --json | jq '[.functions[] | select(.lines > 50)] | length'
|
|
102
|
+
wayfind metrics src --json | jq -r '.functions[] | select(.complexity > 10) | .id'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## The index
|
|
106
|
+
|
|
107
|
+
The first command in a repository parses every file `git ls-files` lists in
|
|
108
|
+
a known language, in a pool of processes. That takes a few seconds on a
|
|
109
|
+
thousand files, and stderr says how many it parsed. Ignored files are never
|
|
110
|
+
indexed; `--exclude <path or glob>` (or `-x`, repeatable) leaves out more,
|
|
111
|
+
such as a `legacy/` folder or generated code, from the views and from what a
|
|
112
|
+
name may match. An excluded file keeps its place in the index, so changing
|
|
113
|
+
the exclusions re-parses nothing. After that each command re-parses only the
|
|
114
|
+
files whose modification time or size changed, so a query costs about a
|
|
115
|
+
second. The index lives in the user's cache directory, one file per
|
|
116
|
+
repository, and every session of the same user shares it.
|
|
117
|
+
|
|
118
|
+
Each grammar downloads the first time the map meets its language. On a host
|
|
119
|
+
without internet those files are skipped, and stderr names how many of which
|
|
120
|
+
language.
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv sync
|
|
126
|
+
make check # format, lint, typecheck, tests
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wayfind"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A tree-sitter map of a git repository: layout, outlines, callers, impact and numbers, for agents and CI"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.13"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"tree-sitter>=0.25",
|
|
10
|
+
"tree-sitter-language-pack>=1.22",
|
|
11
|
+
"typer>=0.24.1",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
wayfind = "wayfind.cli:app"
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Repository = "https://github.com/apiad/wayfind"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"ruff>=0.15.9,<0.16",
|
|
27
|
+
"ty==0.0.29",
|
|
28
|
+
"pytest>=8.0.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
target-version = "py313"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""wayfind: a tree-sitter map of a git repository, for agents.
|
|
2
|
+
|
|
3
|
+
It answers what grep cannot say it answered completely: the repository's
|
|
4
|
+
layout with each module's first doc line, a file's outline, and every caller
|
|
5
|
+
of a symbol and everything that depends on it.
|
|
6
|
+
|
|
7
|
+
The rules, with their reasons:
|
|
8
|
+
|
|
9
|
+
- **Heuristics, no language server.** tree-sitter parses any of the pack's
|
|
10
|
+
languages in milliseconds and needs nothing installed per language. The
|
|
11
|
+
pack's tags queries give definitions and calls for the languages that have
|
|
12
|
+
them; any other language falls back to nodes whose type reads like a
|
|
13
|
+
definition (parse.py).
|
|
14
|
+
- **Every edge says how it was found.** An edge is *sure* when the name is
|
|
15
|
+
defined in the same file, when the using file's imports name both the
|
|
16
|
+
target's module and the name, or when the receiver's type is stated
|
|
17
|
+
(index.py says where from). Anything else is a name match, printed with
|
|
18
|
+
``?``. Measured against jedi on aegis, where this map began, sure edges were right 96% of
|
|
19
|
+
the time and both kinds together found 97% of real references; the ``?``
|
|
20
|
+
edges were mostly common method names, so ``impact`` expands through sure
|
|
21
|
+
edges only (index.py).
|
|
22
|
+
- **One index per repository, shared, refreshed by mtime.** Every session of
|
|
23
|
+
the user reads the same cache file, and each query re-parses only the files
|
|
24
|
+
whose mtime or size changed, so a warm query costs a stat per file. The file
|
|
25
|
+
is replaced atomically, so two sessions writing it at once lose a refresh,
|
|
26
|
+
never the index.
|
|
27
|
+
- **Numbers, not verdicts.** metrics, cycles, unused and diff report per
|
|
28
|
+
definition, file and folder, with ``--json``; thresholds belong to whoever
|
|
29
|
+
reads them (measures.py).
|
|
30
|
+
- **A CLI, not MCP tools.** Every MCP tool's schema rides on every request of
|
|
31
|
+
every session; a subcommand costs nothing until an agent runs it.
|
|
32
|
+
"""
|