bmsdna-devtools 0.48.0__tar.gz → 0.49.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/.gitignore +2 -0
  2. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/PKG-INFO +3 -1
  3. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/README.md +2 -0
  4. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/worktree.py +21 -0
  5. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/pyproject.toml +1 -1
  6. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_worktree_clean.py +73 -0
  7. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/uv.lock +1 -1
  8. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/.github/workflows/auto-release.yml +0 -0
  9. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/.github/workflows/python-publish.yml +0 -0
  10. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/.github/workflows/python-test.yml +0 -0
  11. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/.python-version +0 -0
  12. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/__init__.py +0 -0
  13. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/_api_dump.py +0 -0
  14. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/ado_auth.py +0 -0
  15. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/ado_issue.py +0 -0
  16. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/agent_mentions.py +0 -0
  17. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/api_usage.py +0 -0
  18. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/app_service_logs.py +0 -0
  19. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/azure_arm.py +0 -0
  20. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/azure_auth.py +0 -0
  21. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/bdt_config.py +0 -0
  22. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/business_user.py +0 -0
  23. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/cli.py +0 -0
  24. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/cli_tools.py +0 -0
  25. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/commit.py +0 -0
  26. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/dead_code.py +0 -0
  27. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/env_config.py +0 -0
  28. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/env_files.py +0 -0
  29. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/find_injection.py +0 -0
  30. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/find_repo.py +0 -0
  31. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/gh_args.py +0 -0
  32. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/gh_issue.py +0 -0
  33. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/gh_pr.py +0 -0
  34. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/github_api.py +0 -0
  35. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/gitrepo.py +0 -0
  36. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/injection_frontend.py +0 -0
  37. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/injection_python.py +0 -0
  38. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/issue_do.py +0 -0
  39. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/issue_show.py +0 -0
  40. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/issue_watch.py +0 -0
  41. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint.py +0 -0
  42. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint_findings.py +0 -0
  43. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint_models.py +0 -0
  44. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint_sql.py +0 -0
  45. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint_sql_files.py +0 -0
  46. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint_tooling.py +0 -0
  47. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/lint_typescript.py +0 -0
  48. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/logs.py +0 -0
  49. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_build.py +0 -0
  50. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_choice.py +0 -0
  51. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_create_args.py +0 -0
  52. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_info.py +0 -0
  53. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_issue_link.py +0 -0
  54. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_labels.py +0 -0
  55. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_markdown.py +0 -0
  56. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_merge_defaults.py +0 -0
  57. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pr_ref.py +0 -0
  58. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/pull.py +0 -0
  59. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/testdb.py +0 -0
  60. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/translate.py +0 -0
  61. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/__init__.py +0 -0
  62. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/cli.py +0 -0
  63. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/pipeline.py +0 -0
  64. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/pricing.py +0 -0
  65. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/report.py +0 -0
  66. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/00_schema.sql +0 -0
  67. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/01_ingest_claude.sql +0 -0
  68. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/01_ingest_codex.sql +0 -0
  69. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/02_messages.sql +0 -0
  70. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/03_markers.sql +0 -0
  71. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/04_facts.sql +0 -0
  72. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/bmsdna/devtools/usage/sql/05_views.sql +0 -0
  73. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/justfile +0 -0
  74. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/skills/bmsdna-devtools/SKILL.md +0 -0
  75. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/conftest.py +0 -0
  76. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/fake_github.py +0 -0
  77. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_ado_auth.py +0 -0
  78. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_ado_issue.py +0 -0
  79. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_ado_issue_flow.py +0 -0
  80. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_agent_mentions.py +0 -0
  81. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_api_usage.py +0 -0
  82. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_app_service_logs.py +0 -0
  83. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_azure_arm.py +0 -0
  84. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_bdt_config.py +0 -0
  85. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_business_user.py +0 -0
  86. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_cleanup.py +0 -0
  87. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_commit.py +0 -0
  88. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_issue.py +0 -0
  89. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_pr_create.py +0 -0
  90. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_pr_id.py +0 -0
  91. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_pull.py +0 -0
  92. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_tools.py +0 -0
  93. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_cli_version.py +0 -0
  94. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_commit.py +0 -0
  95. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_env_config.py +0 -0
  96. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_env_files.py +0 -0
  97. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_find_injection.py +0 -0
  98. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_find_repo.py +0 -0
  99. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_gh_issue.py +0 -0
  100. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_gh_pr.py +0 -0
  101. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_github_api.py +0 -0
  102. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_gitrepo.py +0 -0
  103. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_issue_do.py +0 -0
  104. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_issue_for_me.py +0 -0
  105. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_issue_show.py +0 -0
  106. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_issue_watch.py +0 -0
  107. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_lint.py +0 -0
  108. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_lint_models.py +0 -0
  109. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_lint_sql.py +0 -0
  110. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_lint_sql_files.py +0 -0
  111. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_lint_tooling.py +0 -0
  112. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_lint_typescript.py +0 -0
  113. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_logs.py +0 -0
  114. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_build.py +0 -0
  115. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_build_create_options.py +0 -0
  116. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_build_flow.py +0 -0
  117. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_choice.py +0 -0
  118. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_create_args.py +0 -0
  119. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_description_not_truncated.py +0 -0
  120. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_info.py +0 -0
  121. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_issue_link.py +0 -0
  122. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_labels.py +0 -0
  123. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_markdown.py +0 -0
  124. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pr_merge_defaults.py +0 -0
  125. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_pull.py +0 -0
  126. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_testdb.py +0 -0
  127. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_translate.py +0 -0
  128. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_usage.py +0 -0
  129. {bmsdna_devtools-0.48.0 → bmsdna_devtools-0.49.0}/tests/test_wait_timeout.py +0 -0
