rollout-cli 0.4.2__tar.gz → 0.5.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 (120) hide show
  1. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/mass-update/SKILL.md +8 -1
  2. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/mass-update/scripts/mass-update.sh +32 -4
  3. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/CHANGELOG.md +6 -0
  4. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/PKG-INFO +1 -1
  5. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/pyproject.toml +1 -1
  6. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_mass_update.py +60 -0
  7. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/uv.lock +1 -1
  8. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/agent-config/SKILL.md +0 -0
  9. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  10. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  11. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/ask-colleague/SKILL.md +0 -0
  12. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  13. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  14. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  15. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  16. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  17. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  18. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/cicd/SKILL.md +0 -0
  19. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  20. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  21. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  22. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  23. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  24. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/SKILL.md +0 -0
  25. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  26. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  27. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  28. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  29. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  30. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  31. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  32. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  33. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  34. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  35. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/run-tests/SKILL.md +0 -0
  36. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  37. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  38. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  39. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  40. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  41. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/think/SKILL.md +0 -0
  42. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/think/scripts/think.sh +0 -0
  43. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/version-bump/SKILL.md +0 -0
  44. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  45. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.claude/skills.local.yaml.example +0 -0
  46. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.devague/frames/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
  47. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
  48. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.devague/plans/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
  49. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
  50. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.flake8 +0 -0
  51. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.github/workflows/publish.yml +0 -0
  52. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.github/workflows/tests.yml +0 -0
  53. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.gitignore +0 -0
  54. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/.markdownlint-cli2.yaml +0 -0
  55. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/AGENTS.colleague.md +0 -0
  56. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/CLAUDE.md +0 -0
  57. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/LICENSE +0 -0
  58. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/README.md +0 -0
  59. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/culture.yaml +0 -0
  60. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
  61. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/docs/plans/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
  62. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/docs/skill-sources.md +0 -0
  63. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
  64. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/docs/specs/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
  65. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/apache-relicense/apply.sh +0 -0
  66. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/apache-relicense/check.sh +0 -0
  67. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/apache-relicense/files/LICENSE +0 -0
  68. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/apache-relicense/manifest.toml +0 -0
  69. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/README.md +0 -0
  70. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/apply.sh +0 -0
  71. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/check.sh +0 -0
  72. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +0 -0
  73. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/files/prompts/explore.md +0 -0
  74. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/files/prompts/review.md +0 -0
  75. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/files/prompts/write.md +0 -0
  76. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/files/scripts/ask-colleague.sh +0 -0
  77. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/recipes/ask-colleague-revendor/manifest.toml +0 -0
  78. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/__init__.py +0 -0
  79. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/__main__.py +0 -0
  80. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/__init__.py +0 -0
  81. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/__init__.py +0 -0
  82. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/apply.py +0 -0
  83. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/cli.py +0 -0
  84. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/doctor.py +0 -0
  85. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/explain.py +0 -0
  86. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/learn.py +0 -0
  87. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/overview.py +0 -0
  88. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/plan.py +0 -0
  89. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_commands/whoami.py +0 -0
  90. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_errors.py +0 -0
  91. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/cli/_output.py +0 -0
  92. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/__init__.py +0 -0
  93. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/bump.py +0 -0
  94. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/devex_pr.py +0 -0
  95. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/gitops.py +0 -0
  96. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/orchestrate.py +0 -0
  97. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/preflight.py +0 -0
  98. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/recipe.py +0 -0
  99. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/report.py +0 -0
  100. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/core/targets.py +0 -0
  101. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/explain/__init__.py +0 -0
  102. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/rollout/explain/catalog.py +0 -0
  103. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/sonar-project.properties +0 -0
  104. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/__init__.py +0 -0
  105. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_apache_relicense.py +0 -0
  106. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_apply_cmd.py +0 -0
  107. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_bump.py +0 -0
  108. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_cli.py +0 -0
  109. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_cli_introspection.py +0 -0
  110. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_cli_wiring.py +0 -0
  111. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_devex_pr.py +0 -0
  112. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_e2e_apache_relicense.py +0 -0
  113. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_explain_catalog.py +0 -0
  114. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_gitops.py +0 -0
  115. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_orchestrate.py +0 -0
  116. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_plan_cmd.py +0 -0
  117. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_preflight.py +0 -0
  118. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_recipe.py +0 -0
  119. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_report.py +0 -0
  120. {rollout_cli-0.4.2 → rollout_cli-0.5.0}/tests/test_targets.py +0 -0
