seamcheck 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.
- seamcheck-0.1.0/.gitignore +10 -0
- seamcheck-0.1.0/LICENSE +21 -0
- seamcheck-0.1.0/PKG-INFO +170 -0
- seamcheck-0.1.0/README.md +145 -0
- seamcheck-0.1.0/pyproject.toml +65 -0
- seamcheck-0.1.0/seamcheck/AGENTS.md +96 -0
- seamcheck-0.1.0/seamcheck/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/api.py +315 -0
- seamcheck-0.1.0/seamcheck/apps.py +21 -0
- seamcheck-0.1.0/seamcheck/attribution.py +45 -0
- seamcheck-0.1.0/seamcheck/classifier.py +116 -0
- seamcheck-0.1.0/seamcheck/console.py +150 -0
- seamcheck-0.1.0/seamcheck/coverage.py +38 -0
- seamcheck-0.1.0/seamcheck/css_tools/parse_css.bundle.mjs +6192 -0
- seamcheck-0.1.0/seamcheck/css_tools/parse_css.mjs +41 -0
- seamcheck-0.1.0/seamcheck/diff.py +79 -0
- seamcheck-0.1.0/seamcheck/dom_matcher.py +145 -0
- seamcheck-0.1.0/seamcheck/extractors/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/extractors/asgi_extractor.py +79 -0
- seamcheck-0.1.0/seamcheck/extractors/css_extractor.py +139 -0
- seamcheck-0.1.0/seamcheck/extractors/django_extractor.py +106 -0
- seamcheck-0.1.0/seamcheck/extractors/django_models_extractor.py +77 -0
- seamcheck-0.1.0/seamcheck/extractors/dom_js_extractor.py +337 -0
- seamcheck-0.1.0/seamcheck/extractors/entry_points_extractor.py +114 -0
- seamcheck-0.1.0/seamcheck/extractors/js_extractor.py +378 -0
- seamcheck-0.1.0/seamcheck/extractors/reachability.py +96 -0
- seamcheck-0.1.0/seamcheck/extractors/template_scanner.py +84 -0
- seamcheck-0.1.0/seamcheck/field_matcher.py +206 -0
- seamcheck-0.1.0/seamcheck/filetree.py +111 -0
- seamcheck-0.1.0/seamcheck/graph.py +124 -0
- seamcheck-0.1.0/seamcheck/history.py +289 -0
- seamcheck-0.1.0/seamcheck/js_tools/parse_js.bundle.mjs +5679 -0
- seamcheck-0.1.0/seamcheck/js_tools/parse_js.mjs +34 -0
- seamcheck-0.1.0/seamcheck/management/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/management/commands/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/management/commands/seamcheck.py +256 -0
- seamcheck-0.1.0/seamcheck/mapdata.py +274 -0
- seamcheck-0.1.0/seamcheck/matcher.py +84 -0
- seamcheck-0.1.0/seamcheck/mcp_server.py +37 -0
- seamcheck-0.1.0/seamcheck/nodetools.py +56 -0
- seamcheck-0.1.0/seamcheck/pagenames.py +143 -0
- seamcheck-0.1.0/seamcheck/pipeline.py +185 -0
- seamcheck-0.1.0/seamcheck/renderers/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/renderers/_shared.py +29 -0
- seamcheck-0.1.0/seamcheck/renderers/html.py +182 -0
- seamcheck-0.1.0/seamcheck/renderers/map_html.py +1115 -0
- seamcheck-0.1.0/seamcheck/renderers/markdown.py +73 -0
- seamcheck-0.1.0/seamcheck/renderers/terminal.py +61 -0
- seamcheck-0.1.0/seamcheck/report.py +167 -0
- seamcheck-0.1.0/seamcheck/roots.py +91 -0
- seamcheck-0.1.0/seamcheck/serve.py +106 -0
- seamcheck-0.1.0/seamcheck/snapshot.py +37 -0
- seamcheck-0.1.0/seamcheck/tests/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/__init__.py +0 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_admin.py +8 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_asgi.py +23 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom.css +18 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_imported.css +0 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_multiwriter_a.js +3 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_multiwriter_b.js +18 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_single_writer.js +11 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_template.html +5 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_entry.js +5 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_graph_models_sample.json +156 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_included_urls.py +7 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_module.js +25 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_page_entry_a.js +3 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_reachability_a.py +5 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_reachability_b.py +2 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_reachability_root.py +7 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_relative_importer.py +5 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_signals.py +12 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_static_js_template.html +2 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_string_reference.py +5 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_style.css +0 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_tailwind_build_output.css +4 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_template_tags.py +18 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_unreachable.py +2 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_urls.py +11 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_views.py +13 -0
- seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_vite.config.js +13 -0
- seamcheck-0.1.0/seamcheck/tests/test_app_config.py +32 -0
- seamcheck-0.1.0/seamcheck/tests/test_asgi_extractor.py +45 -0
- seamcheck-0.1.0/seamcheck/tests/test_attribution.py +79 -0
- seamcheck-0.1.0/seamcheck/tests/test_classifier.py +49 -0
- seamcheck-0.1.0/seamcheck/tests/test_cli.py +256 -0
- seamcheck-0.1.0/seamcheck/tests/test_console.py +68 -0
- seamcheck-0.1.0/seamcheck/tests/test_coverage.py +38 -0
- seamcheck-0.1.0/seamcheck/tests/test_css_extractor.py +176 -0
- seamcheck-0.1.0/seamcheck/tests/test_css_recall_gaps.py +112 -0
- seamcheck-0.1.0/seamcheck/tests/test_diff.py +101 -0
- seamcheck-0.1.0/seamcheck/tests/test_django_extractor.py +86 -0
- seamcheck-0.1.0/seamcheck/tests/test_django_models_extractor.py +66 -0
- seamcheck-0.1.0/seamcheck/tests/test_dom_js_extractor.py +75 -0
- seamcheck-0.1.0/seamcheck/tests/test_dom_matcher.py +170 -0
- seamcheck-0.1.0/seamcheck/tests/test_entry_points_extractor.py +57 -0
- seamcheck-0.1.0/seamcheck/tests/test_field_matcher.py +123 -0
- seamcheck-0.1.0/seamcheck/tests/test_filetree.py +59 -0
- seamcheck-0.1.0/seamcheck/tests/test_graph.py +104 -0
- seamcheck-0.1.0/seamcheck/tests/test_history.py +184 -0
- seamcheck-0.1.0/seamcheck/tests/test_js_class_usage.py +160 -0
- seamcheck-0.1.0/seamcheck/tests/test_js_extractor.py +78 -0
- seamcheck-0.1.0/seamcheck/tests/test_mapdata.py +149 -0
- seamcheck-0.1.0/seamcheck/tests/test_matcher.py +102 -0
- seamcheck-0.1.0/seamcheck/tests/test_mcp_server.py +60 -0
- seamcheck-0.1.0/seamcheck/tests/test_nodetools.py +74 -0
- seamcheck-0.1.0/seamcheck/tests/test_pagenames.py +143 -0
- seamcheck-0.1.0/seamcheck/tests/test_pipeline_dom_css_regression.py +89 -0
- seamcheck-0.1.0/seamcheck/tests/test_pipeline_fixture_regression.py +93 -0
- seamcheck-0.1.0/seamcheck/tests/test_reachability.py +49 -0
- seamcheck-0.1.0/seamcheck/tests/test_renderer_html.py +249 -0
- seamcheck-0.1.0/seamcheck/tests/test_renderer_map.py +472 -0
- seamcheck-0.1.0/seamcheck/tests/test_renderer_markdown.py +105 -0
- seamcheck-0.1.0/seamcheck/tests/test_renderer_shared.py +49 -0
- seamcheck-0.1.0/seamcheck/tests/test_renderer_terminal.py +115 -0
- seamcheck-0.1.0/seamcheck/tests/test_report.py +174 -0
- seamcheck-0.1.0/seamcheck/tests/test_report_end_to_end.py +106 -0
- seamcheck-0.1.0/seamcheck/tests/test_roots.py +66 -0
- seamcheck-0.1.0/seamcheck/tests/test_serve.py +70 -0
- seamcheck-0.1.0/seamcheck/tests/test_snapshot.py +41 -0
- seamcheck-0.1.0/seamcheck/tests/test_template_scanner.py +61 -0
- seamcheck-0.1.0/seamcheck/tests/test_triage.py +123 -0
- seamcheck-0.1.0/seamcheck/triage.json +3 -0
- seamcheck-0.1.0/seamcheck/triage.py +126 -0
seamcheck-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dardameiz
|
|
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.
|
seamcheck-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: seamcheck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find the code your project no longer connects to - and the connections it only thinks it has.
|
|
5
|
+
Project-URL: Source, https://github.com/dardameiz/seamcheck
|
|
6
|
+
Project-URL: Issues, https://github.com/dardameiz/seamcheck/issues
|
|
7
|
+
Author: dardameiz
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: connectivity,dead-code,django,mcp,static-analysis
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: django>=5.0
|
|
18
|
+
Provides-Extra: mcp
|
|
19
|
+
Requires-Dist: mcp>=1.0; extra == 'mcp'
|
|
20
|
+
Provides-Extra: models
|
|
21
|
+
Requires-Dist: django-extensions>=4.0; extra == 'models'
|
|
22
|
+
Provides-Extra: node
|
|
23
|
+
Requires-Dist: nodejs-wheel-binaries>=20; extra == 'node'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Seamcheck
|
|
27
|
+
|
|
28
|
+
**Your AI wrote 400 lines. Which of them are actually wired to anything?**
|
|
29
|
+
|
|
30
|
+
Seamcheck reads a Django + JavaScript project and tells you what connects to what — which
|
|
31
|
+
`fetch()` lands on which view, which template element the JS is reaching for, which CSS
|
|
32
|
+
rule nothing has referenced since 2023. Then it tells you what it *couldn't* work out,
|
|
33
|
+
which turns out to be the part that matters.
|
|
34
|
+
|
|
35
|
+
No SaaS. No upload. One command, one HTML file, and an exit code for CI.
|
|
36
|
+
|
|
37
|
+
## The four answers
|
|
38
|
+
|
|
39
|
+
Every dead-code tool ever written has told you something was unused and been wrong, and
|
|
40
|
+
you stopped trusting it. Seamcheck has a fourth answer:
|
|
41
|
+
|
|
42
|
+
| | |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `connected` | something reaches this — here's the file and line |
|
|
45
|
+
| `unresolved` | something reaches for this and it isn't there |
|
|
46
|
+
| `unused` | both ends are observable and nothing uses it |
|
|
47
|
+
| **`uncertain`** | **no evidence either way. Not a claim it's dead.** |
|
|
48
|
+
|
|
49
|
+
That last row is the whole product. A page reached by an `<a href>` looks exactly like a
|
|
50
|
+
dead one if you only parse `fetch()` calls. Seamcheck says so instead of guessing. On a
|
|
51
|
+
700-URL project that's the difference between a report you act on and 668 lies.
|
|
52
|
+
|
|
53
|
+
It also reports **its own coverage** — how much of each file it actually reasoned about.
|
|
54
|
+
No other tool I've found will tell you where it wasn't looking.
|
|
55
|
+
|
|
56
|
+
## What it found in the project it was built on
|
|
57
|
+
|
|
58
|
+
Not hypotheticals. Real bugs, in a real 36,000-symbol codebase, found while writing this:
|
|
59
|
+
|
|
60
|
+
- Five CSS custom properties in a **loaded** stylesheet that resolve to nothing —
|
|
61
|
+
`--text-primary`, `--border-color` and friends. Those declarations render nothing today.
|
|
62
|
+
- An endpoint reported missing that turned out to be **`<str:division_id>` matching a
|
|
63
|
+
deliberate `'all'` sentinel** — so I fixed the matcher instead of "fixing" the code.
|
|
64
|
+
- 65 API routes whose path appears in no source file at all.
|
|
65
|
+
- 46 DOM elements written by more than one module — the reason a display bug survives
|
|
66
|
+
being "fixed" in one of them.
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install seamcheck
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then two things in `settings.py`:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
INSTALLED_APPS = [..., "seamcheck"]
|
|
78
|
+
|
|
79
|
+
SEAMCHECK_CONFIG = {
|
|
80
|
+
"urlconf_module": "myproject.urls",
|
|
81
|
+
"templates_root": "myapp/templates",
|
|
82
|
+
"js_source_root": "myapp/static/js",
|
|
83
|
+
"css_source_root": "myapp/static/css",
|
|
84
|
+
"first_party_prefixes": ["myapp", "myproject"],
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Every project-specific path lives in that dict. Nothing is hardcoded anywhere in the
|
|
89
|
+
extractors — which is how this was lifted out of the project it grew in without touching
|
|
90
|
+
a line of it.
|
|
91
|
+
|
|
92
|
+
**You need Node on PATH.** The JS and CSS parsers run on it. You do *not* need npm or
|
|
93
|
+
`node_modules` — acorn and postcss ship inlined in the wheel. If Node is missing,
|
|
94
|
+
Seamcheck says so and gives you the Python half rather than dying.
|
|
95
|
+
|
|
96
|
+
## Use
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python manage.py seamcheck # scan, summary, write the map
|
|
100
|
+
python manage.py seamcheck --check # exit 1 on new findings. This is the CI one.
|
|
101
|
+
python manage.py seamcheck --format map # the UI, one self-contained HTML file
|
|
102
|
+
python manage.py seamcheck --explain <id> # one symbol, with the code around it
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### The UI
|
|
106
|
+
|
|
107
|
+
One file, no network, opens on a phone. A left rail of views; a canvas that draws
|
|
108
|
+
**every symbol a page touches at once** — 1,366 of them on the biggest page here — with
|
|
109
|
+
the broken ones filled in red so they find you rather than the other way round.
|
|
110
|
+
|
|
111
|
+
Click any node and it lights the line through it: page → module → `fetch()` → URL →
|
|
112
|
+
view, each hop with the real source, and a button to show the whole enclosing function.
|
|
113
|
+
Or isolate that one chain and drop everything else.
|
|
114
|
+
|
|
115
|
+
There's a **Files** view too — your actual folder tree, with a bar per file showing how
|
|
116
|
+
many of its declarations Seamcheck reasoned about. Because "no findings" and "never
|
|
117
|
+
looked" are not the same sentence.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python manage.py seamcheck --format map --serve # open it from your phone
|
|
121
|
+
python manage.py seamcheck --format map --serve --tunnel # ...from anywhere
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Nothing is uploaded. `--serve` is a socket on your machine that dies with the command.
|
|
125
|
+
|
|
126
|
+
### Per-commit
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python manage.py seamcheck --backfill 20 # scan the last 20 commits
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Now the map has a commit picker. Pick one and see what *that commit* changed — added,
|
|
133
|
+
removed, status flipped — including things it deleted, which no longer exist to be drawn
|
|
134
|
+
and get named instead.
|
|
135
|
+
|
|
136
|
+
### CI
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
python manage.py seamcheck --check --since $BASE_SHA
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`1` = new findings. `2` = no baseline, so the gate didn't run. `0` = clean. That
|
|
143
|
+
distinction matters: a gate that never ran is not a gate that passed.
|
|
144
|
+
|
|
145
|
+
### Agents
|
|
146
|
+
|
|
147
|
+
`seamcheck_check`, `seamcheck_report`, `seamcheck_explain`, `seamcheck_triage` over MCP,
|
|
148
|
+
and an `AGENTS.md` that tells your agent the one rule that matters: **never delete
|
|
149
|
+
something because it's `uncertain`.**
|
|
150
|
+
|
|
151
|
+
## What it can't do
|
|
152
|
+
|
|
153
|
+
Written down because a tool that hides its blind spots is worse than no tool:
|
|
154
|
+
|
|
155
|
+
- **Django + vanilla JS.** No React, Vue, or TypeScript yet.
|
|
156
|
+
- **Celery, Redis, WebSockets and Stripe aren't traced.** Anything reached only through
|
|
157
|
+
those is invisible, and the UI says so rather than showing a confident zero.
|
|
158
|
+
- **A URL built at runtime** stays `uncertain`. The prefix is recorded, never a guess.
|
|
159
|
+
- **It has been run against one real project.** Mine. That's one more than most tools at
|
|
160
|
+
this stage and far fewer than you'd want.
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Issues and PRs welcome. One house rule, and it's the reason the tool is worth anything:
|
|
165
|
+
**never make a claim the scan can't evidence.** If you can't prove it, it's `uncertain`,
|
|
166
|
+
and the note says which evidence source was missing.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Seamcheck
|
|
2
|
+
|
|
3
|
+
**Your AI wrote 400 lines. Which of them are actually wired to anything?**
|
|
4
|
+
|
|
5
|
+
Seamcheck reads a Django + JavaScript project and tells you what connects to what — which
|
|
6
|
+
`fetch()` lands on which view, which template element the JS is reaching for, which CSS
|
|
7
|
+
rule nothing has referenced since 2023. Then it tells you what it *couldn't* work out,
|
|
8
|
+
which turns out to be the part that matters.
|
|
9
|
+
|
|
10
|
+
No SaaS. No upload. One command, one HTML file, and an exit code for CI.
|
|
11
|
+
|
|
12
|
+
## The four answers
|
|
13
|
+
|
|
14
|
+
Every dead-code tool ever written has told you something was unused and been wrong, and
|
|
15
|
+
you stopped trusting it. Seamcheck has a fourth answer:
|
|
16
|
+
|
|
17
|
+
| | |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `connected` | something reaches this — here's the file and line |
|
|
20
|
+
| `unresolved` | something reaches for this and it isn't there |
|
|
21
|
+
| `unused` | both ends are observable and nothing uses it |
|
|
22
|
+
| **`uncertain`** | **no evidence either way. Not a claim it's dead.** |
|
|
23
|
+
|
|
24
|
+
That last row is the whole product. A page reached by an `<a href>` looks exactly like a
|
|
25
|
+
dead one if you only parse `fetch()` calls. Seamcheck says so instead of guessing. On a
|
|
26
|
+
700-URL project that's the difference between a report you act on and 668 lies.
|
|
27
|
+
|
|
28
|
+
It also reports **its own coverage** — how much of each file it actually reasoned about.
|
|
29
|
+
No other tool I've found will tell you where it wasn't looking.
|
|
30
|
+
|
|
31
|
+
## What it found in the project it was built on
|
|
32
|
+
|
|
33
|
+
Not hypotheticals. Real bugs, in a real 36,000-symbol codebase, found while writing this:
|
|
34
|
+
|
|
35
|
+
- Five CSS custom properties in a **loaded** stylesheet that resolve to nothing —
|
|
36
|
+
`--text-primary`, `--border-color` and friends. Those declarations render nothing today.
|
|
37
|
+
- An endpoint reported missing that turned out to be **`<str:division_id>` matching a
|
|
38
|
+
deliberate `'all'` sentinel** — so I fixed the matcher instead of "fixing" the code.
|
|
39
|
+
- 65 API routes whose path appears in no source file at all.
|
|
40
|
+
- 46 DOM elements written by more than one module — the reason a display bug survives
|
|
41
|
+
being "fixed" in one of them.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install seamcheck
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then two things in `settings.py`:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
INSTALLED_APPS = [..., "seamcheck"]
|
|
53
|
+
|
|
54
|
+
SEAMCHECK_CONFIG = {
|
|
55
|
+
"urlconf_module": "myproject.urls",
|
|
56
|
+
"templates_root": "myapp/templates",
|
|
57
|
+
"js_source_root": "myapp/static/js",
|
|
58
|
+
"css_source_root": "myapp/static/css",
|
|
59
|
+
"first_party_prefixes": ["myapp", "myproject"],
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Every project-specific path lives in that dict. Nothing is hardcoded anywhere in the
|
|
64
|
+
extractors — which is how this was lifted out of the project it grew in without touching
|
|
65
|
+
a line of it.
|
|
66
|
+
|
|
67
|
+
**You need Node on PATH.** The JS and CSS parsers run on it. You do *not* need npm or
|
|
68
|
+
`node_modules` — acorn and postcss ship inlined in the wheel. If Node is missing,
|
|
69
|
+
Seamcheck says so and gives you the Python half rather than dying.
|
|
70
|
+
|
|
71
|
+
## Use
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python manage.py seamcheck # scan, summary, write the map
|
|
75
|
+
python manage.py seamcheck --check # exit 1 on new findings. This is the CI one.
|
|
76
|
+
python manage.py seamcheck --format map # the UI, one self-contained HTML file
|
|
77
|
+
python manage.py seamcheck --explain <id> # one symbol, with the code around it
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### The UI
|
|
81
|
+
|
|
82
|
+
One file, no network, opens on a phone. A left rail of views; a canvas that draws
|
|
83
|
+
**every symbol a page touches at once** — 1,366 of them on the biggest page here — with
|
|
84
|
+
the broken ones filled in red so they find you rather than the other way round.
|
|
85
|
+
|
|
86
|
+
Click any node and it lights the line through it: page → module → `fetch()` → URL →
|
|
87
|
+
view, each hop with the real source, and a button to show the whole enclosing function.
|
|
88
|
+
Or isolate that one chain and drop everything else.
|
|
89
|
+
|
|
90
|
+
There's a **Files** view too — your actual folder tree, with a bar per file showing how
|
|
91
|
+
many of its declarations Seamcheck reasoned about. Because "no findings" and "never
|
|
92
|
+
looked" are not the same sentence.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python manage.py seamcheck --format map --serve # open it from your phone
|
|
96
|
+
python manage.py seamcheck --format map --serve --tunnel # ...from anywhere
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Nothing is uploaded. `--serve` is a socket on your machine that dies with the command.
|
|
100
|
+
|
|
101
|
+
### Per-commit
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python manage.py seamcheck --backfill 20 # scan the last 20 commits
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Now the map has a commit picker. Pick one and see what *that commit* changed — added,
|
|
108
|
+
removed, status flipped — including things it deleted, which no longer exist to be drawn
|
|
109
|
+
and get named instead.
|
|
110
|
+
|
|
111
|
+
### CI
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python manage.py seamcheck --check --since $BASE_SHA
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`1` = new findings. `2` = no baseline, so the gate didn't run. `0` = clean. That
|
|
118
|
+
distinction matters: a gate that never ran is not a gate that passed.
|
|
119
|
+
|
|
120
|
+
### Agents
|
|
121
|
+
|
|
122
|
+
`seamcheck_check`, `seamcheck_report`, `seamcheck_explain`, `seamcheck_triage` over MCP,
|
|
123
|
+
and an `AGENTS.md` that tells your agent the one rule that matters: **never delete
|
|
124
|
+
something because it's `uncertain`.**
|
|
125
|
+
|
|
126
|
+
## What it can't do
|
|
127
|
+
|
|
128
|
+
Written down because a tool that hides its blind spots is worse than no tool:
|
|
129
|
+
|
|
130
|
+
- **Django + vanilla JS.** No React, Vue, or TypeScript yet.
|
|
131
|
+
- **Celery, Redis, WebSockets and Stripe aren't traced.** Anything reached only through
|
|
132
|
+
those is invisible, and the UI says so rather than showing a confident zero.
|
|
133
|
+
- **A URL built at runtime** stays `uncertain`. The prefix is recorded, never a guess.
|
|
134
|
+
- **It has been run against one real project.** Mine. That's one more than most tools at
|
|
135
|
+
this stage and far fewer than you'd want.
|
|
136
|
+
|
|
137
|
+
## Contributing
|
|
138
|
+
|
|
139
|
+
Issues and PRs welcome. One house rule, and it's the reason the tool is worth anything:
|
|
140
|
+
**never make a claim the scan can't evidence.** If you can't prove it, it's `uncertain`,
|
|
141
|
+
and the note says which evidence source was missing.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Paths here are relative to the repo root this file sits at AFTER extraction
|
|
2
|
+
# (see PACKAGING.md). Inside the pointlessbutton repo this file is metadata only:
|
|
3
|
+
# nothing builds it, and the host project's own pyproject.toml is untouched.
|
|
4
|
+
|
|
5
|
+
[build-system]
|
|
6
|
+
requires = ["hatchling"]
|
|
7
|
+
build-backend = "hatchling.build"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "seamcheck"
|
|
11
|
+
version = "0.1.0"
|
|
12
|
+
description = "Find the code your project no longer connects to - and the connections it only thinks it has."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = "MIT"
|
|
15
|
+
requires-python = ">=3.12"
|
|
16
|
+
authors = [{ name = "dardameiz" }]
|
|
17
|
+
keywords = ["django", "static-analysis", "dead-code", "connectivity", "mcp"]
|
|
18
|
+
# No "License :: OSI Approved ::" classifier: PEP 639 replaced it with the
|
|
19
|
+
# License-Expression field above, and PyPI rejects a distribution carrying both.
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Framework :: Django",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
26
|
+
]
|
|
27
|
+
dependencies = ["django>=5.0"]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
# Model extraction shells out to django-extensions' graph_models --json.
|
|
31
|
+
models = ["django-extensions>=4.0"]
|
|
32
|
+
# Exposes the scan to an agent over MCP.
|
|
33
|
+
mcp = ["mcp>=1.0"]
|
|
34
|
+
# Bundles a Node runtime so `pip install` alone is enough on a machine without one
|
|
35
|
+
# (the pyright precedent). The npm parsers still have to be present - see README.
|
|
36
|
+
node = ["nodejs-wheel-binaries>=20"]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Source = "https://github.com/dardameiz/seamcheck"
|
|
40
|
+
Issues = "https://github.com/dardameiz/seamcheck/issues"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["seamcheck"]
|
|
44
|
+
# The parser bundles are the wheel's whole reason for working: a pip install has no
|
|
45
|
+
# node_modules to resolve acorn or postcss against, so they ship inlined.
|
|
46
|
+
artifacts = ["seamcheck/js_tools/*.bundle.mjs", "seamcheck/css_tools/*.bundle.mjs"]
|
|
47
|
+
# Fixtures, tests and the host project's own triage file are not part of what an
|
|
48
|
+
# installing user needs; they stay in the sdist for contributors.
|
|
49
|
+
exclude = ["seamcheck/tests", "seamcheck/triage.json"]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.sdist]
|
|
52
|
+
include = ["seamcheck", "README.md", "LICENSE"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
target-version = "py312"
|
|
56
|
+
line-length = 120 # matches the host project this is developed inside
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "C4"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
# Django's own runner needs a project; pytest plus conftest.py does not, which is what
|
|
64
|
+
# lets someone clone this repo and run the suite without one.
|
|
65
|
+
testpaths = ["seamcheck/tests"]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Seamcheck — instructions for an AI agent
|
|
2
|
+
|
|
3
|
+
Seamcheck is a static-analysis tool that maps what a Django + JavaScript project connects
|
|
4
|
+
to what: which URLs exist, which views they route to, which `fetch()` calls resolve to
|
|
5
|
+
them, which DOM elements the JavaScript writes, and which CSS rules and design tokens
|
|
6
|
+
anything still references.
|
|
7
|
+
|
|
8
|
+
You are likely reading this because someone wants you to use it before committing, or to
|
|
9
|
+
act on something it reported. Read the four statuses first — the whole tool turns on them.
|
|
10
|
+
|
|
11
|
+
## The four statuses, and the one that matters most
|
|
12
|
+
|
|
13
|
+
| Status | Means | What you should do |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `connected` | Something reaches it, and the evidence is attached | Nothing |
|
|
16
|
+
| `unused` | **Both** sides of the contract are observable and nothing uses it | Safe to investigate for removal |
|
|
17
|
+
| `unresolved` | Something reaches for it and it does not exist | Usually a real bug — fix it |
|
|
18
|
+
| `uncertain` | The scan found **no evidence either way** | **Do not act on it** |
|
|
19
|
+
|
|
20
|
+
**`uncertain` is not a soft `unused`.** It means the scan cannot see the evidence that
|
|
21
|
+
would settle the question — a page URL reached by `<a href>`, a class applied by code the
|
|
22
|
+
extractor does not read. Deleting something because it is `uncertain` is the single most
|
|
23
|
+
damaging thing you can do with this tool's output. If a human asks you to "clean up
|
|
24
|
+
everything Seamcheck found", ask them whether they mean the `unused` and `unresolved`
|
|
25
|
+
findings, and say plainly that `uncertain` is not a finding.
|
|
26
|
+
|
|
27
|
+
Every symbol carries the file, the line, and the snippet that produced it. **Quote that
|
|
28
|
+
evidence when you report a finding.** A claim without its snippet is not actionable.
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python manage.py seamcheck # scan + human summary
|
|
34
|
+
python manage.py seamcheck --check # exit 1 on blocking findings
|
|
35
|
+
python manage.py seamcheck --check --format markdown # digest AND exit code
|
|
36
|
+
python manage.py seamcheck --format markdown # digest for a chat or PR
|
|
37
|
+
python manage.py seamcheck --format map # the UI: the map plus the
|
|
38
|
+
# review sections, one file
|
|
39
|
+
python manage.py seamcheck --format map --since REF # what changed
|
|
40
|
+
python manage.py seamcheck --format map --serve # open it from a phone on this wifi
|
|
41
|
+
python manage.py seamcheck --format map --serve --tunnel # ... or off it
|
|
42
|
+
python manage.py seamcheck --backfill 20 # scan the last 20 commits, so the
|
|
43
|
+
# map's commit picker has history
|
|
44
|
+
python manage.py seamcheck --backfill 20 --backfill-ref development
|
|
45
|
+
python manage.py seamcheck --format json # the whole graph
|
|
46
|
+
python manage.py seamcheck --explain <symbol-id> # one symbol's evidence
|
|
47
|
+
python manage.py seamcheck --triage <symbol-id> --status approved --reason "..."
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A full scan takes roughly 15 seconds. `--check` is the CI gate: it exits 1 only on
|
|
51
|
+
`unresolved`/`unused` findings that are untriaged or explicitly confirmed, and never on
|
|
52
|
+
`uncertain`.
|
|
53
|
+
|
|
54
|
+
## Over MCP
|
|
55
|
+
|
|
56
|
+
If the MCP server is attached, prefer these over shelling out:
|
|
57
|
+
|
|
58
|
+
- `seamcheck_check(repo_root)` — `{passed, new_unresolved, new_unused, triage_invalidated, counts}`
|
|
59
|
+
- `seamcheck_report(fmt, repo_root)` — the rendered digest (`terminal`, `markdown`, `html`)
|
|
60
|
+
- `seamcheck_explain(symbol_id, repo_root)` — one symbol's evidence as markdown
|
|
61
|
+
- `seamcheck_triage(symbol_id, status, repo_root, reason)` — record a disposition
|
|
62
|
+
|
|
63
|
+
## The workflow you are probably being asked to run
|
|
64
|
+
|
|
65
|
+
1. `--check` before the commit. If it exits 0, say so in one line and stop.
|
|
66
|
+
2. If it exits 1, `--explain` each reported id and decide, per finding:
|
|
67
|
+
- **a real defect** → fix it, quoting the evidence in your explanation
|
|
68
|
+
- **correct detection, deliberate code** → `--triage <id> --status approved --reason "..."`
|
|
69
|
+
- **the tool is wrong** → say so and why; that is a bug report worth making
|
|
70
|
+
3. A `triage invalidated` line means a human dispositioned that finding earlier and the
|
|
71
|
+
underlying evidence has since changed. **Re-read the code before re-approving it.**
|
|
72
|
+
|
|
73
|
+
## Known gaps — do not over-trust these
|
|
74
|
+
|
|
75
|
+
The tool is honest about what it cannot see, and so should you be:
|
|
76
|
+
|
|
77
|
+
- **CSS selectors reported `uncertain`** are not dead code. Class-applying JavaScript is
|
|
78
|
+
read (`className`, `classList`, `setAttribute`, markup strings), but coverage is not
|
|
79
|
+
proven complete.
|
|
80
|
+
- **Response fields** are matched one view against a whole consuming module, so a field can
|
|
81
|
+
be proven read but never proven unread.
|
|
82
|
+
- **WebSocket payloads, Celery tasks, Redis keys and Stripe hooks are not traced at all.**
|
|
83
|
+
Anything reached only through those is invisible to the scan.
|
|
84
|
+
- **A commit that changed no connection reports nothing.** The graph holds URLs, views,
|
|
85
|
+
calls, selectors and tokens - not values. A commit that adds an entry to a constant
|
|
86
|
+
correctly shows no change; that is not the picker failing.
|
|
87
|
+
- **Selectors and fetch targets built at runtime** are `uncertain` by design, never guessed.
|
|
88
|
+
|
|
89
|
+
## Rules for you specifically
|
|
90
|
+
|
|
91
|
+
- Never delete code because a symbol is `uncertain`.
|
|
92
|
+
- Never report a finding without its `file:line` and snippet.
|
|
93
|
+
- Never claim the scan is clean because a command exited 0 — check that it actually ran and
|
|
94
|
+
what it counted.
|
|
95
|
+
- If you are asked to fix a multi-writer element, the fix is to pick one canonical owner and
|
|
96
|
+
route the others through it, not to patch the writer you happened to find first.
|
|
File without changes
|