Skip to content

fix: make lint results independent of how the target is named#60

Merged
nozaq merged 7 commits into
mainfrom
claude/absolute-path-behavior-j1j087
Jul 25, 2026
Merged

fix: make lint results independent of how the target is named#60
nozaq merged 7 commits into
mainfrom
claude/absolute-path-behavior-j1j087

Conversation

@nozaq

@nozaq nozaq commented Jul 24, 2026

Copy link
Copy Markdown
Member

Directory arguments were passed through almost verbatim, so whether a
target was named relatively or absolutely leaked past the reported paths
into rule verdicts and internal path resolution.

  • id-dir-mismatch derived the containing directory's name from the
    reported path, which has no directory component when the target is
    named "." — the default. Linting a Feature or Template from inside it
    therefore always reported the id as mismatched. The name now comes
    from the directory's own location, handed to rules as linter.Dir.
  • Compose resolution assumed the lint root was absolute, which only held
    when the target was named that way; a Dockerfile outside the
    configuration directory could otherwise be displayed as if inside it.
  • The github format emitted absolute paths, which GitHub cannot place on
    a diff. Paths inside the working directory are now reported relative
    to it, with "/" separators.
  • Arguments naming the same directory twice are linted once.
  • Errors name the path once, cleaned, and say what to pass when given a
    file rather than a directory.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01KsXG8rvSVXzUGYMe4AR5mi

claude and others added 7 commits July 24, 2026 22:52
Directory arguments were passed through almost verbatim, so whether a
target was named relatively or absolutely leaked past the reported paths
into rule verdicts and internal path resolution.

- id-dir-mismatch derived the containing directory's name from the
  reported path, which has no directory component when the target is
  named "." — the default. Linting a Feature or Template from inside it
  therefore always reported the id as mismatched. The name now comes
  from the directory's own location, handed to rules as linter.Dir.
- Compose resolution assumed the lint root was absolute, which only held
  when the target was named that way; a Dockerfile outside the
  configuration directory could otherwise be displayed as if inside it.
- The github format emitted absolute paths, which GitHub cannot place on
  a diff. Paths inside the working directory are now reported relative
  to it, with "/" separators.
- Arguments naming the same directory twice are linted once.
- Errors name the path once, cleaned, and say what to pass when given a
  file rather than a directory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsXG8rvSVXzUGYMe4AR5mi
Paths were still kept in three representations — the spelling given on
the command line, the absolute location, and the github format's own
base directory — so each function had to know which space its path was
in, and conversions like configDirName existed only to move between
them.

Opening the lint root on its absolute location makes every path derived
from it absolute for free: os.Root.OpenRoot names a sub-root by joining
onto its parent's name, so the .devcontainer sub-root and the paths
built from it are absolute too. Only the rendering for the reader is
left, which absPath.String does — relative to the working directory when
the path is inside it, absolute otherwise. Being a Stringer, it renders
in error messages without any call site having to ask for it.

This drops lintRoot, configDirName, lintDir's filepath.Abs, and the
github format's BaseDir. Findings now name a file the same way however
the lint target was named, so an absolute target under the working
directory is reported relative to it.

The Dockerfile reference a Compose build resolves keeps its own base,
the configuration directory: it identifies a contributor inside the
merged configuration, so it must not depend on where decolint was run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsXG8rvSVXzUGYMe4AR5mi
dedupePaths resolved every argument to decide whether it duplicated an
earlier one, then threw the result away and returned the original
spelling, leaving lintPath to resolve the same argument again.

Options.Paths now holds the resolved directories, so lintPath takes them
as they are. Deduplication compares the resolved values themselves, and
the rule about which spelling to keep goes away with them: rendering no
longer depends on how a directory was named, so there is nothing to
choose between.

Resolving in parseOptions means an argument that cannot be resolved
fails there rather than mid-lint, which also makes -version and -rules
fail when the working directory has been deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsXG8rvSVXzUGYMe4AR5mi
…arsed

Holding the resolved directories in Options gave parseOptions a
dependency on the working directory and an error that has nothing to do
with parsing a command line, which is all Options is meant to describe.
It also let a deleted working directory fail -version and -rules, which
name no directory at all.

Options.Paths goes back to the arguments as named, and runLint resolves
and deduplicates them before linting. Each target is still resolved
once: lintPath keeps taking an absPath and does not resolve anything
itself. resolvePaths moves next to absPath, so the whole of the CLI's
path resolution now sits in one file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsXG8rvSVXzUGYMe4AR5mi
resolvePaths walked the arguments to build a slice that runLint then
walked again, so resolving, deduplicating and linting now happen in the
one loop over the arguments.

Folding them together also settles how a target that cannot be resolved
is treated. It used to abort the run before anything was written, while
every other per-target failure is recorded and the remaining targets are
linted — the posture lintDir already takes for a broken file. Resolution
failures now join the same way, so a bad target no longer discards the
findings of the good ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsXG8rvSVXzUGYMe4AR5mi
The directory name a rule is handed comes from the file's location on disk
rather than the path findings report it at. That was checked through
id-dir-mismatch's message and the JSON output, tying the test to the only rule
that reads the name. Observe it with a stub rule at lintPath instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nozaq
nozaq merged commit 725a609 into main Jul 25, 2026
8 checks passed
@nozaq
nozaq deleted the claude/absolute-path-behavior-j1j087 branch July 25, 2026 01:35
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.

2 participants