@@ -43,12 +43,18 @@ gone. The new mechanism:
43
43
 
44
44
  ```bash
45
45
  bash .claude/skills/mass-update/scripts/mass-update.sh \
46
- --repos <owner/repo[,owner/repo...]> <ops> [--pr] [--keep-clone]
46
+ --repos <owner/repo[,owner/repo...]> <ops> [--slug SLUG] [--pr] [--keep-clone]
47
47
  ```
48
48
 
49
49
  Run it from the rollout-cli repo root. `--repos` takes comma-separated GitHub
50
50
  slugs (e.g. `agentculture/widget-cli`).
51
51
 
52
+ The work branch defaults to `mass-update/<owner>-<repo>`. Pass `--slug SLUG` to
53
+ suffix it as `mass-update/<owner>-<repo>-<slug>`, so several independent updates
54
+ to the same repo can coexist as distinct branches (and distinct PRs) instead of
55
+ colliding on the one default branch name. `SLUG` must be ref-safe — letters,
56
+ digits, `.`, `_`, `-`; start with a letter or digit; no trailing `.` or `-`.
57
+
52
58
  ## Op shapes (combinable in one call)
53
59
 
54
60
  | Op | What it does |
@@ -65,6 +71,7 @@ All ops given in one call are applied to each clone. At least one op is required
65
71
  |------|--------|
66
72
  | *(none)* | **PREVIEW.** Clone, apply ops, print diff, cleanup. **Zero outward effect.** Always start here. |
67
73
  | `--pr` | Clone, apply, commit, push, open PR, file a companion tracking issue via `communicate/scripts/post-issue.sh`, cleanup. Both the PR URL and issue URL are printed. |
74
+ | `--slug SLUG` | Suffix the work branch: `mass-update/<owner>-<repo>-<slug>` (default has no suffix). Use when a repo may receive more than one independent mass-update so the branches don't collide. |
68
75
  | `--keep-clone` | Retain the scratch clone on exit (debug). |
69
76
 
70
77
  ## Worked example — propagate a LICENSE to a sibling repo
@@ -28,6 +28,9 @@ Targets:
28
28
  --repos a/b,c/d Comma-separated GitHub slugs (owner/repo). Required.
29
29
 
30
30
  Other:
31
+ --slug SLUG Suffix the work branch: mass-update/<owner>-<repo>-SLUG
32
+ (default: mass-update/<owner>-<repo>). Lets several
33
+ independent updates to the same repo coexist.
31
34
  -h, --help Show this help.
32
35
  EOF
33
36
  }
@@ -47,6 +50,7 @@ EDITS=()
47
50
  DO_PR=0
48
51
  KEEP_CLONE=0
49
52
  PENDING_FILE=""
53
+ SLUG=""
50
54
 
51
55
  _push_file() {
52
56
  [[ -n "$PENDING_FILE" ]] || { echo "error: $([[ "$1" == "from" ]] && echo --from || echo --content) given before any --file DEST" >&2; exit 2; }
@@ -93,6 +97,11 @@ while [[ $# -gt 0 ]]; do
93
97
  EDITS+=("$2")
94
98
  shift 2
95
99
  ;;
100
+ --slug)
101
+ need_value "$#" "$1"
102
+ SLUG="$2"
103
+ shift 2
104
+ ;;
96
105
  --pr)
97
106
  DO_PR=1
98
107
  shift
@@ -133,6 +142,22 @@ if [[ ${#FILE_DESTS[@]} -eq 0 && ${#EDITS[@]} -eq 0 ]]; then
133
142
  exit 2
134
143
  fi
135
144
 
145
+ # Validate the optional branch slug — it becomes a git ref suffix, so it must be
146
+ # a valid ref *component*, not merely a "nice-looking" string. Beyond the safe
147
+ # charset and leading/trailing dash/dot rules, git also rejects a '..' sequence
148
+ # and a '.lock' suffix in a ref name; allowing them here would pass validation
149
+ # but blow up later at `checkout -b` / `push` / `pr create --head` (where the
150
+ # failure is swallowed by `|| true`), so reject them up front with a clear error.
151
+ if [[ -n "$SLUG" ]]; then
152
+ if [[ ! "$SLUG" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ \
153
+ || "$SLUG" == *. || "$SLUG" == *- \
154
+ || "$SLUG" == *..* || "$SLUG" == *.lock ]]; then
155
+ echo "error: '$SLUG' is not a valid --slug (must be a valid git ref component)" >&2
156
+ echo "hint: letters, digits, '.', '_', '-'; start with a letter/digit; no trailing '.'/'-', no '..' sequence, no '.lock' suffix" >&2
157
+ exit 2
158
+ fi
159
+ fi
160
+
136
161
  # ── validate slug format ────────────────────────────────────────────────────
137
162
  validate_slug() {
138
163
  local slug="$1"
@@ -314,6 +339,9 @@ for raw in "${TARGETS[@]}"; do
314
339
  repo="${target#*/}"
315
340
  clone_name="${owner}-${repo}"
316
341
  clone_dir="$CLONES_DIR/$clone_name"
342
+ # Work branch: mass-update/<owner>-<repo>, with an optional -<slug> suffix so
343
+ # several independent updates to the same repo get distinct branches.
344
+ branch="mass-update/${clone_name}${SLUG:+-$SLUG}"
317
345
 
318
346
  # Check auth before cloning
319
347
  if ! gh auth status >/dev/null 2>&1; then
@@ -337,8 +365,8 @@ for raw in "${TARGETS[@]}"; do
337
365
  fi
338
366
 
339
367
  # Create work branch
340
- git -C "$clone_dir" checkout -b "mass-update/$clone_name" 2>/dev/null || \
341
- git -C "$clone_dir" switch -c "mass-update/$clone_name" 2>/dev/null || true
368
+ git -C "$clone_dir" checkout -b "$branch" 2>/dev/null || \
369
+ git -C "$clone_dir" switch -c "$branch" 2>/dev/null || true
342
370
 
343
371
  # Apply file ops
344
372
  apply_file_ops "$clone_dir" || { overall_rc=1; continue; }
@@ -366,7 +394,7 @@ for raw in "${TARGETS[@]}"; do
366
394
  fi
367
395
 
368
396
  # Push work branch — a failed push must not be swallowed.
369
- if ! git -C "$clone_dir" push origin "mass-update/$clone_name"; then
397
+ if ! git -C "$clone_dir" push origin "$branch"; then
370
398
  echo "error: push failed for $target" >&2
371
399
  overall_rc=1
372
400
  continue
@@ -377,7 +405,7 @@ for raw in "${TARGETS[@]}"; do
377
405
  --repo "$target" \
378
406
  --title "mass-update: deterministic update" \
379
407
  --body "Automated update via mass-update skill." \
380
- --head "mass-update/$clone_name")" || [[ -z "$pr_url" ]]; then
408
+ --head "$branch")" || [[ -z "$pr_url" ]]; then
381
409
  echo "error: gh pr create failed for $target" >&2
382
410
  overall_rc=1
383
411
  continue
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/). This project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2026-06-13
9
+
10
+ ### Added
11
+
12
+ - mass-update: --slug SLUG flag suffixes the work branch as `mass-update/<owner>-<repo>-<slug>`, so several independent updates to the same repo get distinct branches/PRs instead of colliding on the default branch name. Slug is validated as a valid git ref component — rejecting not just bad charset / leading-trailing dash-or-dot, but also the `..` sequences and `.lock` suffix git refuses (which would otherwise pass validation and only fail later at checkout/push/PR). (Qodo PR #10 bug 1.)
13
+
8
14
  ## [0.4.2] - 2026-06-13
9
15
 
10
16
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rollout-cli
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Cross-repo propagation workflow built on refactor-cli — apply a transformation across many repos at once.
5
5
  Project-URL: Homepage, https://github.com/agentculture/rollout-cli
6
6
  Project-URL: Issues, https://github.com/agentculture/rollout-cli/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rollout-cli"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  description = "Cross-repo propagation workflow built on refactor-cli — apply a transformation across many repos at once."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -478,6 +478,66 @@ def test_pr_writes_no_tempfiles_outside_repo(repo, fake_bin, tmp_path):
478
478
  assert not (repo / ".mass-update").exists()
479
479
 
480
480
 
481
+ # --------------------------------------------------------------------------- #
482
+ # --slug: suffix the work branch so several independent updates to the same repo
483
+ # get distinct branches (mass-update/<owner>-<repo>-<slug>).
484
+ # --------------------------------------------------------------------------- #
485
+
486
+
487
+ def _current_branch(clone: Path) -> str:
488
+ return subprocess.run(
489
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
490
+ cwd=clone,
491
+ capture_output=True,
492
+ text=True,
493
+ ).stdout.strip()
494
+
495
+
496
+ def test_default_branch_has_no_slug_suffix(repo, fake_bin):
497
+ r = run(repo, fake_bin, "--repos", SLUG, "--file", "NOTICE", "--content", "x\n", "--keep-clone")
498
+ assert r.returncode == 0, r.stderr
499
+ assert _current_branch(_clone_dir(repo)) == "mass-update/agentculture-widget-cli"
500
+
501
+
502
+ def test_slug_suffixes_the_work_branch(repo, fake_bin):
503
+ r = run(
504
+ repo,
505
+ fake_bin,
506
+ "--repos",
507
+ SLUG,
508
+ "--file",
509
+ "NOTICE",
510
+ "--content",
511
+ "x\n",
512
+ "--slug",
513
+ "notice-v2",
514
+ "--keep-clone",
515
+ )
516
+ assert r.returncode == 0, r.stderr
517
+ assert _current_branch(_clone_dir(repo)) == "mass-update/agentculture-widget-cli-notice-v2"
518
+
519
+
520
+ @pytest.mark.parametrize(
521
+ "bad",
522
+ [
523
+ "-leading",
524
+ "trailing-",
525
+ "has space",
526
+ "has/slash",
527
+ "trailing.",
528
+ "@bad",
529
+ # git rejects these as ref names even though they look benign:
530
+ "dot..dot", # '..' sequence
531
+ "foo.lock", # '.lock' suffix
532
+ ],
533
+ )
534
+ def test_invalid_slug_is_rejected(repo, fake_bin, bad):
535
+ r = run(repo, fake_bin, "--repos", SLUG, "--file", "NOTICE", "--content", "x\n", "--slug", bad)
536
+ assert r.returncode == 2, r.stderr
537
+ assert "slug" in (r.stdout + r.stderr).lower()
538
+ assert not (repo / ".mass-update").exists()
539
+
540
+
481
541
  # finding 2: when communicate's post-issue.sh is absent, --pr must error (route
482
542
  # issues only through /communicate), not silently fall back to `agtag` directly.
483
543
  def test_missing_post_issue_script_errors_no_agtag_fallback(repo, fake_bin):
@@ -430,7 +430,7 @@ wheels = [
430
430
 
431
431
  [[package]]
432
432
  name = "rollout-cli"
433
- version = "0.4.2"
433
+ version = "0.5.0"
434
434
  source = { editable = "." }
435
435
 
436
436
  [package.dev-dependencies]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes