Skip to content

Stop skipping source packages named build - #43

Merged
alexander-yevsyukov merged 2 commits into
masterfrom
fix-build-package-exclusion
Sep 3, 2026
Merged

alexander-yevsyukov merged 2 commits into
masterfrom
fix-build-package-exclusion

Conversation

@alexander-yevsyukov

@alexander-yevsyukov alexander-yevsyukov commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

update_copyright.py silently skipped 12 tracked source files in the config repository. Surfaced while re-stamping every header there for a copyright-holder change: the files under io.spine.dependency.build came back untouched.

The bug

is_excluded() asked whether any path segment is a denied directory name:

if any(part in EXCLUDED_DIRS for part in parts):
    return True

EXCLUDED_DIRS lists Gradle's output directories by name, build among them. But build names two different things:

buildSrc/build/generated/Foo.kt                              <- Gradle output
buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt    <- a source package

The second is io.spine.dependency.build, the package holding declarations for build-time tools (Dokka, ErrorProne, Pmd, CheckStyle, KSP). The unanchored test matched its seventh segment and dropped the file.

Two things made this hard to notice. The filtering happens in expand_requested_paths() before any work, so it applies to explicitly-passed paths too — naming the file on the command line does not get it stamped. And the script reports only what it did update, so a skipped file looks exactly like one that needed no change. In practice the header year just quietly stops advancing, including on the PostToolUse hook run after each edit.

The fix

Not every denied name is ambiguous, so they no longer share one rule.

Excluded by name, at any depth — tool and VCS state (.git, .idea, .gradle, .agents, .kotlin) plus generated. No package is called .git, and guidelines/coding.md classifies every **/generated/** path as generated whatever its depth, so a generated tree inside a source root stays out of scope.

Excluded by positionbuild, out, and tmp, whose names genuinely double as package names. A build tool creates these beside a build script and never inside a source tree, so the first marker to appear decides: a src segment means package, an output name means build output. This is the same line the Spine .gitignore already draws, pairing **/build/** with !**/src/**/build/** — the ambiguity was already known there, just not here.

Resulting behaviour:

Path
buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt stamp — the bug fixed
src/main/kotlin/out/Api.kt stamp — a package named out
buildSrc/build/generated/Foo.kt skip — Gradle output
module/src/main/generated/Foo.java skip — generated at any depth

Tests

Four added:

  • a source package named build, stamped via an explicit path
  • the same via the default tracked-files run, which is how a repo-wide refresh reaches it
  • Gradle's actual output directory, which must stay excluded
  • a generated tree inside a source root, which must stay excluded

The first two fail against the previous script (Updated 0 file(s).); the others pin behaviour that must not change. Suite: 17 tests, green.

Note

guidelines/coding.md names **/generated-proto/** alongside **/generated/**, but generated-proto is not in the exclusion set and was not before this change either. Left alone deliberately — adding it would stop stamping files that are stamped today, which is a behaviour change beyond this fix.

🤖 Generated with Claude Code

`is_excluded()` tested every path segment against `EXCLUDED_DIRS`, which lists
Gradle's output directories by name. `build` names both that output directory
and an ordinary source package: `io.spine.dependency.build` holds the
declarations for build-time tools. Matching the name at any depth dropped
those 12 tracked files from every run, so their headers silently kept a stale
year — and because the script reports only what it updated, a skipped file is
indistinguishable from one that needed no change.

The distinction is positional, not by name. A build tool creates these
directories beside a build script, never inside a source tree, so a name that
follows a `src` segment denotes a package. Split the set in two: tool and VCS
directories still match at any depth, since no package is named `.git`, while
the output names match only until a source tree is entered. The Spine
`.gitignore` draws the same line, pairing `**/build/**` with
`!**/src/**/build/**`.

Both regression tests fail against the previous script; the third pins the
behaviour that must not change, and passes either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 3, 2026 16:58
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 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
📝 Code Review Completed 2026-09-03T17:01:42.959689Z 8ac9caa PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The behavioral fix is covered by targeted regression tests, and the only noted follow-up is a non-blocking maintainability/performance improvement to the helper implementation.

Pull request overview

This PR fixes an exclusion bug in update_copyright.py where source files could be silently skipped if any path segment matched an output directory name like build, even when that segment was part of a legitimate source package path (e.g., io.spine.dependency.build). It refines the exclusion logic so build output directory names are only treated as output before entering a src subtree, and adds regression tests to prevent the issue from returning.

Changes:

  • Split excluded directories into “tool/VCS” dirs (excluded at any depth) vs. build output dirs (excluded only before src).
  • Added in_build_output() helper to implement the positional “before src” rule for output directories.
  • Added regression tests covering build as a package name (explicit path + tracked-files run) and confirming Gradle output remains excluded.
File summaries
File Description
skills/update-copyright/scripts/update_copyright.py Refines exclusion logic to avoid skipping source packages named like build output dirs.
skills/update-copyright/tests/test_update_copyright.py Adds regression tests for build as a source package and for Gradle output exclusion.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread skills/update-copyright/scripts/update_copyright.py Outdated
@alexander-yevsyukov alexander-yevsyukov moved this from 🏗 In progress to In Review in v2.0 Sep 3, 2026

@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 Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ac9caa052

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/update-copyright/scripts/update_copyright.py Outdated
The positional rule was applied to every output-directory name, which admitted
a generated tree laid out inside a source root: `src/main/generated/Foo.java`
followed a `src` segment, so it read as a package and would have been
re-stamped. `guidelines/coding.md` classifies every `**/generated/**` path as
generated whatever its depth, and re-stamping such a file churns an edit the
next code generation reverts.

`generated` is therefore excluded by name, alongside the tool and VCS
directories. The positional rule stays for `build`, `out`, and `tmp`, whose
names genuinely double as package names — that ambiguity is what this branch
set out to resolve.

Also walk the segments once instead of re-scanning the prefix for `src` at
every step: the first of the two markers to appear decides the answer, so a
single pass settles it and drops the quadratic scan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 3, 2026 17:05
@alexander-yevsyukov
alexander-yevsyukov merged commit f3d8ac5 into master Sep 3, 2026
1 check passed
@alexander-yevsyukov
alexander-yevsyukov deleted the fix-build-package-exclusion branch September 3, 2026 17:07
@github-project-automation github-project-automation Bot moved this from In Review to ✅ Done in v2.0 Sep 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

One added test (test_gradle_output_directory_is_still_skipped) currently can’t actually detect regressions in the new positional logic because it’s also excluded by the always-excluded generated segment.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +484 to +488
generated = "buildSrc/build/generated/Generated.kt"
source = root / generated
self.write_file(source, STALE_BLOCK + "object Generated\n")

result = self.run_script(root, "--year", "2026", generated)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants