Skip to content

Add @command behave tags to all feature files#1141

Merged
spoorcc merged 8 commits intomainfrom
claude/behave-tags-pdf-reorganize-Dz22u
Apr 19, 2026
Merged

Add @command behave tags to all feature files#1141
spoorcc merged 8 commits intomainfrom
claude/behave-tags-pdf-reorganize-Dz22u

Conversation

@spoorcc
Copy link
Copy Markdown
Contributor

@spoorcc spoorcc commented Apr 15, 2026

Each feature file now carries a tag identifying the dfetch command it
exercises (e.g. @update, @check, @diff, @add, @remove, @report,
@Freeze, @import, @Validate, @format-patch, @update-patch).

Journey features that exercise multiple commands receive all relevant
tags (e.g. @update @check for journey-basic-usage.feature).

Files that already had @remote-svn retain it and gain the new command
tag on the same line (e.g. @remote-svn @update).

This makes it possible to run only the scenarios for a specific
command, for example:

behave features/ --tags=update

https://claude.ai/code/session_01BvyikyxX9c3sDXev8HdP4f

Summary by CodeRabbit

  • Documentation

    • Added an appendix that collects and groups real feature scenarios into a “Feature Examples” appendix; HTML shows inline expandable examples while PDF collects grouped appendices.
    • Added configuration and docs describing how scenarios are classified and included in each build format.
  • Chores

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

Warning

Rate limit exceeded

@spoorcc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 32 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a2f56ac-ebca-464d-8dac-2c96e6955877

📥 Commits

Reviewing files that changed from the base of the PR and between bef2ee7 and c22ccb1.

📒 Files selected for processing (5)
  • doc/_ext/latex_tabs.py
  • doc/_ext/scenario_directive.py
  • doc/conf.py
  • features/fetch-git-repo-with-submodule.feature
  • features/guard-against-overwriting-svn.feature

Walkthrough

Replaces single-mode scenario inclusion with a builder-aware system: HTML renders scenarios inline (with optional :inline:), while LaTeX/PDF defers and collects scenarios into a generated appendix grouped by feature tags. Adds new directive, placeholder/ref nodes, config, env lifecycle hooks, and appendix document.

Changes

Cohort / File(s) Summary
Scenario directive & handlers
doc/_ext/scenario_directive.py
Rewrote directive to support builder-dependent modes: HTML inline expansion (literalinclude) and PDF/LaTeX deferred appendix. Added ScenarioAppendixPlaceholder, ScenarioAppendixRef, ScenarioAppendixDirective, inline flag on ScenarioIncludeDirective, and handlers resolve_scenario_appendix_refs, process_scenario_appendix, purge_scenario_appendix, merge_scenario_appendix. Updated setup() to register config, directives, nodes, events and return extension metadata.
Documentation structure & config
doc/appendix/scenarios.rst, doc/conf.py, doc/index.rst
Added new appendix file and toctree entry; introduced scenario_non_command_tags config (default set in conf).
Feature file tags
features/*.feature (many files; see repo)
Added command/category tags (@add, @check, @update, @diff, @format-patch, @freeze, @import, @report, @validate, @remove, @update-patch, etc.) across ~52 feature files to support grouping/filtering.
Docs & guidance
AGENTS.md, doc/howto/contributing.rst
Updated contributor/test guidance to document command-tag usage and example behave invocation.

Sequence Diagram(s)

sequenceDiagram
    participant Sphinx as Sphinx App
    participant Directive as scenario-include/<br/>scenario-appendix
    participant Doctree as Doctree
    participant Resolver as doctree-resolved<br/>Handler
    participant Env as Sphinx Env
    participant Renderer as HTML/PDF Renderer

    Directive->>Env: record scenario metadata (PDF mode)
    Directive->>Doctree: insert inline nodes (HTML mode) or placeholder node (PDF mode)
    Sphinx->>Resolver: emit doctree-resolved
    Resolver->>Doctree: find & replace placeholders
    Resolver->>Env: retrieve collected scenarios grouped by tag
    Resolver->>Doctree: inject appendix sections (PDF mode)
    Doctree->>Renderer: final doctree for output generation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

documentation, testing

Suggested reviewers

  • ben-edna
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add @command behave tags to all feature files' accurately describes the main change: adding command-specific behave tags (@update, @check, @diff, etc.) to feature files throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/behave-tags-pdf-reorganize-Dz22u

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/_ext/scenario_directive.py`:
- Around line 179-196: The directive currently hardcodes "Scenario:" when
building the literalinclude bounds; update the parsing code in _all_scenarios()
to preserve the original header token (e.g., header = "Scenario" or "Scenario
Outline") alongside title and use that header when composing the start-after and
end-before lines in directive_rst (e.g., f":start-after: {header}: {title}" and
f":end-before: {header}: {title}" when applicable), ensuring end_before remains
empty for the last item as before.
- Around line 235-249: The appendix entry currently stores a single "source_doc"
and is removed when that one doc is reread, dropping references from other
pages; change the entry shape to track a set of referencing docs (e.g. replace
"source_doc" with "source_docs" as a set when creating the entry in
env.scenario_appendix_entries for feature_abs), update merging logic to add
env.docname into existing["source_docs"] when appending scenarios (alongside
existing["scenarios"].append), and update the purge/remove logic (the code that
deletes entries on document re-read) to remove the current env.docname from
existing["source_docs"] and only delete the whole entry if that set is empty.
Ensure all places that read/write "source_doc" are updated to use the new
"source_docs" set.
- Around line 349-352: The PDF/LaTeX appendix currently always emits the full
feature via _full_feature_content(entry["feature_abs"]) into a
nodes.literal_block (assigned to code and appended to feat_section), which
ignores the :scenario: filter; change the PDF path to either (A) build the
literal_block from only entry["scenarios"] (i.e., render and join only the
selected scenarios instead of calling _full_feature_content) when the builder is
LaTeX/PDF, or (B) detect the LaTeX/PDF builder (via app.builder.name or
buildername) and raise/emit a clear error/warning rejecting the :scenario:
option for PDF builds; update the code that constructs code =
nodes.literal_block(...) and the content source to use the chosen approach so
HTML and PDF outputs remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9f4a274-e2f9-42ba-bbf5-f63a6c85d8d2

📥 Commits

Reviewing files that changed from the base of the PR and between 7da5a8c and 3abcbbd.

📒 Files selected for processing (55)
  • doc/_ext/scenario_directive.py
  • doc/appendix/scenarios.rst
  • doc/conf.py
  • doc/index.rst
  • features/add-project-through-cli.feature
  • features/check-archive.feature
  • features/check-git-repo.feature
  • features/check-report-code-climate.feature
  • features/check-report-jenkins.feature
  • features/check-report-sarif.feature
  • features/check-specific-projects.feature
  • features/check-svn-repo.feature
  • features/checked-project-has-dependencies.feature
  • features/diff-in-git.feature
  • features/diff-in-svn.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/fetch-file-pattern-git.feature
  • features/fetch-file-pattern-svn.feature
  • features/fetch-git-repo-with-submodule.feature
  • features/fetch-git-repo.feature
  • features/fetch-single-file-git.feature
  • features/fetch-single-file-svn.feature
  • features/fetch-specific-project.feature
  • features/fetch-svn-repo.feature
  • features/fetch-with-ignore-git.feature
  • features/fetch-with-ignore-svn.feature
  • features/format-patch-in-git.feature
  • features/format-patch-in-svn.feature
  • features/freeze-archive.feature
  • features/freeze-inplace.feature
  • features/freeze-projects.feature
  • features/freeze-specific-projects.feature
  • features/guard-against-overwriting-git.feature
  • features/guard-against-overwriting-svn.feature
  • features/handle-invalid-metadata.feature
  • features/import-from-git.feature
  • features/import-from-svn.feature
  • features/interactive-add.feature
  • features/journey-basic-patching.feature
  • features/journey-basic-usage.feature
  • features/keep-license-in-project.feature
  • features/list-projects.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/patch-fuzzy-matching-git.feature
  • features/remove-project.feature
  • features/report-sbom-archive.feature
  • features/report-sbom-license.feature
  • features/report-sbom.feature
  • features/suggest-project-name.feature
  • features/update-patch-in-git.feature
  • features/update-patch-in-svn.feature
  • features/updated-project-has-dependencies.feature
  • features/validate-manifest.feature

Comment thread doc/_ext/scenario_directive.py Outdated
Comment thread doc/_ext/scenario_directive.py
Comment thread doc/_ext/scenario_directive.py Outdated
@spoorcc spoorcc force-pushed the claude/behave-tags-pdf-reorganize-Dz22u branch from 3abcbbd to ea1a3ee Compare April 15, 2026 20:30
@spoorcc
Copy link
Copy Markdown
Contributor Author

spoorcc commented Apr 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@spoorcc spoorcc force-pushed the claude/behave-tags-pdf-reorganize-Dz22u branch from ea1a3ee to b32e6eb Compare April 19, 2026 08:20
claude and others added 5 commits April 19, 2026 08:28
Each feature file now carries a tag identifying the dfetch command it
exercises (e.g. @update, @check, @diff, @add, @remove, @report,
@Freeze, @import, @Validate, @format-patch, @update-patch).

Journey features that exercise multiple commands receive all relevant
tags (e.g. @update @check for journey-basic-usage.feature).

Files that already had @remote-svn retain it and gain the new command
tag on the same line (e.g. @remote-svn @update).

This makes it possible to run only the scenarios for a specific
command, for example:

    behave features/ --tags=update

https://claude.ai/code/session_01BvyikyxX9c3sDXev8HdP4f
The scenario-include directive now has two modes:

HTML (unchanged):
  Scenarios are rendered inline as collapsible <details> blocks,
  exactly as before.

PDF / LaTeX:
  Scenarios are moved to an auto-generated appendix page grouped by
  dfetch command (update, check, add, …). In the place of the directive
  the text shows a cross-reference: "Scenarios: see <Feature title> in
  the appendix." The command grouping is derived from the @command
  behave tag added to each feature file in the previous commit.

New directive option :inline: keeps a scenario in its original
position even in PDF mode, for cases where the example is essential
reading in context:

    .. scenario-include:: ../features/journey-basic-usage.feature
       :inline:

New directive scenario-appendix renders the collected scenarios in
the PDF appendix; in HTML it emits an orientation note instead.

doc/appendix/scenarios.rst added as the appendix landing page; it is
wired into the index.rst toctree under a new "Appendix" caption.

https://claude.ai/code/session_01BvyikyxX9c3sDXev8HdP4f
Three changes:

1. Remove hardcoded _NON_COMMAND_TAGS, _TAG_LABELS, _TAG_ORDER constants.
   The directive no longer knows anything about dfetch commands.

2. Sort appendix sections alphabetically by tag.  The previous fixed
   ordering (update, check, add, …) was dfetch-specific.

3. Expose scenario_non_command_tags as a conf.py config value (default []).
   Any tag in that list is skipped when choosing the group tag for a
   feature file. dfetch's conf.py now sets:

       scenario_non_command_tags = ["remote-svn"]

   Section titles are derived from the tag itself (title-cased, hyphens
   replaced by spaces), so no label map is needed.

https://claude.ai/code/session_01BvyikyxX9c3sDXev8HdP4f
@spoorcc spoorcc force-pushed the claude/behave-tags-pdf-reorganize-Dz22u branch from b32e6eb to 812915d Compare April 19, 2026 08:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/_ext/scenario_directive.py`:
- Around line 306-313: After computing available = _all_scenarios(feature_abs)
and scenario_titles = self._requested_scenarios(available) in run(), validate
the requested titles before calling _render_pdf() and raise a directive error if
any requested title is missing or if the user specified :scenario: but no
matching scenarios were found; specifically, compare scenario_titles against
available (or check for requested vs matched counts) and call self.error(...)
with a clear message when there's a mismatch so the PDF path (_render_pdf) never
receives silently-omitted scenario requests.
- Around line 22-35: Add a user-facing documentation page that explains how to
use the scenario-include and scenario-appendix directives, documents the
scenario_non_command_tags configuration and the :inline: flag, and clearly
describes the PDF/LaTeX appendix behavior and grouping rules; include a short
example showing a scenario-include with and without :inline:, an example
scenario-appendix placement, and a note about how non-excluded behave tags
affect appendix grouping so readers can reproduce the behavior and configure it.
- Around line 257-299: The generated appendix IDs can collide because label =
f"appendix-{basename}" and group_label = f"appendix-{tag}" share a namespace and
basename alone ignores path; fix by introducing and using a collision-resistant
helper (e.g., _appendix_id(value, *, kind)) that slug-normalizes and namespaces
values (include file path or feature_abs for feature IDs, and prefix/group for
group IDs) and replace usages: set label = _appendix_id(feature_abs,
kind="feature"), group_label = _appendix_id(tag, kind="group"), and store the
same stable IDs in env.scenario_appendix_entries and in the ScenarioAppendixRef
node (ensure ScenarioAppendixRef["label"], ["group_label"] and keys in
env.scenario_appendix_entries use the new helper-generated IDs).
- Around line 421-458: When appendix_docname is None we should warn so authors
know examples will be omitted: add "from sphinx.util import logging" and create
a module logger (e.g. logger = logging.getLogger(__name__)), then inside the
loop where appendix_docname is checked (the block that currently does the else:
para += nodes.Text(f"See …")), call logger.warning with a clear message about
the missing ".. scenario-appendix::" directive and its effect (e.g. "PDF build
will omit deferred examples because scenario_appendix_docname is not set; add ..
scenario-appendix:: to include them"), passing location=ref_node to attach the
warning to the source location of the ScenarioAppendixRef node. Ensure the
warning runs only when appendix_docname is falsy so behavior for the existing
branch is unchanged.

In `@features/fetch-git-repo-with-submodule.feature`:
- Line 1: The feature file features/fetch-git-repo-with-submodule.feature is
currently tagged only with `@update` but also exercises dfetch report; add the
missing `@report` tag alongside `@update` at the top of the feature so command-based
test filtering includes both commands (i.e., change the feature tag line to
include both `@update` and `@report`); ensure the tag text matches the existing
tagging style used in other features.

In `@features/guard-against-overwriting-svn.feature`:
- Line 1: The feature file guard-against-overwriting-svn.feature only has the
`@update` tag but should follow the established pattern of using `@remote-svn`
alongside command tags; edit the file header to add the `@remote-svn` tag so the
top line reads both `@remote-svn` and `@update`, mirroring other SVN features like
fetch-svn-repo.feature and check-svn-repo.feature (i.e., include the `@remote-svn`
tag next to `@update`).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 43210ba6-65b1-4d6f-ba4e-099bfd7e98e0

📥 Commits

Reviewing files that changed from the base of the PR and between 3abcbbd and bef2ee7.

📒 Files selected for processing (57)
  • AGENTS.md
  • doc/_ext/scenario_directive.py
  • doc/appendix/scenarios.rst
  • doc/conf.py
  • doc/howto/contributing.rst
  • doc/index.rst
  • features/add-project-through-cli.feature
  • features/check-archive.feature
  • features/check-git-repo.feature
  • features/check-report-code-climate.feature
  • features/check-report-jenkins.feature
  • features/check-report-sarif.feature
  • features/check-specific-projects.feature
  • features/check-svn-repo.feature
  • features/checked-project-has-dependencies.feature
  • features/diff-in-git.feature
  • features/diff-in-svn.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/fetch-file-pattern-git.feature
  • features/fetch-file-pattern-svn.feature
  • features/fetch-git-repo-with-submodule.feature
  • features/fetch-git-repo.feature
  • features/fetch-single-file-git.feature
  • features/fetch-single-file-svn.feature
  • features/fetch-specific-project.feature
  • features/fetch-svn-repo.feature
  • features/fetch-with-ignore-git.feature
  • features/fetch-with-ignore-svn.feature
  • features/format-patch-in-git.feature
  • features/format-patch-in-svn.feature
  • features/freeze-archive.feature
  • features/freeze-inplace.feature
  • features/freeze-projects.feature
  • features/freeze-specific-projects.feature
  • features/guard-against-overwriting-git.feature
  • features/guard-against-overwriting-svn.feature
  • features/handle-invalid-metadata.feature
  • features/import-from-git.feature
  • features/import-from-svn.feature
  • features/interactive-add.feature
  • features/journey-basic-patching.feature
  • features/journey-basic-usage.feature
  • features/keep-license-in-project.feature
  • features/list-projects.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/patch-fuzzy-matching-git.feature
  • features/remove-project.feature
  • features/report-sbom-archive.feature
  • features/report-sbom-license.feature
  • features/report-sbom.feature
  • features/suggest-project-name.feature
  • features/update-patch-in-git.feature
  • features/update-patch-in-svn.feature
  • features/updated-project-has-dependencies.feature
  • features/validate-manifest.feature

Comment on lines +22 to +35
**PDF / LaTeX builds** automatically move examples to an appendix grouped by
the first non-excluded behave tag found on the feature file. The directive's
original location receives a cross-reference to the appendix entry instead.

Use the ``:inline:`` flag to keep a specific inclusion in place even when
building a PDF:

.. scenario-include:: ../features/fetch-git-repo.feature
:inline:

The appendix itself is rendered by placing the companion directive anywhere in
the document tree:

.. scenario-appendix::
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files doc/ | head -30

Repository: dfetch-org/dfetch

Length of output: 1100


🏁 Script executed:

cat -n doc/_ext/scenario_directive.py | head -100

Repository: dfetch-org/dfetch

Length of output: 4100


🏁 Script executed:

find doc -type f -name "*.rst" -o -name "*.md" | grep -E "(how-to|reference)" | head -20

Repository: dfetch-org/dfetch

Length of output: 226


🏁 Script executed:

rg -l "scenario-include|scenario-appendix|:inline:" doc/

Repository: dfetch-org/dfetch

Length of output: 304


🏁 Script executed:

cat -n doc/appendix/scenarios.rst | head -50

Repository: dfetch-org/dfetch

Length of output: 681


🏁 Script executed:

cat -n doc/_ext/scenario_directive.py | sed -n '510,530p'

Repository: dfetch-org/dfetch

Length of output: 920


🏁 Script executed:

rg "scenario-include|scenario-appendix" doc/appendix/scenarios.rst -B 2 -A 2

Repository: dfetch-org/dfetch

Length of output: 191


🏁 Script executed:

find doc/howto -type f -exec grep -l "scenario" {} \;

Repository: dfetch-org/dfetch

Length of output: 239


🏁 Script executed:

find doc -type f \( -name "*.rst" -o -name "*.md" \) | grep -E "(howto|reference)" | xargs grep -l "directive" 2>/dev/null | head -10

Repository: dfetch-org/dfetch

Length of output: 43


Add user-facing documentation for the scenario directives and PDF behavior.

This adds public directives (scenario-include, scenario-appendix), configuration (scenario_non_command_tags), and features (:inline: flag, PDF appendix grouping). While the module docstring documents these well, the repo requires user-visible changes in doc/howto/ or doc/reference/. Create a dedicated guide explaining how to use the scenario-include directive, the :inline: flag, and how PDF/LaTeX builds automatically organize examples via the appendix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/_ext/scenario_directive.py` around lines 22 - 35, Add a user-facing
documentation page that explains how to use the scenario-include and
scenario-appendix directives, documents the scenario_non_command_tags
configuration and the :inline: flag, and clearly describes the PDF/LaTeX
appendix behavior and grouping rules; include a short example showing a
scenario-include with and without :inline:, an example scenario-appendix
placement, and a note about how non-excluded behave tags affect appendix
grouping so readers can reproduce the behavior and configure it.

Comment on lines +257 to +299
basename = os.path.splitext(os.path.basename(feature_abs))[0]
label = f"appendix-{basename}"
tag = _group_tag(feature_abs, non_group_tags)
title = _feature_title(feature_abs)

# ----------------------------------------------------------
# Store entry so the appendix directive can render it later.
# The dict is keyed by absolute path to deduplicate across
# multiple RST files that reference the same feature.
# ----------------------------------------------------------
if not hasattr(env, "scenario_appendix_entries"):
env.scenario_appendix_entries = {}

if feature_abs not in env.scenario_appendix_entries:
env.scenario_appendix_entries[feature_abs] = {
"feature_file": feature_file,
"feature_abs": feature_abs,
"feature_title": title,
"group_tag": tag,
"label": label,
"source_docs": {env.docname},
"scenarios": list(scenario_titles),
}
else:
existing = env.scenario_appendix_entries[feature_abs]
existing["source_docs"].add(env.docname)
for s in scenario_titles:
if s not in existing["scenarios"]:
existing["scenarios"].append(s)

# ----------------------------------------------------------
# Return a deferred ScenarioAppendixRef block node. It is
# resolved to a full paragraph with make_refnode links during
# doctree-resolved, once the appendix document name is known.
# Sphinx's make_refnode handles LaTeX/HTML builder differences,
# producing a proper \hyperref in PDF output.
# ----------------------------------------------------------
ref_node = ScenarioAppendixRef()
ref_node["label"] = label
ref_node["reftitle"] = title
ref_node["group_tag"] = tag
ref_node["group_label"] = f"appendix-{tag}"
ref_node["scenario_count"] = len(scenario_titles)
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find current feature/tag pairs that would generate duplicate appendix IDs.

python - <<'PY'
from collections import defaultdict
from pathlib import Path

non_group_tags = {"remote-svn"}
ids = defaultdict(list)

for path in Path(".").rglob("*.feature"):
    tags = []
    try:
        lines = path.read_text(encoding="utf-8").splitlines()
    except UnicodeDecodeError:
        continue

    for line in lines:
        stripped = line.strip()
        if stripped.startswith("Feature:"):
            break
        if stripped.startswith("@"):
            tags.extend(token.lstrip("@") for token in stripped.split())

    group = next((tag for tag in tags if tag not in non_group_tags), "other")
    feature_id = f"appendix-{path.stem}"
    group_id = f"appendix-{group}"
    ids[feature_id].append(f"feature {path}")
    ids[group_id].append(f"group {group} from {path}")

for target_id, owners in sorted(ids.items()):
    if len(owners) > 1:
        print(target_id)
        for owner in owners:
            print(f"  - {owner}")
PY

Repository: dfetch-org/dfetch

Length of output: 3136


🏁 Script executed:

# First, read the actual code at the mentioned lines
head -n 310 doc/_ext/scenario_directive.py | tail -n 55

Repository: dfetch-org/dfetch

Length of output: 2676


🏁 Script executed:

# Also check the other location mentioned (lines 378-388)
sed -n '370,395p' doc/_ext/scenario_directive.py

Repository: dfetch-org/dfetch

Length of output: 1095


🏁 Script executed:

# Check if any .feature file has a tag matching its own basename
python - <<'PY'
from pathlib import Path

non_group_tags = {"remote-svn"}

for path in sorted(Path(".").rglob("*.feature")):
    basename = path.stem
    tags = []
    try:
        lines = path.read_text(encoding="utf-8").splitlines()
    except UnicodeDecodeError:
        continue

    for line in lines:
        stripped = line.strip()
        if stripped.startswith("Feature:"):
            break
        if stripped.startswith("@"):
            tags.extend(token.lstrip("@") for token in stripped.split())

    # Check for basename matching a tag
    for tag in tags:
        if tag == basename or tag.startswith(basename + "-") or basename.endswith("-" + tag):
            print(f"Match found: {path.stem} has tag {tag}")
            print(f"  file: {path}")
            print(f"  tags: {tags}")
            break
PY

Repository: dfetch-org/dfetch

Length of output: 158


🏁 Script executed:

# Check for duplicate basenames across different directories
python - <<'PY'
from collections import defaultdict
from pathlib import Path

basenames = defaultdict(list)

for path in sorted(Path(".").rglob("*.feature")):
    basename = path.stem
    basenames[basename].append(str(path))

for basename in sorted(basenames.keys()):
    paths = basenames[basename]
    if len(paths) > 1:
        print(f"Duplicate basename '{basename}':")
        for p in paths:
            print(f"  - {p}")
PY

Repository: dfetch-org/dfetch

Length of output: 43


Use collision-safe IDs for group and feature appendix targets.

The current ID generation can produce collisions: label = f"appendix-{basename}" (line 264) and group_label = f"appendix-{tag}" (line 296) share the same namespace. A feature file with basename matching its group tag (e.g., update.feature with @update tag) would generate duplicate section IDs. Additionally, if the same feature basename appears in different directories, they'd share a single ID, causing ambiguous cross-references. The proposed fix using a collision-resistant _appendix_id() helper with slug normalization resolves this:

Proposed fix
+def _appendix_id(prefix: str, value: str) -> str:
+    """Return a stable docutils-safe appendix target id."""
+    slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
+    return f"scenario-appendix-{prefix}-{slug or 'item'}"
+
+
 def _tag_section_title(tag: str) -> str:
     """Human-readable section title derived from *tag* alone."""
     return tag.replace("-", " ").title()
@@
         env = self._env()
         non_group_tags = frozenset(getattr(env.config, "scenario_non_command_tags", []))
         basename = os.path.splitext(os.path.basename(feature_abs))[0]
-        label = f"appendix-{basename}"
         tag = _group_tag(feature_abs, non_group_tags)
+        label = _appendix_id("feature", feature_file)
+        group_label = _appendix_id("group", tag)
         title = _feature_title(feature_abs)
@@
         ref_node["label"] = label
         ref_node["reftitle"] = title
         ref_node["group_tag"] = tag
-        ref_node["group_label"] = f"appendix-{tag}"
+        ref_node["group_label"] = group_label
         ref_node["scenario_count"] = len(scenario_titles)
         return [ref_node]
@@
     for tag in sorted(by_tag):
         tag_entries = sorted(by_tag[tag], key=lambda e: e["feature_title"])
-        label = f"appendix-{tag}"
+        label = _appendix_id("group", tag)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/_ext/scenario_directive.py` around lines 257 - 299, The generated
appendix IDs can collide because label = f"appendix-{basename}" and group_label
= f"appendix-{tag}" share a namespace and basename alone ignores path; fix by
introducing and using a collision-resistant helper (e.g., _appendix_id(value, *,
kind)) that slug-normalizes and namespaces values (include file path or
feature_abs for feature IDs, and prefix/group for group IDs) and replace usages:
set label = _appendix_id(feature_abs, kind="feature"), group_label =
_appendix_id(tag, kind="group"), and store the same stable IDs in
env.scenario_appendix_entries and in the ScenarioAppendixRef node (ensure
ScenarioAppendixRef["label"], ["group_label"] and keys in
env.scenario_appendix_entries use the new helper-generated IDs).

Comment thread doc/_ext/scenario_directive.py
Comment thread doc/_ext/scenario_directive.py
Comment thread features/fetch-git-repo-with-submodule.feature Outdated
Comment thread features/guard-against-overwriting-svn.feature Outdated
@spoorcc spoorcc merged commit ac7dd04 into main Apr 19, 2026
39 checks passed
@spoorcc spoorcc deleted the claude/behave-tags-pdf-reorganize-Dz22u branch April 19, 2026 09:45
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