Skip to content

Improvement plan (tasks 1-12) + rename default config to licet.toml - #12

Merged
Adam Poulemanos (bashandbone) merged 13 commits into
mainfrom
muse/session-01a08e88-0c91-7280-8a1a-1566f7963f99
Sep 11, 2026
Merged

Improvement plan (tasks 1-12) + rename default config to licet.toml#12
Adam Poulemanos (bashandbone) merged 13 commits into
mainfrom
muse/session-01a08e88-0c91-7280-8a1a-1566f7963f99

Conversation

@bashandbone

Copy link
Copy Markdown
Contributor

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)

  1. feat: implement licet improvement plan (tasks 1-12, F01-F19) — engine (git-aware selection, stateless scans, rule precedence, OOB REUSE.toml/dep5, drift classification, snippet lint), CLI (preserving init, selection flags, offline add-license, gated completions), report-v2/schema/CLI contracts + spec updates, 24 differential tests vs reuse 6.2.0, CI conformance gates. 390 tests green.
  2. docs: attribute vendored REUSE Specification v3.3 (CC-BY-SA-4.0) — the vendored spec copy lacked the attribution CC-BY-SA requires; adds FSFE notice, SPDX tag, source link. (Upstream is ShareAlike, not plain CC-BY.)
  3. feat!: rename default config license.toml to licet.toml — names containing license trip license-detection heuristics (GitHub licensee, REUSE tooling). Default is now <root>/licet.toml via config::CONFIG_FILENAME. Migration is mv license.toml licet.toml; a lone legacy file fails exit 2 naming the rename, and explicit --config still 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.0
  • cargo clippy --locked --all-targets -- -D warnings, cargo fmt --all --check, cargo test --doc, MSRV 1.89, offline-guard: clean
  • Release binary exercised end-to-end (init writes licet.toml, legacy hint, explicit-legacy escape hatch)

Notes

  • Each commit is green on its own (commit 1 verified at 390 before stacking).
  • Historical docs (audit, improvement plan, spec-process artifacts) intentionally still reference the old filename; generated CHANGELOG untouched.
  • .specify/memory/constitution.md (gitignored agent memory) still says license.toml; tracked contracts carry the rename.

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).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-11T13:00:27.561441Z 54cb738 PR opened

Security findings

Advisory findings (1)

ℹ️ About Codex in GitHub

Your 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.

@codacy-production

codacy-production Bot commented Sep 11, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 12 medium

Alerts:
⚠ 12 issues (≤ 0 issues of at least medium severity)

Results:
12 new issues

Category Results
BestPractice 1 medium
Complexity 11 medium

View in Codacy

🔴 Metrics 1297 complexity · 350 duplication

Metric Results
Complexity ⚠️ 1297 (≤ 20 complexity)
Duplication ⚠️ 350 (≤ 2 duplication)

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

@codacy-production codacy-production Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_write rejects writing through a symlink destination or ancestor.
  • Ensure check --staged reads bytes from the Git index even when the working tree has unstaged changes.
  • Confirm that apply appends a superseding override annotation to REUSE.toml instead of editing existing stanzas in-place.
  • Verify that the init command pins root-level files as ./filename to prevent them from matching deeper files of the same name.
  • Check that lint reports missing_copyright errors 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 --explain provides winning/losing rules and metadata provenance without scanning the full tree.
  • Confirm the offline-guard CI 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

Comment thread src/reuse/atomic.rs Outdated
/// - `replacement`: exact bytes to install.
///
/// See the module docs for the containment checks applied to every call.
pub fn atomic_write(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Comment thread src/reconcile/mod.rs Outdated
/// 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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Comment thread src/walk/mod.rs

/// Resolve `selection` into paths + snapshot + configuration, without parsing
/// the configuration (commands parse it and hand it to the engine).
pub fn prepare(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Comment thread src/reuse/oob.rs Outdated
@@ -1,197 +1,691 @@
//! Out-of-band REUSE metadata: read `REUSE.toml` (current spec) and `.reuse/dep5`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: Lists should be surrounded by blank lines for better readability and consistent parsing.

Comment thread src/cli/mod.rs
/// 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<()> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ 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.

Comment thread src/reuse/oob.rs Outdated
}
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ 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.

Comment thread src/walk/git.rs Outdated
/// 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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

P1 Badge 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.
@socket-security

socket-security Bot commented Sep 11, 2026

Copy link
Copy Markdown

- 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.
@bashandbone

Copy link
Copy Markdown
Contributor Author

Rebase + Windows-green update

Rebased onto de522d2 (remote tip). The rebase surfaced three latent issues, all fixed in this branch:

Dep reconciliation (da14ca3) — the merged manifest had lost the serde_json dependency while src still uses serde_json::json!, so the tree did not compile. Re-added serde_json = "1" (lock resolves to 1.0.150); lockfile takes the Deprecation bumps with the removed gix/sha2 subtrees pruned. Note: main@de522d2 itself does not compile as committed for the same reason.

Windows CI (red on main, now green here) — four fixes, all verified by the matrix:

  • a3e1c2d: MetadataExt, not FileAttributesExt, provides file_attributes() on fs::Metadata; gate the OsString import on unix (its only use is cfg(unix)).
  • 4a79d93: split the platform-divergent resolver test so no cfg(not(unix)) branch ends in return (-D unreachable-code).
  • 9888586: force backslash_escape(true) on both oob glob builders — globset disables escapes by default on Windows, which broke literal ?[]{} /`-escape matching (4 failing lookup tests).
  • cb6f476: --files compared the cwd-joined input lexically against git's root, but on Windows the cwd can carry 8.3 aliases (RUNNER~1) while git rev-parse reports the long form → bogus "outside the project root" (exit 2). Both sides now compare through a spelling-normalized form on Windows; Unix path handling is untouched.

Local gates on the pushed head: 400 tests pass, clippy -D warnings clean, fmt clean, MSRV 1.89 ok, 24/24 REUSE differential, offline guard clean. GitHub matrix (ubuntu/macos/windows + all jobs) is green.

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 (explain_one, scan, generate) outside this diff entirely — plus 1 false positive (i18n complaint about tests/fixtures/table/sample.jsx, which is test data). This reads as a default-threshold gate mismatched to the codebase rather than actionable review feedback; fixing it properly means either a 12-function refactor campaign or a threshold/fixture-exclusion adjustment in Codacy. Leaving that call to the maintainer.

…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).
@bashandbone
Adam Poulemanos (bashandbone) merged commit 0dd1484 into main Sep 11, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant