feat(vault): add task workflow
This commit is contained in:
@@ -1,304 +0,0 @@
|
||||
# Deadnix Architecture
|
||||
|
||||
## Purpose
|
||||
|
||||
This note explains how `deadnix` finds unused Nix code.
|
||||
It focuses on architecture, not whether each check belongs in our linter.
|
||||
|
||||
Use this note when deciding whether our linter needs scope analysis.
|
||||
|
||||
## What Deadnix Is
|
||||
|
||||
`deadnix` is a dead-code scanner for Nix.
|
||||
It finds bindings that introduce names which no live code uses.
|
||||
|
||||
It is not a general style linter.
|
||||
It does not have many independent rules.
|
||||
It implements one analysis family: unused declarations.
|
||||
|
||||
Like `statix`, it parses source text with `rnix`.
|
||||
It does not evaluate Nix.
|
||||
It does not import files.
|
||||
It reasons over local syntax and local scope only.
|
||||
|
||||
## Minimal Dead-Code Vocabulary
|
||||
|
||||
- A binding introduces a name.
|
||||
- A scope defines where introduced names are visible.
|
||||
- A use is an identifier occurrence that refers to a binding.
|
||||
- Shadowing happens when an inner scope reuses an outer name.
|
||||
- Dead code is a binding that no live code uses.
|
||||
- A fixed point is the final result after repeated analysis no longer changes.
|
||||
|
||||
Dead-code analysis needs more state than a simple syntax lint.
|
||||
It must know which names a scope introduces and where those names can be used.
|
||||
|
||||
## Component Map
|
||||
|
||||
`deadnix` has fewer layers than `statix`.
|
||||
The command-line entry point, file walk, parsing, analysis, reporting, and editing live in one small tool.
|
||||
|
||||
The important concepts are separate modules:
|
||||
|
||||
- settings control analysis options;
|
||||
- scopes describe where names are introduced;
|
||||
- bindings describe individual declared names;
|
||||
- usage search answers whether a name appears in a live body;
|
||||
- dead-code results connect a binding to its scope;
|
||||
- reports render findings;
|
||||
- edits remove or rename unused declarations.
|
||||
|
||||
## Main Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Parse CLI options] --> B[Collect input files]
|
||||
B --> C[Read each file]
|
||||
C --> D[Parse with rnix]
|
||||
D --> E[Skip file on parse errors]
|
||||
D --> F[Run dead-code analysis]
|
||||
F --> G[Print reports unless quiet]
|
||||
G --> H[Apply edits when --edit is set]
|
||||
H --> I[Exit 1 when --fail and findings exist]
|
||||
```
|
||||
|
||||
The CLI accepts files or directories.
|
||||
Directory mode walks `.nix` files.
|
||||
Hidden files require an explicit option.
|
||||
Excludes filter paths before analysis.
|
||||
|
||||
The tool skips dead-code diagnostics when a file has parse errors.
|
||||
It reports parse errors to the user and continues with other files.
|
||||
|
||||
## Analysis Settings
|
||||
|
||||
The analysis has four important switches.
|
||||
|
||||
`no_lambda_arg` disables checks for single lambda arguments.
|
||||
This can avoid noise when an API requires an argument name.
|
||||
|
||||
`no_lambda_pattern_names` disables checks for names in attribute-set function patterns.
|
||||
This matters for `callPackage`-style code, where argument names can be external API.
|
||||
|
||||
`no_underscore` ignores bindings that start with `_`.
|
||||
This treats `_name` as an intentional unused placeholder.
|
||||
|
||||
`warn_used_underscore` reports underscore-prefixed bindings that code actually uses.
|
||||
This enforces the convention that `_name` means unused.
|
||||
|
||||
These settings are architecture-relevant because dead-code policy depends on API context.
|
||||
A repository linter needs a way to express that context.
|
||||
|
||||
## Scope Model
|
||||
|
||||
`deadnix` recognizes four scope types.
|
||||
|
||||
### Lambda pattern scope
|
||||
|
||||
A lambda pattern is a function argument written as an attribute set pattern.
|
||||
It can introduce field names and an optional alias for the whole argument set.
|
||||
|
||||
Example shape:
|
||||
|
||||
```nix
|
||||
args @ { pkgs, lib, ... }: body
|
||||
```
|
||||
|
||||
This scope can introduce `args`, `pkgs`, and `lib`.
|
||||
|
||||
### Lambda argument scope
|
||||
|
||||
A single lambda argument introduces one name.
|
||||
|
||||
Example shape:
|
||||
|
||||
```nix
|
||||
pkgs: body
|
||||
```
|
||||
|
||||
This scope introduces `pkgs`.
|
||||
|
||||
### Let scope
|
||||
|
||||
A `let` expression introduces bindings and inherited names.
|
||||
Those names are available in binding expressions and in the body.
|
||||
|
||||
Example shape:
|
||||
|
||||
```nix
|
||||
let
|
||||
inherit (pkgs) hello;
|
||||
value = hello;
|
||||
in
|
||||
value
|
||||
```
|
||||
|
||||
This scope introduces `hello` and `value`.
|
||||
|
||||
### Recursive attribute-set scope
|
||||
|
||||
A `rec` attribute set introduces names that sibling attributes can reference.
|
||||
`deadnix` models this scope, but treats its bindings as non-removable.
|
||||
|
||||
That conservative choice avoids deleting public-looking attributes from recursive sets.
|
||||
|
||||
## Binding Model
|
||||
|
||||
A binding stores three facts.
|
||||
|
||||
It stores the identifier name.
|
||||
It stores the syntax node that declared the name.
|
||||
It stores whether the binding is mortal.
|
||||
|
||||
A mortal binding can become a dead-code finding.
|
||||
A non-mortal binding participates in name lookup but will not be removed.
|
||||
|
||||
Single lambda arguments that already start with `_` are non-mortal.
|
||||
Recursive attribute-set bindings are also non-mortal.
|
||||
|
||||
A binding also checks for the `deadnix: skip` pragma.
|
||||
The pragma applies to a nearby following declaration.
|
||||
The scanner searches backward from the declaration and stops after a blank-line boundary.
|
||||
|
||||
## Usage Search
|
||||
|
||||
Usage search asks whether a given name appears inside a syntax subtree.
|
||||
|
||||
The search is scope-aware.
|
||||
When it enters a child scope that binds the same name, it stops following that branch.
|
||||
That prevents an inner binding from counting as a use of an outer binding.
|
||||
|
||||
Attribute paths get special handling.
|
||||
Ordinary attribute keys do not count as uses.
|
||||
Dynamic or string attribute names can contain expressions, so the search looks inside those parts.
|
||||
|
||||
`inherit` also gets special handling.
|
||||
A scope can inherit names from an outer scope.
|
||||
The analysis checks whether the inherited expression uses the searched name.
|
||||
|
||||
## Fixed-Point Analysis
|
||||
|
||||
`deadnix` repeats the scan until the result set stops changing.
|
||||
|
||||
This matters because dead code can depend on other dead code.
|
||||
A binding can appear used during the first pass because another binding references it.
|
||||
If the referring binding is dead, the referenced binding is also dead.
|
||||
|
||||
The loop tracks dead declaration nodes.
|
||||
During later scans, uses inside already-dead bodies do not keep other bindings alive.
|
||||
|
||||
This fixed-point loop is the central architectural difference from simple local syntax lints.
|
||||
It makes dead-code results transitive.
|
||||
|
||||
## Reporting Model
|
||||
|
||||
A result stores the scope and binding for one finding.
|
||||
The display text combines both parts.
|
||||
Examples include unused lambda pattern, unused lambda argument, and unused let binding.
|
||||
|
||||
Human output uses `ariadne`.
|
||||
The reporter groups ranges by line and labels the exact binding names.
|
||||
It uses different colors for different scope kinds.
|
||||
|
||||
JSON output exists behind a feature flag.
|
||||
It emits file, message, line, column, and end column.
|
||||
|
||||
`NO_COLOR` disables colored human output.
|
||||
|
||||
## Edit Model
|
||||
|
||||
`deadnix --edit` converts findings into text edits.
|
||||
Each edit has a start offset, end offset, and replacement string.
|
||||
|
||||
The edit behavior depends on scope kind.
|
||||
|
||||
For unused lambda pattern names, it removes the name and nearby comma or whitespace.
|
||||
For unused pattern aliases, it removes the alias syntax.
|
||||
For unused lambda arguments, it renames the argument with an underscore prefix.
|
||||
For unused `let` assignments, it deletes the assignment.
|
||||
For unused inherited names, it deletes only the inherited name.
|
||||
|
||||
After those edits, it parses the edited source again.
|
||||
Then it removes empty `let in` constructs and empty `inherit` statements.
|
||||
|
||||
This cleanup pass prevents the first edit batch from leaving invalid or useless scaffolding.
|
||||
|
||||
## Error Handling
|
||||
|
||||
If a path cannot be stated or read, the CLI reports the file error and continues.
|
||||
If a file has parse errors, the CLI reports those errors and skips dead-code analysis for that file.
|
||||
|
||||
The `--fail` flag changes exit status when findings exist.
|
||||
Without `--fail`, findings do not make the command fail.
|
||||
|
||||
This design separates report generation from CI policy.
|
||||
|
||||
## Architectural Strengths
|
||||
|
||||
The scope model is explicit.
|
||||
Each scope type declares which bindings it introduces and where those bindings are visible.
|
||||
|
||||
The fixed-point loop catches transitive dead code.
|
||||
A binding used only by dead code also becomes dead.
|
||||
|
||||
The usage search handles shadowing.
|
||||
That prevents common false positives in nested functions and nested `let` expressions.
|
||||
|
||||
The edit model is tied to analysis results.
|
||||
It knows whether a binding came from a lambda, pattern, `let`, or `inherit`.
|
||||
|
||||
The tool has few moving parts.
|
||||
That makes the analysis easy to follow compared to a general multi-rule linter.
|
||||
|
||||
## Architectural Limits
|
||||
|
||||
`deadnix` does not evaluate Nix.
|
||||
It cannot know which names external callers depend on.
|
||||
This matters for module arguments, package function arguments, and public attribute sets.
|
||||
|
||||
It has global switches rather than project-specific rule contexts.
|
||||
For example, all lambda pattern names can be checked or skipped.
|
||||
The tool does not know that one directory contains `callPackage` functions.
|
||||
|
||||
The analysis is local to one file.
|
||||
It cannot see that another file imports a function and relies on its argument names.
|
||||
|
||||
The fixer performs text edits with byte ranges.
|
||||
It must handle punctuation and whitespace carefully.
|
||||
Pattern edits are harder than simple assignment deletion.
|
||||
|
||||
The tool has one analysis family.
|
||||
It is not a framework for many unrelated rules.
|
||||
|
||||
## Lessons for Our Linter
|
||||
|
||||
Dead-code checks need a scope pass.
|
||||
A per-node rule interface is not enough when a diagnostic depends on name visibility.
|
||||
|
||||
Represent scopes explicitly.
|
||||
Each scope type should provide introduced bindings and visible bodies.
|
||||
|
||||
Track shadowing before reporting unused names.
|
||||
A text search for an identifier is not sufficient.
|
||||
|
||||
Use fixed-point analysis for transitive dead code.
|
||||
One pass misses names used only by dead declarations.
|
||||
|
||||
Separate policy from mechanics.
|
||||
The mechanics can find unused names.
|
||||
Repository policy decides whether module arguments, package arguments, or underscore names count.
|
||||
|
||||
Treat autofix as scope-specific.
|
||||
Removing a pattern name is not the same operation as deleting a `let` assignment.
|
||||
|
||||
Run cleanup after edits.
|
||||
Dead-code fixes can create empty `let` expressions and empty `inherit` statements.
|
||||
|
||||
## Open Design Questions for Our Linter
|
||||
|
||||
- Should dead-code analysis be part of `nix-style-linter`, or a separate command?
|
||||
- Should policy vary by path, file kind, or suppression comment?
|
||||
- Should recursive attribute-set members ever become removable findings?
|
||||
- Should underscore names mean ignored, or should used underscore names be errors?
|
||||
- Should the linter report unused names without offering autofix first?
|
||||
- Should cleanup rules run as ordinary rules or as a post-fix pass?
|
||||
@@ -1,102 +0,0 @@
|
||||
# Hyprland Lua Migration Plan
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The Hyprland compositor config uses Home Manager's hyprlang output.
|
||||
Hyprland deprecates hyprlang for the main compositor config.
|
||||
The repository needs to generate a Lua config without changing desktop behavior.
|
||||
|
||||
## Solution
|
||||
|
||||
Keep the config owned by Nix and Home Manager.
|
||||
Switch the Hyprland Home Manager config output to Lua.
|
||||
Convert only the settings whose shape changes under Lua.
|
||||
|
||||
This is not a desktop redesign.
|
||||
The change preserves the current monitors, binds, window rules, environment variables, and UWSM startup.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. As the operator, I want Hyprland to load `hyprland.lua`, so that startup no longer warns about hyprlang deprecation.
|
||||
|
||||
2. As the operator, I want existing keyboard binds to keep working, so that the desktop behavior stays unchanged.
|
||||
|
||||
3. As the operator, I want host monitor layout to stay unchanged, so that the physical display setup does not move.
|
||||
|
||||
4. As the operator, I want session environment variables to stay in the compositor config, so that Wayland and cursor behavior stays unchanged.
|
||||
|
||||
5. As the operator, I want auxiliary module binds to keep working, so that launcher, lock, screenshot, clipboard, notifications, and recording flows stay usable.
|
||||
|
||||
## Implementation Decisions
|
||||
|
||||
1. Use Home Manager's Lua config generator.
|
||||
Set the Hyprland config type to Lua and keep package ownership unchanged.
|
||||
|
||||
2. Keep ordinary compositor options declarative in Nix.
|
||||
Move input, general, decoration, xwayland, animation, and layout settings into the Lua config structure.
|
||||
|
||||
3. Remove hyprlang-only variables from generated config.
|
||||
Keep modifier and terminal values as Nix locals or helper inputs.
|
||||
|
||||
4. Add minimal local helpers for Lua calls.
|
||||
Use helpers only for repeated bind, dispatcher, command, environment, and rule shapes.
|
||||
|
||||
5. Convert all bind strings to Lua bind call data.
|
||||
Preserve key combinations, dispatchers, commands, repeat behavior, and locked-session behavior.
|
||||
|
||||
6. Convert contributed binds in their owning modules.
|
||||
Keep each bind near the module that owns the tool it launches.
|
||||
|
||||
7. Convert monitor strings to Lua monitor records.
|
||||
Preserve output names, modes, positions, scale values, and rotation.
|
||||
|
||||
8. Convert the screenshot window rule to the Lua rule shape.
|
||||
Preserve the Satty class matcher and floating behavior.
|
||||
|
||||
9. Convert environment strings to Lua environment calls.
|
||||
Preserve the Wayland flag and cursor variables.
|
||||
|
||||
10. Delete or update comments that describe hyprlang syntax.
|
||||
Keep comments that explain behavior or non-obvious invariants.
|
||||
|
||||
## Testing Decisions
|
||||
|
||||
1. Run the repository Nix formatting and style checks for modified Nix files.
|
||||
|
||||
2. Evaluate or build the affected host configuration.
|
||||
This proves Home Manager accepts the Lua settings structure.
|
||||
|
||||
3. Confirm the generated config file uses Lua.
|
||||
The expected output is `hyprland.lua`, not `hyprland.conf`.
|
||||
|
||||
4. Verify Hyprland accepts the generated Lua config.
|
||||
Use Hyprland config verification when the installed build supports it.
|
||||
Otherwise reload the compositor and inspect errors.
|
||||
|
||||
5. Smoke test the changed desktop surface.
|
||||
Test one bind from each source area after reload.
|
||||
|
||||
6. Check the explicit monitor layout on the host that declares it.
|
||||
Confirm resolution, position, scale, and rotation stayed unchanged.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Do not split the config into Lua modules.
|
||||
- Do not hand-write a full Lua config.
|
||||
- Do not add new Hyprland behavior.
|
||||
- Do not change UWSM startup.
|
||||
- Do not redesign binds, monitor layout, or visual settings.
|
||||
- Do not change hyprlock, hypridle, waybar, mako, or rofi settings beyond their Hyprland binds.
|
||||
|
||||
## Further Notes
|
||||
|
||||
Home Manager documents `configType = "lua"` for Hyprland.
|
||||
It maps settings to `hl.*` calls.
|
||||
It supports `_args` for multi-argument calls.
|
||||
It supports raw Lua expressions with `lib.generators.mkLuaInline`.
|
||||
|
||||
The riskiest parts are binds, monitor declarations, and window rules.
|
||||
Those parts changed shape more than scalar config sections.
|
||||
|
||||
Assumption: the pinned Home Manager input contains the documented Lua Hyprland generator.
|
||||
Validate this during the configuration build.
|
||||
Reference in New Issue
Block a user