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.
Files changed (124) hide show
  1. seamcheck-0.1.0/.gitignore +10 -0
  2. seamcheck-0.1.0/LICENSE +21 -0
  3. seamcheck-0.1.0/PKG-INFO +170 -0
  4. seamcheck-0.1.0/README.md +145 -0
  5. seamcheck-0.1.0/pyproject.toml +65 -0
  6. seamcheck-0.1.0/seamcheck/AGENTS.md +96 -0
  7. seamcheck-0.1.0/seamcheck/__init__.py +0 -0
  8. seamcheck-0.1.0/seamcheck/api.py +315 -0
  9. seamcheck-0.1.0/seamcheck/apps.py +21 -0
  10. seamcheck-0.1.0/seamcheck/attribution.py +45 -0
  11. seamcheck-0.1.0/seamcheck/classifier.py +116 -0
  12. seamcheck-0.1.0/seamcheck/console.py +150 -0
  13. seamcheck-0.1.0/seamcheck/coverage.py +38 -0
  14. seamcheck-0.1.0/seamcheck/css_tools/parse_css.bundle.mjs +6192 -0
  15. seamcheck-0.1.0/seamcheck/css_tools/parse_css.mjs +41 -0
  16. seamcheck-0.1.0/seamcheck/diff.py +79 -0
  17. seamcheck-0.1.0/seamcheck/dom_matcher.py +145 -0
  18. seamcheck-0.1.0/seamcheck/extractors/__init__.py +0 -0
  19. seamcheck-0.1.0/seamcheck/extractors/asgi_extractor.py +79 -0
  20. seamcheck-0.1.0/seamcheck/extractors/css_extractor.py +139 -0
  21. seamcheck-0.1.0/seamcheck/extractors/django_extractor.py +106 -0
  22. seamcheck-0.1.0/seamcheck/extractors/django_models_extractor.py +77 -0
  23. seamcheck-0.1.0/seamcheck/extractors/dom_js_extractor.py +337 -0
  24. seamcheck-0.1.0/seamcheck/extractors/entry_points_extractor.py +114 -0
  25. seamcheck-0.1.0/seamcheck/extractors/js_extractor.py +378 -0
  26. seamcheck-0.1.0/seamcheck/extractors/reachability.py +96 -0
  27. seamcheck-0.1.0/seamcheck/extractors/template_scanner.py +84 -0
  28. seamcheck-0.1.0/seamcheck/field_matcher.py +206 -0
  29. seamcheck-0.1.0/seamcheck/filetree.py +111 -0
  30. seamcheck-0.1.0/seamcheck/graph.py +124 -0
  31. seamcheck-0.1.0/seamcheck/history.py +289 -0
  32. seamcheck-0.1.0/seamcheck/js_tools/parse_js.bundle.mjs +5679 -0
  33. seamcheck-0.1.0/seamcheck/js_tools/parse_js.mjs +34 -0
  34. seamcheck-0.1.0/seamcheck/management/__init__.py +0 -0
  35. seamcheck-0.1.0/seamcheck/management/commands/__init__.py +0 -0
  36. seamcheck-0.1.0/seamcheck/management/commands/seamcheck.py +256 -0
  37. seamcheck-0.1.0/seamcheck/mapdata.py +274 -0
  38. seamcheck-0.1.0/seamcheck/matcher.py +84 -0
  39. seamcheck-0.1.0/seamcheck/mcp_server.py +37 -0
  40. seamcheck-0.1.0/seamcheck/nodetools.py +56 -0
  41. seamcheck-0.1.0/seamcheck/pagenames.py +143 -0
  42. seamcheck-0.1.0/seamcheck/pipeline.py +185 -0
  43. seamcheck-0.1.0/seamcheck/renderers/__init__.py +0 -0
  44. seamcheck-0.1.0/seamcheck/renderers/_shared.py +29 -0
  45. seamcheck-0.1.0/seamcheck/renderers/html.py +182 -0
  46. seamcheck-0.1.0/seamcheck/renderers/map_html.py +1115 -0
  47. seamcheck-0.1.0/seamcheck/renderers/markdown.py +73 -0
  48. seamcheck-0.1.0/seamcheck/renderers/terminal.py +61 -0
  49. seamcheck-0.1.0/seamcheck/report.py +167 -0
  50. seamcheck-0.1.0/seamcheck/roots.py +91 -0
  51. seamcheck-0.1.0/seamcheck/serve.py +106 -0
  52. seamcheck-0.1.0/seamcheck/snapshot.py +37 -0
  53. seamcheck-0.1.0/seamcheck/tests/__init__.py +0 -0
  54. seamcheck-0.1.0/seamcheck/tests/fixtures/__init__.py +0 -0
  55. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_admin.py +8 -0
  56. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_asgi.py +23 -0
  57. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom.css +18 -0
  58. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_imported.css +0 -0
  59. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_multiwriter_a.js +3 -0
  60. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_multiwriter_b.js +18 -0
  61. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_single_writer.js +11 -0
  62. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_dom_template.html +5 -0
  63. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_entry.js +5 -0
  64. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_graph_models_sample.json +156 -0
  65. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_included_urls.py +7 -0
  66. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_module.js +25 -0
  67. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_page_entry_a.js +3 -0
  68. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_reachability_a.py +5 -0
  69. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_reachability_b.py +2 -0
  70. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_reachability_root.py +7 -0
  71. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_relative_importer.py +5 -0
  72. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_signals.py +12 -0
  73. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_static_js_template.html +2 -0
  74. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_string_reference.py +5 -0
  75. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_style.css +0 -0
  76. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_tailwind_build_output.css +4 -0
  77. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_template_tags.py +18 -0
  78. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_unreachable.py +2 -0
  79. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_urls.py +11 -0
  80. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_views.py +13 -0
  81. seamcheck-0.1.0/seamcheck/tests/fixtures/fixture_vite.config.js +13 -0
  82. seamcheck-0.1.0/seamcheck/tests/test_app_config.py +32 -0
  83. seamcheck-0.1.0/seamcheck/tests/test_asgi_extractor.py +45 -0
  84. seamcheck-0.1.0/seamcheck/tests/test_attribution.py +79 -0
  85. seamcheck-0.1.0/seamcheck/tests/test_classifier.py +49 -0
  86. seamcheck-0.1.0/seamcheck/tests/test_cli.py +256 -0
  87. seamcheck-0.1.0/seamcheck/tests/test_console.py +68 -0
  88. seamcheck-0.1.0/seamcheck/tests/test_coverage.py +38 -0
  89. seamcheck-0.1.0/seamcheck/tests/test_css_extractor.py +176 -0
  90. seamcheck-0.1.0/seamcheck/tests/test_css_recall_gaps.py +112 -0
  91. seamcheck-0.1.0/seamcheck/tests/test_diff.py +101 -0
  92. seamcheck-0.1.0/seamcheck/tests/test_django_extractor.py +86 -0
  93. seamcheck-0.1.0/seamcheck/tests/test_django_models_extractor.py +66 -0
  94. seamcheck-0.1.0/seamcheck/tests/test_dom_js_extractor.py +75 -0
  95. seamcheck-0.1.0/seamcheck/tests/test_dom_matcher.py +170 -0
  96. seamcheck-0.1.0/seamcheck/tests/test_entry_points_extractor.py +57 -0
  97. seamcheck-0.1.0/seamcheck/tests/test_field_matcher.py +123 -0
  98. seamcheck-0.1.0/seamcheck/tests/test_filetree.py +59 -0
  99. seamcheck-0.1.0/seamcheck/tests/test_graph.py +104 -0
  100. seamcheck-0.1.0/seamcheck/tests/test_history.py +184 -0
  101. seamcheck-0.1.0/seamcheck/tests/test_js_class_usage.py +160 -0
  102. seamcheck-0.1.0/seamcheck/tests/test_js_extractor.py +78 -0
  103. seamcheck-0.1.0/seamcheck/tests/test_mapdata.py +149 -0
  104. seamcheck-0.1.0/seamcheck/tests/test_matcher.py +102 -0
  105. seamcheck-0.1.0/seamcheck/tests/test_mcp_server.py +60 -0
  106. seamcheck-0.1.0/seamcheck/tests/test_nodetools.py +74 -0
  107. seamcheck-0.1.0/seamcheck/tests/test_pagenames.py +143 -0
  108. seamcheck-0.1.0/seamcheck/tests/test_pipeline_dom_css_regression.py +89 -0
  109. seamcheck-0.1.0/seamcheck/tests/test_pipeline_fixture_regression.py +93 -0
  110. seamcheck-0.1.0/seamcheck/tests/test_reachability.py +49 -0
  111. seamcheck-0.1.0/seamcheck/tests/test_renderer_html.py +249 -0
  112. seamcheck-0.1.0/seamcheck/tests/test_renderer_map.py +472 -0
  113. seamcheck-0.1.0/seamcheck/tests/test_renderer_markdown.py +105 -0
  114. seamcheck-0.1.0/seamcheck/tests/test_renderer_shared.py +49 -0
  115. seamcheck-0.1.0/seamcheck/tests/test_renderer_terminal.py +115 -0
  116. seamcheck-0.1.0/seamcheck/tests/test_report.py +174 -0
  117. seamcheck-0.1.0/seamcheck/tests/test_report_end_to_end.py +106 -0
  118. seamcheck-0.1.0/seamcheck/tests/test_roots.py +66 -0
  119. seamcheck-0.1.0/seamcheck/tests/test_serve.py +70 -0
  120. seamcheck-0.1.0/seamcheck/tests/test_snapshot.py +41 -0
  121. seamcheck-0.1.0/seamcheck/tests/test_template_scanner.py +61 -0
  122. seamcheck-0.1.0/seamcheck/tests/test_triage.py +123 -0
  123. seamcheck-0.1.0/seamcheck/triage.json +3 -0
  124. seamcheck-0.1.0/seamcheck/triage.py +126 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ node_modules/
9
+ .ruff_cache/
10
+ .pytest_cache/
@@ -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.
@@ -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