-
Notifications
You must be signed in to change notification settings - Fork 0
Adopt Skylos for dead-code detection #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
3a15793
Plan dead-code detector benchmark
e7ad321
Record dead-code scanner smoke results
50f5c44
Add dead-code benchmark scorer
a58018d
Add labelled dead-code benchmark corpus
190f299
Capture dead-code detector results
c982c2a
Document dead-code detector comparison
7db20e2
Use Oxford spelling for analyser
e2c1a1c
Adopt Skylos for dead-code linting
bee6e86
Document Skylos guidance for agents
77c27f7
Refresh Skylos lock after rebase
af30bbc
Refactor dead-code benchmark scoring
2279213
Clarify dead-code scoring helpers
dd6c4cc
Load Skylos configuration explicitly
2575d86
Document Skylos runtime entry points
4851fbf
Classify Skylos method entry points
48de8d3
Clarify Skylos suppression guidance
97f6bf2
Address review findings after rebase
leynos 51771fb
Isolate the production Skylos dead-code graph
leynos 9c3e344
Align tests with the DF12 lint suite
leynos 5c5ebc3
Tighten Skylos review contracts
leynos 3727517
Add scorer property tests
leynos 66855a9
Document dead-code benchmark fixtures
leynos aa0d5c9
Document Makefile validation check
leynos 189f86e
Explain dead-code benchmark suppressions
leynos 8a03821
Clarify benchmark removal boundary
leynos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Reproducible evaluation artefacts for Episodic development tools.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Dead-code detector benchmark | ||
|
|
||
| This directory contains the reusable, tool-neutral corpus and normalizer for | ||
| the pyscn and Skylos comparison. It is development evidence, not part of the | ||
| Episodic application or its test fixtures. | ||
|
|
||
| `corpus/` is a deliberately small Python project. Its own `pyproject.toml` | ||
| bounds project-root discovery without configuring either detector. The source | ||
| contains intentional unused and unreachable code, so each corpus module uses a | ||
| file-level Ruff suppression to keep repository lint separate from benchmark | ||
| ground truth. | ||
|
|
||
| `expectations.json` is the oracle. Each entry labels one source location before | ||
| scanner output is considered, assigns it to either the `unused-symbol` or | ||
| `unreachable-statement` lane, and explains why Python semantics make it live or | ||
| dead. Add a new label only when its liveness can be decided without trusting a | ||
| scanner. Do not change a label merely to make a detector result pass. | ||
|
|
||
| The `score.py` module is intentionally specific to the two released JSON | ||
| schemas captured by this comparison. Reuse it for reruns of this corpus; add a | ||
| separate parser when evaluating a different detector rather than disguising | ||
| schema differences inside an existing parser. | ||
|
|
||
| `results/` retains the tool output, wall-clock metadata, normalized scores, and | ||
| repository-scan adjudication from 2026-07-27. The large repository reports are | ||
| compressed with deterministic gzip metadata; their SHA-256 digests are in | ||
| `production-adjudication.json`. Absolute checkout prefixes in the Skylos report | ||
| were replaced by `./` before compression, so the retained evidence does not | ||
| depend on one workstation path. Finding content was otherwise unchanged. | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| Run the corpus commands from `benchmarks/dead_code/corpus/`: | ||
|
|
||
| ```bash | ||
| uvx pyscn@1.28.0 analyze --select deadcode --min-severity info --json . | ||
| uvx skylos@4.30.0 . --no-upload --no-provenance --confidence 0 --no-grep-verify --format json | ||
| ``` | ||
|
|
||
| Run the practical comparison from the repository root: | ||
|
|
||
| ```bash | ||
| uvx pyscn@1.28.0 analyze --select deadcode --min-severity info --json episodic | ||
| uvx skylos@4.30.0 episodic --no-upload --no-provenance --confidence 0 --no-grep-verify --format json | ||
| ``` | ||
|
|
||
| The elapsed times are single wall-clock observations, not performance | ||
| benchmarks. They are retained to expose order-of-magnitude differences only. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Dead-code detector comparison corpus and scoring support.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| """Public surface for the dead-code detector corpus.""" | ||
|
|
||
| from .symbols import exported_function | ||
|
|
||
| __all__ = ["exported_function"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| # Benchmark source locations are intentionally stable. | ||
| """Dynamic and registered live symbols for false-positive controls.""" | ||
|
|
||
| import typing as typ | ||
|
|
||
|
|
||
| class RegisteredFunction(typ.Protocol): | ||
| """Describe a zero-argument callable that can enter the registry. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| __name__ : str | ||
| Name used as the registry key. | ||
| """ | ||
|
|
||
| __name__: str | ||
|
|
||
| def __call__(self) -> int: | ||
| """Invoke the registered callable. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| Result produced by the callable. | ||
| """ | ||
| ... | ||
|
|
||
|
|
||
| REGISTRY: dict[str, RegisteredFunction] = {} | ||
|
|
||
|
|
||
| def register(function: RegisteredFunction) -> RegisteredFunction: | ||
| """Store a callable under its declared name. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| function : RegisteredFunction | ||
| Callable to add to the registry. | ||
|
|
||
| Returns | ||
| ------- | ||
| RegisteredFunction | ||
| The same callable passed in ``function``. | ||
| """ | ||
| REGISTRY[function.__name__] = function | ||
| return function | ||
|
|
||
|
|
||
| @register | ||
| def registered_plugin() -> int: | ||
| """Return the result exposed by the registered plugin. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| Constant plugin result. | ||
| """ | ||
| return 29 | ||
|
|
||
|
|
||
| class DynamicHandler: | ||
| """Provide a method resolved through dynamic attribute lookup.""" | ||
|
|
||
| def invoked_by_name(self) -> int: # noqa: PLR6301 - dynamic getattr requires an instance method. | ||
| """Return the result of the dynamically selected method. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| Constant handler result. | ||
| """ | ||
| return 31 | ||
|
|
||
|
|
||
| class CallableHandler: | ||
| """Provide a callable object for invocation-based discovery.""" | ||
|
|
||
| def __call__(self) -> int: | ||
| """Return the result produced when the handler is called. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| Constant handler result. | ||
| """ | ||
| return 37 | ||
|
|
||
|
|
||
| DYNAMIC_RESULT = getattr(DynamicHandler(), "invoked_by_name")() # noqa: B009 | ||
| REGISTERED_RESULT = REGISTRY["registered_plugin"]() | ||
| CALLABLE_RESULT = CallableHandler()() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # Benchmark source locations are intentionally stable. | ||
| """Reachable and unreachable statements for control-flow comparison.""" | ||
|
|
||
| EXPECTED_BENCHMARK_EXCEPTION_MESSAGE = "expected benchmark exception" | ||
|
|
||
|
|
||
| def after_return() -> int: # noqa: RET503 - preserve the required return-unreachability fixture. | ||
| """Return the fixed benchmark value. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The fixed benchmark value. | ||
| """ | ||
| return 41 | ||
| unreachable_after_return = 43 # noqa: F841 - retain the return-unreachability fixture. | ||
|
|
||
|
|
||
| def after_raise() -> None: | ||
| """Raise the expected benchmark exception. | ||
|
|
||
| Raises | ||
| ------ | ||
| ValueError | ||
| Always, as required to exercise the unreachable assignment. | ||
| """ | ||
| raise ValueError(EXPECTED_BENCHMARK_EXCEPTION_MESSAGE) | ||
| unreachable_after_raise = 47 # noqa: F841 - retain the raise-unreachability fixture. | ||
|
|
||
|
|
||
| def after_continue(values: tuple[int, ...]) -> int: | ||
| """Return the number of values after skipping each loop body. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| values : tuple[int, ...] | ||
| Values iterated only to exercise an unreachable assignment. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The number of supplied values. | ||
| """ | ||
| for value in values: | ||
| continue | ||
| unreachable_after_continue = value # noqa: F841 - retain the continue-unreachability fixture. | ||
| return len(values) | ||
|
|
||
|
|
||
| def after_break(values: tuple[int, ...]) -> int: | ||
| """Return the number of values after breaking the loop. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| values : tuple[int, ...] | ||
| Values iterated only to exercise an unreachable assignment. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The number of supplied values. | ||
| """ | ||
| for value in values: | ||
| break | ||
| unreachable_after_break = value # noqa: F841 - retain the break-unreachability fixture. | ||
| return len(values) | ||
|
|
||
|
|
||
| def constant_false_branch() -> int: | ||
| """Return the fixed value after an unreachable branch. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The fixed benchmark value. | ||
| """ | ||
| if False: | ||
| unreachable_false_branch = 53 # noqa: F841 - retain the false-branch fixture. | ||
| return 59 | ||
|
|
||
|
|
||
| def conditional_return(flag: bool) -> int: # noqa: FBT001 - boolean selects the conditional-return fixture. | ||
| """Return the fixed value after the conditional branch. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| flag : bool | ||
| Whether to take the early return branch. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The fixed benchmark value. | ||
| """ | ||
| if flag: | ||
| return 61 | ||
| reachable_after_conditional = 67 | ||
| return reachable_after_conditional # noqa: RET504 - keep the explicit reachable fall-through fixture. | ||
|
|
||
|
|
||
| RETURN_RESULT = after_return() | ||
| try: # noqa: SIM105 | ||
| after_raise() | ||
| except ValueError: | ||
| pass | ||
| CONTINUE_RESULT = after_continue((1, 2)) | ||
| BREAK_RESULT = after_break((3, 4)) | ||
| FALSE_RESULT = constant_false_branch() | ||
| CONDITIONAL_RESULT = conditional_return(False) # noqa: FBT003 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| [project] | ||
| name = "episodic-dead-code-corpus" | ||
| version = "0.0.0" | ||
| requires-python = ">=3.11" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.