48 Commits
Author SHA1 Message Date
james.maes a14dee227a Merge feature/GH-30-detect-overmatch (#30): confine header detection 2026-06-09 10:12:26 -05:00
james.maes e1e4563e03 Merge feature/GH-33-symlink-license (#33): refuse symlinked LICENSE 2026-06-09 10:12:19 -05:00
james.maes d4c72311cf Merge feature/GH-31-spdxnorm-no-guess (#31): stop guessing SPDX ids 2026-06-09 10:11:35 -05:00
james.maes e116c60b88 Merge feature/GH-29-init-tui-wizard (#29): init full TUI wizard
# Conflicts:
#	CHANGELOG.md
#	cmd/license-tool/commands_test.go
#	docs/SESSION-STATE.md
2026-06-09 10:11:34 -05:00
james.maes 6497f0be81 fix(#30): confine license-header detection to contiguous comment lines
Header detection over-matched on line-comment file types: lineCommentRegion
tolerated truly-blank lines between comment lines, so a layout of
"license header / blank line / unrelated file doc comment" collapsed into a
single detected span. render.replaceSpan then deleted that doc comment on
relicense or year-refresh. This is safety-critical -- a false positive in
detection means apply removes real source content.

Two fixes:

1. lineCommentRegion now stops at the first non-comment line, and a
   truly-blank line ends the contiguous run. Our own rendered headers never
   contain a truly-blank internal line: the renderer emits separator lines as
   a bare comment prefix (e.g. "#" or "//"), which is still a comment line and
   stays in-region. Confining the region to contiguous comment lines is
   therefore byte-idempotent for headers we write, while refusing to absorb a
   foreign comment that a blank line has separated from the header.

2. spdxIdentifierTag now requires the SPDX-License-Identifier tag to begin a
   comment line (leading whitespace allowed) rather than matching anywhere via
   strings.Index. A comment that merely mentions the tag mid-prose is
   documentation, not a managed header, and must not qualify a foreign comment
   for replacement.

When in doubt the detector now under-detects (leaves a foreign comment alone)
rather than over-detecting. Block-comment multi-block de-stacking (issue #30
LOW) is deferred: spanning only the first block is the safe, under-detecting
choice.

Idempotency and round-trip are preserved and verified by the existing
TestSpliceIdempotent / TestApplyFile* suites plus a new end-to-end applier
test that relicenses a line-comment file and asserts a doc comment below the
header's blank separator survives. The enshrining region test was updated to
require the corrected contiguous behavior with the safety rationale inline.
2026-06-09 08:38:50 -05:00
james.maes 05157ddf48 fix(#33): refuse to overwrite a symlinked license file
AtomicWrite used os.Stat + os.Rename with no symlink check, so writing a
managed LICENSE / LICENSES/<id>.txt that is a deliberate symlink (e.g.
LICENSE -> LICENSES/MIT.txt) would replace the link with a regular file,
silently breaking it.

AtomicWrite now lstats the target and refuses with errSymlinkTarget when it
is a symlink, leaving the link and its target untouched. The error message
contains "refusing to write" so the CLI's isWriteRefusal mapping classifies
it as a write refusal (exit code 3). writeManaged propagates this refusal as
a returned error (other write failures stay recorded per-file as before).

Closes #33
2026-06-09 08:00:58 -05:00
james.maes 194fb0e49f fix(#31): resolve ambiguous license aliases to unresolved instead of guessing
The spdxAliases table fabricated SPDX ids, violating the package's
"never guess" invariant:

- "bsd" / "bsd license" invented a clause count (could be 2/3/4-clause).
- "gnu lesser general public license" invented BOTH a version (2.1 vs 3.0)
  and the grant ("only" vs "or-later").

These wrong guesses flowed into deny/allow verdicts and the incompatibility
table, where -only vs -or-later and clause counts are decisive. Such ambiguous
spellings now leave normalizeSPDX returning ok=false, so the caller emits an
unresolved dependency with a reason rather than a fabricated id. Only
unambiguous, exact spellings remain (explicit 3-clause BSD, Apache, MIT, ISC,
MPL-2.0, Unlicense, the explicit AGPL "v3.0 or later" spelling).

Also fixes the related resolved-but-unclassifiable contradiction: alias
re-validation now uses spdx.Lookup (curated rendering set) instead of
spdx.Validate (full index). The Eclipse Public License aliases (EPL-2.0 /
EPL-1.0) passed Validate but are outside the curated set, so policy.Classify
returned Unknown while the dependency was still marked resolved. They are now
left unresolved. Any future alias whose target is valid-but-uncurated is
structurally rejected by the Lookup gate.

Closes #31
2026-06-09 07:59:28 -05:00
james.maes 3852c9122b fix(#35): harden group risk under --only; review-driven follow-ups
Address findings from a three-agent adversarial review of the GH-35
delta:

- Compute the repo-wide incompatibility set from the full report, not the
  --only-narrowed listing, so a filtered listing can no longer understate
  a group's policy-aware risk. Add groupFilesWith(r, spec, incompatIDs)
  and thread the full-report set through the text/markdown/JSON renderers.
- Use path.Base (not filepath.Base) on the slash-normalized enumerate
  path when matching the config file, for cross-platform correctness.
- Tests: directory-grouping escalation across a mixed bucket, incompat
  beating the headerless-unknown fallback, non-policy tokens not
  escalating, incompatibleIDs cardinality (0/1/3 ids), the filtered-
  listing risk contract, nested/--only config exclusion, no detected-
  license/violation leak on the skipped config, and a check exit-code
  regression guard for the config exclusion.
- Docs: correct the README grouped-output samples (risk markers,
  (no-header) label, JSON group shape) and note the check exit-code
  implication of the config exclusion in README and CHANGELOG.
2026-06-09 07:44:21 -05:00
james.maes 2822d862d8 feat(#35): policy-aware group risk
A group's [risk] marker was computed solely from its files' license
categories, so it was policy-blind: an Apache-2.0 group beside an
AGPL-3.0-or-later group read "low" even though the pair is a curated
hard incompatibility, and a deny-listed permissive license also read
"low".

Escalate a group's risk to "high" when it carries a policy concern: a
file whose license is party to a repo-level hard incompatibility
(computed once over the distinct source ids via policy.Incompatible),
or a file carrying a file-scoped policy-violation token. Category-only
risk is preserved when there is no policy concern, with no false
escalation (MIT beside AGPL, not an incompatibility, stays "low").
2026-06-09 07:10:16 -05:00
james.maes ce192cad54 feat(#35): exclude tool's own .license-tool.yaml from source coverage
The config file is YAML (commentable), so the audit counted it as a
coverable source file missing a managed header, inflating sourceTotal
and sourceMissing. It is tool metadata, not coverable source, and never
carries a header.

Mark any file whose basename is .license-tool.yaml as skipped (reason
"tool config") in the audit pipeline, so it still appears in the file
listing but is excluded from source/header tallies. Export
config.RepoConfigName as the single source of truth for the name.
2026-06-08 15:30:38 -05:00
james.maes 366ffece45 feat(#35): --only filter to list problem files (missing|unknown|copyleft|violations) 2026-06-08 15:12:29 -05:00
james.maes b43e362a21 fix(#35): unify (no-header) token, unknown-risk for headerless groups, full JSON under --summary 2026-06-08 14:58:42 -05:00
james.maes 96389108a8 feat(#35): per-group license breakdown, --depth directory grouping 2026-06-08 14:21:52 -05:00
james.maes 29c7108c80 feat(#35): per-group risk markers, (no-header) rename, --sort key|count 2026-06-08 14:14:19 -05:00
james.maes daef9c396d feat(#35): findings + totals/percentages + riskLevel parity across text/markdown/json 2026-06-08 14:07:52 -05:00
james.maes 48fd159368 feat(#35): attributable policy violations (which license/rule/file) in all formats 2026-06-08 14:01:30 -05:00
james.maes dd4b34ba93 feat(#35): RenderOptions for summary + grouped views across all formats 2026-06-08 13:19:45 -05:00
james.maes c3755f7baf feat(#35): add pure GroupFiles + ParseGroupBy for audit grouping 2026-06-08 13:12:04 -05:00
james.maes 0031b00eb3 feat(#29): single-form init wizard with adaptive live preview
Replaces the 7-step 3-pane wizard with a single repo-seeded form and a
persistent adaptive preview. wizard.go is now a thin Bubble Tea adapter;
all logic lives in internal/initwizard at 100% coverage.

Closes #29
2026-06-08 12:33:49 -05:00
james.maes 09c8174841 feat(#29): pure adaptive view rendering for init form 2026-06-08 12:21:34 -05:00
james.maes a6cc331fea feat(#29): adaptive panel layout planner 2026-06-08 12:14:31 -05:00
james.maes 45dcfd90e1 feat(#29): pure form state machine with live validation 2026-06-08 12:13:25 -05:00
james.maes ddcc51d27f feat(#29): seed init answers from repo detection 2026-06-08 11:58:01 -05:00
james.maes bafa2810a6 refactor(#29): unify Answers->Config behind one translator 2026-06-08 11:50:28 -05:00
james.maes 4e3561ef29 fix(#32): include globs honor ** via shared gitignore matcher 2026-06-08 11:44:56 -05:00
james.maes 44d4986063 feat(init): add pane-based TUI wizard
Refs #29
2026-06-08 09:28:03 -05:00
james.maes 1bfddf89f9 merge: init tui shell slice 2026-06-04 16:37:21 -05:00
james.maes ca3479ef02 merge: init preview engine slice 2026-06-04 16:36:55 -05:00
james.maes f80ab905f2 feat(init): add source preview engine
Refs #29
2026-06-04 16:36:05 -05:00
james.maes 4773596e5d feat(init): wire tui collector
Refs #29.
2026-06-04 16:36:03 -05:00
james.maes fa1b86da03 feat(init): persist include patterns
Refs #29
2026-06-04 16:35:20 -05:00
james.maes 36ec31d511 fix: satisfy integrated quality gates 2026-06-04 14:23:58 -05:00
james.maes 9893efdc37 merge: dependency release docs audit fixes
# Conflicts:
#	cmd/license-tool/commands.go
#	cmd/license-tool/commands_test.go
2026-06-04 12:04:51 -05:00
james.maes 0e2b665463 merge: spdx policy audit fixes
# Conflicts:
#	cmd/license-tool/commands.go
#	cmd/license-tool/commands_test.go
2026-06-04 12:02:31 -05:00
james.maes 0232911ea9 merge: write safety audit fixes
# Conflicts:
#	internal/applier/applier.go
2026-06-04 12:00:27 -05:00
james.maes 6c482a964d merge: filetype coverage audit fixes 2026-06-04 11:59:32 -05:00
james.maes 9fff8debaf feat(filetype): add shebang-aware coverage
Refs #9, #10.
2026-06-04 11:58:11 -05:00
james.maes bf86694c37 fix(header): preserve leading semantic prefixes
Keep Go build constraints, CSS charset rules, and markup doctypes ahead of inserted managed headers.

Refs #6

Refs #7

Refs #8
2026-06-04 11:53:39 -05:00
james.maes c4538c6907 fix(spdx): validate renderable and policy license ids
Reject unrenderable write licenses and exact-match policy typos before execution.

Closes #19

Closes #20
2026-06-04 11:50:04 -05:00
james.maes b0b566e600 fix(write-safety): gate writes and scope commits
Refs #12, #13, #14, #15.
2026-06-04 11:49:14 -05:00
james.maes 3e18e6c8d5 fix: harden release and dependency docs
Pins release workflow actions and tools, discovers nested dependency manifests, clarifies Gradle tool-tier behavior, and refreshes public support/security scaffolding.

Refs #21 #22 #23 #26
2026-06-04 11:41:43 -05:00
james.maes 5d862308b5 fix(header): preserve coding equals before headers
Share the managed-header sentinel and preserve-boundary rules across detect and render.

Refs #24

Refs #27
2026-06-04 11:41:39 -05:00
james.maesandJames Maes f4a111f8ec fix: preserve shebang universally so headers never precede #! (#5)
Co-authored-by: James Maes <jmaes@dmdbrands.com>
2026-06-04 10:58:58 -05:00
james.maesandJames Maes 0670cb0c25 v0.2.0: init wizard + audit findings-summary (#4)
* feat(init): interactive huh wizard + .license-tool.yaml scaffolding

* feat: audit findings-summary + isTTY x/term fix; changelog v0.2.0

---------

Co-authored-by: James Maes <jmaes@dmdbrands.com>
2026-06-03 18:50:09 -05:00
James Maes 069ced4abb test: pin git init -b main in gitutil tests (CI defaults to master) 2026-06-03 15:12:15 -05:00
James Maes 26082c9929 fix: satisfy golangci-lint (errcheck exclusions, var shadow, error naming) 2026-06-03 14:55:28 -05:00
James Maes fcc6717bdf test: 100% coverage suite, CI coverage gate, release scaffolding 2026-06-03 14:50:01 -05:00
James Maes 7ebffebae6 feat: license-tool audit + apply with SPDX/REUSE engine and scaffolding 2026-06-03 14:03:50 -05:00