Skip to content

fix: ensure idempotent project-relative path rewriting in CommandRegistrar - #4553

Open
darion-yaphet wants to merge 3 commits into
github:mainfrom
darion-yaphet:fix/idempotent-path-rewriting
Open

darion-yaphet wants to merge 3 commits into
github:mainfrom
darion-yaphet:fix/idempotent-path-rewriting

Conversation

@darion-yaphet

@darion-yaphet darion-yaphet commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Description

Refactors CommandRegistrar.rewrite_project_relative_paths() from multi-step string replacements and sequential regex passes into a unified single-pass regex callback.

Key improvements:

  1. Single-pass regex callback: Replaces the fragile pattern of three sequential string replacements followed by three re.sub passes and trailing .replace(".specify/.specify/", ".specify/") cleanup with a clean, unified regex match callback.
  2. Deterministic routing: Consistently routes deeper parent-relative references (../../, ../../../) to root .specify/<target>/ while preserving extension-local scoping for top-level scripts when extension_id is supplied.
  3. Expanded delimiter & assignment support: Recognizes Markdown punctuation enclosures ([], (), {}, <>), quotes, backticks, and option/variable assignment delimiters (=, e.g., --template=../../templates/spec.md and SCRIPT=../../scripts/bash/run.sh).
  4. Idempotency guard: Naturally guards already-normalized .specify/ paths from duplicate prefixing across repeated passes without needing post-hoc string stripping.

Testing

  • Ran existing tests with pytest tests/test_extensions.py (542 passed)
  • Ran idempotency, delimiter, and assignment test suite with pytest tests/test_extensions.py -k "test_rewrite_project_relative_paths" (5 passed)
  • Ran linter check with ruff check src/specify_cli/agents.py tests/test_extensions.py (0 errors)

AI Disclosure

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

AI disclosure: Implemented with Google Antigravity using Gemini 3.8 Flash (High) in human-supervised mode for code refactoring, test case generation, and addressing review feedback.

@mnriem mnriem added triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review author-needs-disclosure AI use, or the agent/model/settings behind it, not disclosed per CONTRIBUTING author-awaiting Waiting on author response labels Sep 12, 2026
@mnriem

mnriem commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

The delimiter and deeper-parent-path improvements have useful regression coverage. Before merge, please preserve parent-relative paths following =: --template=../../templates/spec.md and SCRIPT=../../scripts/bash/run.sh were rewritten previously but are now left unchanged. Add regression cases for these alongside the existing idempotency checks.

Please also clarify the description: the supplied samples already remain stable under repeated rewriting on the old code; the demonstrated improvements are routing and delimiter handling. Finally, complete this PR’s AI disclosure with the tool, model, mode/settings, and extent of assistance—the checkbox alone does not describe the contribution.

Drafted for @mnriem by GitHub Copilot (model: GPT-6 Astra; comment drafting).

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 refactor preserves routing behavior and includes focused regression coverage.

Pull request overview

Refactors project-relative path rewriting into an idempotent, single-pass implementation.

Changes:

  • Consolidates path normalization into one regex callback.
  • Adds regression coverage for idempotency, delimiters, and invalid inputs.
File summaries
File Description
src/specify_cli/agents.py Implements unified path rewriting.
tests/test_extensions.py Adds comprehensive regression tests.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…strar

Replace the fragile pattern of three sequential string replacements followed by three re.sub calls and trailing `.replace(".specify/.specify/", ".specify/")` / `.replace(".specify.specify/", ".specify/")` patches in CommandRegistrar.rewrite_project_relative_paths.

Consolidate the transformation into a unified regex match callback that:
- Inspects matched path prefixes (`.specify/`, `../`, `./`, `/`, or bare)
- Naturally guards already-normalized `.specify/` paths from double-prefixing
- Directs parent relative references (`../`) to root `.specify/<target>/`
- Preserves extension-local script scoping when extension_id is provided
- Expands boundary delimiters to include Markdown brackets, parentheses, braces, angle brackets, and backticks

Add unit tests in tests/test_extensions.py covering repeated passes for idempotency, markdown enclosure delimiters, and edge-case inputs.

Assisted-by: Antigravity (supervised)
…lative_paths

Extend the delimiter boundary character class in CommandRegistrar.rewrite_project_relative_paths
to include '=', ensuring option flags (e.g., '--template=../../templates/spec.md') and
environment variable assignments (e.g., 'SCRIPT=../../scripts/bash/run.sh') continue to
be rewritten properly.

Add regression test coverage in tests/test_extensions.py covering '=' assignments and
verifying repeated passes for idempotency.

Assisted-by: Google Antigravity (model: Gemini 3.8 Flash, supervised)
@darion-yaphet
darion-yaphet force-pushed the fix/idempotent-path-rewriting branch from 661957c to f6ecaa9 Compare September 20, 2026 08:45
@darion-yaphet

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and catching the = assignment case!

I have addressed the feedback in commit f6ecaa9:

  1. Preserved = prefix: Added = to the boundary delimiter class ((^|[\s"'([{<=])), ensuring CLI option flags (like --template=../../templates/spec.md) and environment variable assignments (like SCRIPT=../../scripts/bash/run.sh`) are properly rewritten.
  2. Added regression coverage: Added test cases for = followed by parent-relative and extension-scoped paths in tests/test_extensions.py, asserting correct transformation and idempotency across repeated passes.
  3. Clarified PR description: Clarified in the description that the improvements focus on the single-pass regex architecture, deeper parent-path routing, and expanded delimiter/assignment handling.
  4. Completed AI disclosure: Updated the PR body with the full AI disclosure (tool, model, supervised mode, and extent of assistance).

Drafted on behalf of @darion-yaphet by Google Antigravity (model: Gemini 3.8 Flash (High), human-supervised; comment drafting and code refinement).

@mnriem mnriem removed author-needs-disclosure AI use, or the agent/model/settings behind it, not disclosed per CONTRIBUTING author-awaiting Waiting on author response labels Sep 21, 2026
@mnriem
mnriem requested a balanced review from Copilot September 21, 2026 19:33

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.

Copilot review overview

🟡 Changes recommended

Parent-relative paths after valid punctuation or shell operators are no longer rewritten.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread src/specify_cli/agents.py Outdated
return text.replace(".specify/.specify/", ".specify/").replace(
".specify.specify/", ".specify/"
pattern = re.compile(
r"""(^|[\s`"'(\[{<=])(\.specify/|(?:\.\./)+|(?:\.?/))?(scripts|memory|templates)/"""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

../ now matches independently of the delimiter allowlist, so parent-relative paths after ;, :, &&, and other operators rewrite to .specify/.... Bare scripts/ / memory/ / templates/ still require a recognized boundary. Covered in test_rewrite_project_relative_paths_punctuation_and_shell_operator_boundaries.

The delimiter allowlist skipped inputs such as run;../../scripts. Match
../ independently of that list, keep the boundary guard for bare paths,
and add punctuation/shell-operator regression cases.

Assisted-by: Google Antigravity (model: Gemini 3.8 Flash, supervised)

This branch has not been deployed

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

Labels

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