Skip to content

feat: add .presetignore support - #5020

Merged
mnriem merged 11 commits into
github:mainfrom
grafvonb:feat/presetignore
Oct 21, 2026
Merged

mnriem merged 11 commits into
github:mainfrom
grafvonb:feat/presetignore

Conversation

@grafvonb

Copy link
Copy Markdown
Contributor

Description

Add .presetignore support so preset authors can exclude development-only files from installed presets, matching the existing .extensionignore behavior.

  • Share ignore loading and matching between extensions and presets, preserving extension behavior.
  • Support Git-style patterns, comments, negation, directory patterns, root anchoring, UTF-8, and existing whitespace/backslash normalization.
  • Exclude .presetignore itself. Preserve existing copying behavior when absent; do not use .gitignore or introduce default exclusions.
  • Validate ignore files before the source-directory installer removes existing files or registration state.
  • Apply filtering to directory installs, archive installs, reinstalls, and update replacements.
  • Document supported semantics and examples.

preset update retains its documented remove-then-add behavior. The existing extension force-removal ordering remains unchanged.

Testing

Commands use this checkout’s virtual environment, following repository guidance.

  • Tested locally with .venv/bin/specify --help
  • Ran existing tests with uv sync --extra test and .venv/bin/python -m pytest tests -q
  • Tested with a sample project

Results:

  • 12,673 passed and 101 skipped across the full run and targeted retries. The sandboxed run encountered 9 failures and 628 setup errors involving network access and local HTTP servers; all 637 cases passed outside the sandbox without code changes. The initial run emitted 62 warnings.
  • Captured 25 failing preset cases before implementation and verified they pass afterward.
  • Added mirrored helper tests and installer regression coverage.
  • Validated specify init --preset, template resolution, and preset update in a temporary Codex project.
  • Executed the installed speckit-specify skill; the generated specification retained the preset template marker and satisfied all 16 quality-checklist criteria.
  • Ruff 0.15.10 and git diff --check passed.

Validation ran on macOS with Python 3.11.15. The cross-platform CI matrix remains pending.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: OpenAI Codex using GPT-6 in autonomous coding-agent mode authored the implementation, tests, documentation, commit messages, and this PR description. Codex also reviewed repository guidance, ran automated tests, and executed the sample-project workflow validation. This does not claim human manual testing or line-by-line review.

Share extension ignore loading and matching with presets while preserving
extension behavior. Validate preset ignore files before the directory
installer removes existing files, registry entries, or registered commands.

Cover pattern semantics, encoding failures, ignored manifests and assets,
archive reinstalls, and CLI updates. Document filtering rules and the
existing remove-then-add update limitation.

Validation: 1,520 tests pass in this checkout's virtual environment, with
three failure-path warnings. Capture 25 failing preset cases before the
implementation and verify that they pass afterward.

Assisted-by: Codex (model: GPT-6, autonomous)
Move all 16 pattern cases into the mirrored test_install_ignore suite and
run them for both extension and preset ignore files. Retain installer
lifecycle coverage and test absent files, self-exclusion, domain validation
errors, and read failures at the shared helper boundary.

Validation:
- 1,546 relevant tests pass with three existing failure-path warnings.
- Full-suite collection increases from 12,748 to 12,774; all original
  pattern parameter sets remain represented.
- Ruff 0.15.10 and git diff --check pass for the follow-up.
- Initialize a temporary Codex skills project with a local preset using
  .presetignore and confirm development files are excluded while a template
  matched by .gitignore remains installed.
- Execute the installed speckit-specify skill in the Codex session, resolve
  the preset template, and create a reading-list specification with its
  preset marker retained and all 16 quality-checklist criteria satisfied.
- Run preset update with replacement ignore rules and verify the new
  retained file replaces the previously retained file.

Manual workflow validation was performed by Codex on macOS with Bash
project scripts; no human testing or cross-platform CI result is claimed.
The full test suite and CI OS/Python matrix were not executed.

Assisted-by: Codex (model: GPT-6, autonomous)
@grafvonb
grafvonb requested a review from mnriem as a code owner October 18, 2026 13:11
Copilot AI max review requested due to automatic review settings October 18, 2026 13:11

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

