Improvement plan (tasks 1-12) + rename default config to licet.toml - #12
Conversation
Audit-driven overhaul from docs/2026-09-10-licet-audit.md per docs/superpowers/2026-09-10-licet-improvements.md: - Engine: git-aware selection (staged/changed/files) via git CLI, stateless scans (drop walk cache, sha2/gix deps), rule precedence with conflict surfacing, OOB REUSE.toml/dep5 detection, drift classification, snippet-aware lint with documented warning reasons - CLI: init preserves observed licensing (exact-path rules, safe overwrite semantics), check/apply/enforce selection flags, add-license offline bundle installs, completions gated on success - Contracts: report v2 schema with hint/warning duality, config schema, CLI exit-code contract; spec + data-model updated alongside - Tests: 390 passing across 19 suites, incl. 24 differential tests against reuse 6.2.0 (comparator-enforced) and perf/soak gates - CI: fmt, clippy -D warnings, test matrix, doctests, MSRV 1.89, release-cycle EPIPE probe, offline dependency guard
docs/REUSE_Specification_v3.3.md is a verbatim copy of the FSFE REUSE Specification, whose upstream reuse-docs repository is licensed CC-BY-SA-4.0 (not CC-BY-4.0). Add the required attribution: FSFE copyright notice, SPDX identifier, source link, and a note that no REUSE-tool source (GPL-3.0-or-later) is included in this project.
Names containing 'license' are claimed by license-detection heuristics (GitHub licensee, REUSE tooling), which misread the declarative config as a license text. The default config path is now <root>/licet.toml everywhere (check/apply/lint/add-license discovery, init output, --config help), via config::CONFIG_FILENAME. Migration: 'mv license.toml licet.toml'. A lone legacy license.toml is never picked up by default - check/apply fail exit 2 naming the rename, and init refuses to write a competing default next to one. An explicit --config path still reads any filename as an escape hatch. Covers default load, legacy hint, and init paths with three new enforce tests; snapshots, living docs, and contracts updated. 393 tests passing (24 differential vs reuse 6.2.0).
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
Security findingsAdvisory findings (1)ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| BestPractice | 1 medium |
| Complexity | 11 medium |
🔴 Metrics 1297 complexity · 350 duplication
Metric Results Complexity ⚠️ 1297 (≤ 20 complexity)Duplication ⚠️ 350 (≤ 2 duplication)
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
This pull request implements a comprehensive overhaul of the scanning engine and CLI to achieve REUSE 3.3 compliance. However, the PR is currently not up to standards due to a significant increase in cyclomatic complexity across core modules and a failure to meet quality thresholds, evidenced by 15 new issues and over 300 detected code clones.
A critical risk exists in the high-complexity logic found in src/walk/mod.rs, src/reuse/atomic.rs, and src/reconcile/mod.rs. These files handle sensitive filesystem operations and file selection but currently lack sufficient automated test coverage to safely support their high cyclomatic complexity (CCN > 25). Additionally, the consolidation of 12 distinct tasks into a single update significantly hampers reviewability and increases the likelihood of side effects in the new atomic writer or git-index snapshotting logic.
About this PR
- The Pull Request combines 12 major independent tasks and a breaking rename into a single update. This significantly increases review complexity and the risk of regression compared to delivering these improvements incrementally.
Test suggestions
- Verify that
atomic_writerejects writing through a symlink destination or ancestor. - Ensure
check --stagedreads bytes from the Git index even when the working tree has unstaged changes. - Confirm that
applyappends a supersedingoverrideannotation toREUSE.tomlinstead of editing existing stanzas in-place. - Verify that the
initcommand pins root-level files as./filenameto prevent them from matching deeper files of the same name. - Check that
lintreportsmissing_copyrighterrors independently of whether the file matches its declared license intent. - Ensure the engine detects and inventories snippets located deep in a large file (> 8 KiB).
- Verify that
check --explainprovides winning/losing rules and metadata provenance without scanning the full tree. - Confirm the
offline-guardCI job prevents network-capable crates from entering the dependency tree.
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| /// - `replacement`: exact bytes to install. | ||
| /// | ||
| /// See the module docs for the containment checks applied to every call. | ||
| pub fn atomic_write( |
There was a problem hiding this comment.
🔴 HIGH RISK
The primary safety helper is too complex (CCN 29) for a component lacking full test coverage, making it difficult to guarantee that security checks (like symlink ancestors) are applied correctly. Refactor atomic_write to extract destination state classification and temporary file staging logic.
| /// lines with closers derived from `style` — never from re-parsed trailing | ||
| /// code. Copyright lines are never touched (FR-009). Returns [`PlanError`] | ||
| /// instead of guessing when the target block cannot be edited safely. | ||
| pub fn plan_file( |
There was a problem hiding this comment.
🔴 HIGH RISK
High complexity (CCN 32) in reconciliation planning combined with low test coverage increases the risk of source file corruption during apply runs. Refactor plan_file to extract copyright policy evaluation and anchored insertion point calculation into dedicated private methods.
|
|
||
| /// Resolve `selection` into paths + snapshot + configuration, without parsing | ||
| /// the configuration (commands parse it and hand it to the engine). | ||
| pub fn prepare( |
There was a problem hiding this comment.
🔴 HIGH RISK
This function is overly complex (CCN 35) and lacks sufficient test coverage, making it difficult to audit for the subtle correctness requirements of staged vs worktree reads.
Refactor the prepare function to extract the logic for Selection::Staged and Selection::Changed into dedicated private functions, and separate the configuration reading logic from the file marking logic.
| @@ -1,197 +1,691 @@ | |||
| //! Out-of-band REUSE metadata: read `REUSE.toml` (current spec) and `.reuse/dep5` | |||
There was a problem hiding this comment.
🟡 MEDIUM RISK
File length and mixed responsibilities make oob.rs difficult to maintain. The implementation of Task 4 should have included splitting this into a module directory (src/reuse/oob/) with dedicated files for parsing, lookup resolution, and batch writing. Note: This file is currently identified as high-complexity with insufficient coverage.
| | `writes` | list of write records | Independent of file states: one record per planned/applied/failed/blocked write with destination, kind, outcome, covered files, and exact text for text writes (FR-021). | | ||
| | `summary` | `{ pass, partial, complete, before_pass, projected_pass, counts }` | `counts` holds per-`DriftClass` totals **plus** `conflicts` and `contradictions`; `partial` (FR-021) and `pass` (FR-012a, SC-009) live here too. `complete` is false when final verification did not run; `before_pass` is the pre-apply gate; dry-run `pass` means `projected_pass`. Drives the exit code. This is the canonical shape; `report.schema.json` matches it. | | ||
|
|
||
| **PlannedWrite / write record** |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Use a heading instead of bold emphasis for section titles to improve documentation navigation.
| out-of-band coverage for non-annotatable files. | ||
| - Lists referenced-but-missing license texts. Resolves known ids from the **offline | ||
| bundle**; `--allow-network` permits fetching only ids absent from the bundle (FR-017). | ||
| - Reports REUSE conformance posture over **actual** metadata, independently of any |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Lists should be surrounded by blank lines for better readability and consistent parsing.
| /// A closed pipe (the reader went away first, e.g. `| head`) terminates | ||
| /// quietly with exit 0 instead of panicking on `EPIPE`; any other output | ||
| /// error is returned normally for the caller to report. | ||
| pub fn emit_stdout(text: &str) -> Result<()> { |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Avoid hard-coding std::process::exit in utility functions. It is better to propagate the error via Result and handle process exit logic in the main dispatch or a higher-level handler to improve testability.
| } | ||
| let mut paras: Vec<Para> = vec![Para::default()]; | ||
| for raw_line in text.lines() { | ||
| let line = raw_line.strip_suffix('\r').unwrap_or(raw_line); |
There was a problem hiding this comment.
⚪ LOW RISK
Nitpick: The strip_suffix('\r') call is redundant because str::lines() already normalizes line endings and removes terminators during iteration.
…63f99 Signed-off-by: Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>
There was a problem hiding this comment.
🛡️ Codex Security Review · Automatically triggered
Here are some automated security review suggestions for this pull request.
Reviewed commit: 54cb738bd9
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "Codex (@codex) security review"
- A regular code review gets triggered (for example, "Codex (@codex) review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
| /// Discover the repository disposition of `start`, distinguishing "not a | ||
| /// repository" from Git being missing or failing (F13). | ||
| pub fn discover_repo(start: &Path) -> Result<RepoDisposition> { | ||
| let output = std::process::Command::new("git") |
There was a problem hiding this comment.
🛡️ Codex Security Review · Automatically triggered
Security: Resolve Git outside the repository search path
On Windows, running any licet command from an attacker-controlled checkout can execute a committed git.exe. Every command now reaches this call during root discovery, and Command::new("git") uses the platform executable search path, where the current directory can precede the system Git. The -C argument and status checks happen only after the planted image starts, so it runs with the maintainer/CI job's privileges. Resolve a trusted absolute Git path or use an in-process API; fix the new unqualified curl helper similarly.
Useful? React with 👍 / 👎.
Every licet command runs with the evaluated repository as its working directory, and OS executable search can consult the CWD (notably on Windows, where it precedes PATH entries). A checkout containing a planted git.exe/curl could therefore supply the helper binary. New crate::tool::resolve scans PATH explicitly, ignoring empty entries (CWD by convention) and relative entries (CWD-relative), requires a regular executable file, and probes .exe on Windows. All git (3 sites) and curl (fetch path, already --disable/proto-pinned) spawns go through it; unresolvable tools fail exit 2 naming the PATH lookup. Covered by 5 hermetic resolver unit tests plus an integration test running check with an empty PATH.
Answer the Codacy findings with behavior-preserving extractions,
each covered by the existing suites:
- atomic_write: extract classify_destination (destination-state
classification without following symlinks)
- plan_file: extract plan_missing_header and resolve_target_block
(target validation stays refusal-only, never clamping)
- prepare: extract the common Assembly/assemble tail shared by all
five selection arms (also removes the duplicated packing)
- lint: extract explicit --config validation (check_explicit_config)
- reuse/oob.rs (1828 lines) -> reuse/oob/{mod,parse,lookup,write,tests};
public paths unchanged via re-exports
- dep5: drop the redundant strip_suffix (every value path trims;
verified by mutation) and pin CRLF-tolerance with a regression test
- docs: blank line around lint list (cli.md), headings + code-span
spacing (data-model.md)
Deliberately not changed: emit_stdout's quiet EPIPE exit(0) is the
tested contract (broken_stdout_pipe_exits_quietly), not a utility
accident.
Main's manifest lost the serde_json dependency while src (report, add-license, lint) still uses serde_json::json!, so the rebased tree did not compile. Re-add serde_json = "1" (lock resolves to 1.0.150, matching the stale lock entry main carried). Lockfile: apply Deprecation version bumps, prune gix/sha2 subtrees removed from the manifest. Verified: 400 tests pass, clippy -D warnings clean, fmt clean, MSRV 1.89 check ok, 24/24 REUSE differential, offline guard shows no network crates.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
- reuse/atomic: file_attributes() on fs::Metadata comes from MetadataExt, not FileAttributesExt (which is for fs::File). - walk/git: gate the OsString import on unix, its only use site (bytes_to_path) is cfg(unix); on Windows it trips -D warnings. Verified with a local x86_64-pc-windows-msvc lib check; native gates still green (400 tests, clippy -D warnings, fmt).
…nings The non_executable test ended its cfg(not(unix)) branch with return, making the shared assert unreachable on Windows (deny unreachable_code). Split into a cfg(unix) negative test and a cfg(not(unix)) positive test that also pins the accept-a-plain-file semantics.
…pendent
globset disables backslash escapes by default where backslash is a
path separator (Windows), turning the escaped literals our REUSE
pattern compiler emits (for ?[]{} and backslash escapes) into path
separators. Force backslash_escape(true) for both REUSE.toml and dep5
patterns; candidates are already forward-slash normalized, so matching
is now identical on every OS. No-op on Unix (already the default).
Git reports the repo root in long form (C:/Users/runneradmin/...) while the process cwd can carry 8.3 short-name aliases (C:\Users\RUNNER~1\...), so lexical strip_prefix rejected in-root --files inputs as outside the project root (exit 2). Compare both sides through a spelling-normalized form on Windows (canonicalize, with nearest-ancestor fallback for missing inputs); Unix keeps the untouched lexical path. Also applied to explicit --config resolution, which shares the comparison.
Rebase + Windows-green updateRebased onto Dep reconciliation ( Windows CI (red on main, now green here) — four fixes, all verified by the matrix:
Local gates on the pushed head: 400 tests pass, One remaining red check: Codacy (13 new issues, gate max 0). 12 are cyclomatic-complexity findings (limit 12) on functions this diff touches — including three ( |
…ty gate Seven behavior-preserving extractions (no logic changes,Darwin-tested 404 green): - check/explain_one: resolve_explain_target (selection+stat validation) and explain_sources (provenance lines) leave evaluate+render. - oob/lookup: consult_tables (doc/dep5 consultation) and closest_fallbacks (per-field fallback) leave assembly. - oob/parse_dep5: unfold_dep5_paragraphs (pure continuation unfolding) leaves license validation/materialization; + unit test. - oob/write_annotations: render_stanzas (pure TOML serialization) leaves triage/verify/commit; + unit test (copyright shapes, override, escaping, newlines). - git/prefetch: prefetch_want_list (pure fetch-set assembly) leaves the cat-file round trip; + unit test. - atomic_write: stage_replacement (temp+perms+sync) leaves the validate/commit protocol. - init/generate: compressed_ext_rules (uniform-group compression) leaves exact rules/default/sort; + unit test. Left as-is (single coherent concern, flat branching): engine/scan (result assembly), inventory/compute (classify-then-derive pipeline), oob/load_snapshot (three load steps), walk/prepare (selection dispatch), walk/is_reuse_ignored (predicate cascade).
Implements the improvement plan from docs/superpowers/2026-09-10-licet-improvements.md (audit: docs/2026-09-10-licet-audit.md), plus follow-ups found in review.
Stack (3 commits)
licensetrip license-detection heuristics (GitHub licensee, REUSE tooling). Default is now<root>/licet.tomlviaconfig::CONFIG_FILENAME. Migration ismv license.toml licet.toml; a lone legacy file fails exit 2 naming the rename, and explicit--configstill reads any name. 3 new tests; 393 green (24 differential, comparator-enforced).Verification (this branch head)
cargo test --locked --lib --bins --tests: 393 passed, 0 failed (19 suites)LICET_REQUIRE_REUSE=1 cargo test --test reuse_differential: 24/24 vs reuse 6.2.0cargo clippy --locked --all-targets -- -D warnings,cargo fmt --all --check,cargo test --doc, MSRV 1.89, offline-guard: cleanNotes
.specify/memory/constitution.md(gitignored agent memory) still sayslicense.toml; tracked contracts carry the rename.