@@ -219,3 +219,5 @@ __marimo__/
219
219
 
220
220
  # CodeGraph local index
221
221
  .codegraph/
222
+ .worktrees/
223
+ .claude/worktrees/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bmsdna-devtools
3
- Version: 0.48.0
3
+ Version: 0.49.0
4
4
  Summary: Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects
5
5
  Requires-Python: >=3.14
6
6
  Requires-Dist: azure-identity>=1.19.0
@@ -645,6 +645,8 @@ Creates `.worktrees/<name>` branched from `--base`. It first fetches `origin/<ba
645
645
  a local base that is ahead of or has diverged from the remote is kept as is. Then it initializes submodules
646
646
  (unless `--no-submodules`), and copies an env file into the new worktree as
647
647
  `.env` (auto-detects `.local_env` then `.env` if `--env-file` isn't given).
648
+ It also adds `.worktrees/` and `.claude/worktrees/` to the repo root's `.gitignore` if Git
649
+ doesn't already ignore them, and reminds you to commit the change.
648
650
 
649
651
  ## `bdt find-repo`
650
652
 
@@ -627,6 +627,8 @@ Creates `.worktrees/<name>` branched from `--base`. It first fetches `origin/<ba
627
627
  a local base that is ahead of or has diverged from the remote is kept as is. Then it initializes submodules
628
628
  (unless `--no-submodules`), and copies an env file into the new worktree as
629
629
  `.env` (auto-detects `.local_env` then `.env` if `--env-file` isn't given).
630
+ It also adds `.worktrees/` and `.claude/worktrees/` to the repo root's `.gitignore` if Git
631
+ doesn't already ignore them, and reminds you to commit the change.
630
632
 
631
633
  ## `bdt find-repo`
632
634
 
@@ -96,6 +96,26 @@ def _start_point(root: Path, base: str) -> tuple[str, bool]:
96
96
  return base, False
97
97
 
98
98
 
99
+ def _ensure_worktree_gitignore(root: Path) -> None:
100
+ missing = [
101
+ entry
102
+ for entry in (".worktrees/", ".claude/worktrees/")
103
+ if not _git_ok(["check-ignore", "-q", entry], root)
104
+ ]
105
+ if not missing:
106
+ return
107
+ gitignore = root / ".gitignore"
108
+ if gitignore.is_symlink():
109
+ print(f"warning: {gitignore} is a symlink; not touching it. Add {' and '.join(missing)} to your ignore rules yourself.", file=sys.stderr)
110
+ return
111
+ content = gitignore.read_text() if gitignore.exists() else ""
112
+ with gitignore.open("a") as f:
113
+ if content and not content.endswith("\n"):
114
+ f.write("\n")
115
+ f.write("\n".join(missing) + "\n")
116
+ print(f"Updated {gitignore} to ignore worktree directories; please commit it.")
117
+
118
+
99
119
  def create(
100
120
  name: str,
101
121
  base: str = "dev",
@@ -112,6 +132,7 @@ def create(
112
132
  start, remote_start = _start_point(root, base)
113
133
  # --no-track: the new branch must not push to / compare against origin/<base>
114
134
  _run(["git", "worktree", "add", *(["--no-track"] if remote_start else []), str(path), "-b", name, start], cwd=root)
135
+ _ensure_worktree_gitignore(root)
115
136
 
116
137
  if submodules and (root / ".gitmodules").exists():
117
138
  _run(["git", "submodule", "update", "--init"], cwd=path)
@@ -10,7 +10,7 @@ packages = ["bmsdna"]
10
10
 
11
11
  [project]
12
12
  name = "bmsdna-devtools"
13
- version = "0.48.0"
13
+ version = "0.49.0"
14
14
  description = "Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects"
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.14" # pgdevkit>=0.7.1 requires 3.14; was >=3.11 before adding it as a dependency
@@ -48,6 +48,67 @@ def add_submodule(repo, sub_repo, sub_path="vendor/sub"):
48
48
  # --- create (`bdt worktree`) -------------------------------------------------
49
49
 
50
50
 
51
+ def test_create_creates_gitignore_when_absent(tmp_path, capsys) -> None:
52
+ repo = init_repo(tmp_path / "repo")
53
+
54
+ create("my-feature", base="main", submodules=False, root=repo)
55
+
56
+ assert (repo / ".gitignore").read_text() == ".worktrees/\n.claude/worktrees/\n"
57
+ for entry in (".worktrees/", ".claude/worktrees/"):
58
+ assert (
59
+ _git(["check-ignore", "-q", entry], cwd=repo, check=False).returncode == 0
60
+ )
61
+ out = capsys.readouterr().out
62
+ assert f"Updated {repo / '.gitignore'}" in out
63
+ assert "please commit it" in out
64
+
65
+
66
+ @pytest.mark.parametrize(
67
+ "existing, missing",
68
+ [(".worktrees/", ".claude/worktrees/"), (".claude/worktrees/", ".worktrees/")],
69
+ )
70
+ @pytest.mark.parametrize("ending", ["", "\n"])
71
+ def test_create_appends_only_missing_gitignore_entry(
72
+ tmp_path, existing, missing, ending
73
+ ) -> None:
74
+ repo = init_repo(tmp_path / "repo")
75
+ (repo / ".gitignore").write_text(existing + ending)
76
+
77
+ create("my-feature", base="main", submodules=False, root=repo)
78
+
79
+ assert (repo / ".gitignore").read_text() == f"{existing}\n{missing}\n"
80
+
81
+
82
+ def test_create_gitignore_is_idempotent(tmp_path, capsys) -> None:
83
+ repo = init_repo(tmp_path / "repo")
84
+ create("first-feature", base="main", submodules=False, root=repo)
85
+ gitignore = repo / ".gitignore"
86
+ content = gitignore.read_bytes()
87
+ modified = gitignore.stat().st_mtime_ns
88
+ capsys.readouterr()
89
+
90
+ create("second-feature", base="main", submodules=False, root=repo)
91
+
92
+ assert gitignore.read_bytes() == content
93
+ assert gitignore.stat().st_mtime_ns == modified
94
+ assert "Updated" not in capsys.readouterr().out
95
+
96
+
97
+ @pytest.mark.parametrize(
98
+ "patterns", ["/.worktrees/\n/.claude/worktrees/\n", ".worktrees\n.claude/\n"]
99
+ )
100
+ def test_create_does_not_duplicate_equivalent_gitignore_patterns(
101
+ tmp_path, capsys, patterns
102
+ ) -> None:
103
+ repo = init_repo(tmp_path / "repo")
104
+ (repo / ".gitignore").write_text(patterns)
105
+
106
+ create("my-feature", base="main", submodules=False, root=repo)
107
+
108
+ assert (repo / ".gitignore").read_text() == patterns
109
+ assert "Updated" not in capsys.readouterr().out
110
+
111
+
51
112
  def test_create_hints_plain_bdt_pull_when_base_is_not_main_or_master(tmp_path, capsys) -> None:
52
113
  repo = init_repo(tmp_path / "repo")
53
114
  _git(["branch", "dev"], cwd=repo)
@@ -782,3 +843,15 @@ def test_clean_current_worktree_removes_a_worktree_containing_a_submodule(tmp_pa
782
843
  clean_current_worktree(path, confirm=True, keep_db=True, pg_host="localhost", pg_port=54322, pg_user="tester")
783
844
 
784
845
  assert not path.exists()
846
+
847
+
848
+ def test_create_does_not_follow_gitignore_symlink(tmp_path, capsys) -> None:
849
+ repo = init_repo(tmp_path / "repo")
850
+ target = tmp_path / "shared-ignore"
851
+ target.write_text("*.log\n")
852
+ (repo / ".gitignore").symlink_to(target)
853
+
854
+ create("my-feature", base="main", submodules=False, root=repo)
855
+
856
+ assert target.read_text() == "*.log\n"
857
+ assert "symlink" in capsys.readouterr().err
@@ -51,7 +51,7 @@ wheels = [
51
51
 
52
52
  [[package]]
53
53
  name = "bmsdna-devtools"
54
- version = "0.48.0"
54
+ version = "0.49.0"
55
55
  source = { editable = "." }
56
56
  dependencies = [
57
57
  { name = "azure-identity" },