UTF-8 byte-order marks can silently disable the first ignore rule.

1 open issue
What changed in this PR

Adds .presetignore support so preset authors can exclude development files using the existing extension filtering semantics.

Changes:

  • Share ignore loading and matching between presets and extensions.
  • Validate preset ignore files before directory replacement.
  • Add installation regressions and document supported patterns.
File Description
tests/​test_extensions.py Verify declared files remain filterable.
tests/​specify_cli/​test_install_ignore.py Test shared matching and validation contracts.
tests/​specify_cli/​presets/​test_manager.py Cover filtering, reinstalls, and state preservation.
tests/​integration/​test_preset_update_workflow.py Verify filtering during updates.
src/​specify_cli/​presets/​_manager.py Validate and apply preset ignore rules.
src/​specify_cli/​extensions/​__init__.py Delegate filtering to the shared helper.
src/​specify_cli/​_install_ignore.py Centralize ignore loading and matching.
docs/​reference/​presets.md Explain patterns and installation behavior.

Review effort: Max

Comment thread src/specify_cli/_install_ignore.py Outdated
Decode preset and extension ignore files with utf-8-sig so an optional BOM does not become part of the first pattern. Cover ASCII and non-ASCII first patterns with BOM and CRLF fixtures, and document BOM support.

Validation: four new regression cases failed before the fix; the relevant preset, extension, and shared-ignore suites passed with 1550 tests and 3 warnings. Ruff and git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 18, 2026 14:13

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.

🔵 Needs a closer look

Shared installation and replacement behavior needs human sign-off while cross-platform validation remains pending.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Malformed patterns can raise an unhandled ValueError

src/​specify_cli/​_install_ignore.py:51

Malformed patterns can bypass the normal validation message. A lone ! is rejected by supported pathspec versions with a ValueError, but this helper only translates decoding errors. preset add catches preset-domain errors, not ValueError, so this typo can produce a traceback instead of a useful validation error.

Wrap pattern-parsing failures in the supplied validation_error and add a shared contract test for malformed input. This improves error reporting without changing when validation occurs or when existing files are removed.

1 resolved since last review

Review effort: Max

@mnriem mnriem added the triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review label Oct 19, 2026
@mnriem

mnriem commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback including the previously missed

@mnriem mnriem added the author-awaiting Waiting on author response label Oct 19, 2026
Translate pathspec parsing failures into the caller's domain validation error, retaining the ignore-file path and parser message. Add shared regression coverage for malformed preset and extension patterns without changing installation ordering.

Validation: all four new regression cases failed before the fix. Relevant suites passed with 1554 tests and 3 warnings in this checkout's virtual environment. Ruff and git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 09:39

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.

🔵 Needs a closer look

Shared installer and encoding changes still need cross-platform validation and final human review.

1 open issue

Review effort: Max

Comment thread src/specify_cli/_install_ignore.py Outdated
Decode as UTF-8 before removing one leading BOM so invalid-encoding errors report file-relative byte offsets. Cover BOM-prefixed and BOM-free invalid input for both presets and extensions.

Validation: both new BOM-prefixed cases failed before the fix. Relevant suites passed with 1603 tests and 3 warnings. Ruff and git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 11:20

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.

🔵 Needs a closer look

Shared installation changes affect destructive reinstalls, and cross-platform validation remains pending.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Low severity Clarify nested .presetignore exclusion depends on root file presence

docs/​reference/​presets.md:81

Optional: Qualify this exclusion with the presence of a root .presetignore. When that file is absent, the loader returns no filter and nested .presetignore files are copied, as covered by test_nested_ignore_is_not_loaded. The current sentence suggests these files are always excluded.

1 resolved since last review

Review effort: Max

Document that nested .presetignore files are excluded only when a root .presetignore exists and are otherwise copied unchanged. No code changes; git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 11:44

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.

🔵 Needs a closer look

Shared filtering affects destructive reinstall paths, and cross-platform validation remains pending.

No open issues 🎉

Review effort: Max

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.

🔵 Needs a closer look

Shared installer changes need cross-platform confirmation, and malformed-pattern error handling remains incomplete.

1 open issue

Review effort: Max

Comment thread src/specify_cli/_install_ignore.py Outdated
@mnriem

mnriem commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback. If not applicable, please explain why

Catch regular expression errors from pathspec alongside ValueError, preserving the file-specific validation message. Add reversed-range regression coverage for preset and extension ignore files.

Validation: both new reversed-range cases failed before the fix. Relevant suites passed with 1605 tests and 3 warnings. Ruff and git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 14:58

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.

🔵 Needs a closer look

Shared filtering affects destructive reinstall paths, and the reported cross-platform CI validation remains pending.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Low severity Clarify glob negation to avoid re-including unintended docs files

docs/​reference/​presets.md:76

Optional clarification: docs/, !docs/, !docs/api.md re-includes all files under docs/, not just api.md. The parent-negation advice can therefore include development files an author intended to exclude. Show docs/* followed by !docs/api.md for keeping just that file, consistent with the extension guide's “Negation inside excluded directories” section.

1 resolved since last review

Review effort: Max

Show how to retain only docs/api.md by excluding docs/*, and explain that re-including the parent directory can restore its other contents. Documentation only; git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 15:23

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.

🔵 Needs a closer look

Shared installation behavior changes for both presets and extensions, while cross-platform validation remains pending.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Low severity Document malformed ignore patterns and pre-removal validation

docs/​reference/​presets.md:103

Optional: Mention malformed patterns alongside invalid UTF-8: load_install_ignore also raises PresetValidationError for a bare !, as covered by the shared contract tests. Naming local directories and extracted archives here would also clarify that both receive pre-removal validation. Keep the separate preset update exception.

Review effort: Max

@mnriem

mnriem commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Please address previously missed Copilot feedback. If not applicable, please explain why

@grafvonb

Copy link
Copy Markdown
Contributor Author

@mnriem I’ve been addressing Copilot feedback across successive review rounds. The latest comment concerns documenting the malformed-pattern validation added in an earlier round. That behavior is implemented and tested, but the documentation still mentions only invalid UTF-8. This clarification is applicable, and I’ll update it.

Document malformed patterns alongside invalid UTF-8 and identify local directories and extracted archives as using pre-removal validation. Preserve the separate preset update exception. Documentation only; git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 16:11

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.

🔵 Needs a closer look

Shared filtering affects installation replacements, and cross-platform validation remains pending.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Low severity Improve malformed ignore rule error diagnostics

src/​specify_cli/​_install_ignore.py:58

Optional: include the malformed rule's line number and normalized pattern in this error. The message currently names only the file, and a re.error position refers to the generated regular expression rather than a location in the ignore file. That makes a bad rule harder to find in a long file. Diagnose the offending line only after compilation fails, preserving the original cause without compiling valid ignore files twice.

Review effort: Max

@mnriem

mnriem commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Please address the previously missed Copilot feedback

Diagnose individual source rules only after compilation fails, retaining the original exception as the cause. Add shared coverage for line numbering, normalization, the first malformed rule, and single compilation of valid files.

Validation: eight malformed-input regression cases failed before the fix. Relevant suites passed with 1609 tests and 3 warnings. Ruff and git diff --check passed.

Assisted-by: Codex (model: GPT-6, autonomous)
Copilot AI max review requested due to automatic review settings October 21, 2026 16:35

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.

🔵 Needs a closer look

Shared filtering affects replacement installs that remove existing files, and cross-platform validation remains pending.

No open issues 🎉

Review effort: Max

@grafvonb

Copy link
Copy Markdown
Contributor Author

@mnriem done 👍🏻

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.

🔵 Needs a closer look

Shared extension and preset installation behavior warrants maintainer sign-off while cross-platform CI remains pending.

No open issues 🎉

Review effort: Max

@mnriem
mnriem merged commit 6fa3985 into github:main Oct 21, 2026
16 checks passed
@mnriem

mnriem commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author-awaiting Waiting on author response triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants