rollout-cli 0.4.2__tar.gz → 0.5.1__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.
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/ask-colleague/SKILL.md +13 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +46 -4
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/mass-update/SKILL.md +8 -1
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/mass-update/scripts/mass-update.sh +32 -4
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/CHANGELOG.md +17 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/PKG-INFO +1 -1
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/docs/skill-sources.md +1 -1
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/pyproject.toml +1 -1
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/README.md +1 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +13 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/scripts/ask-colleague.sh +46 -4
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/manifest.toml +8 -6
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/apply.py +5 -1
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/bump.py +17 -5
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/sonar-project.properties +11 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_bump.py +13 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_mass_update.py +60 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/uv.lock +1 -1
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/agent-config/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/cicd/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/think/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/think/scripts/think.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/version-bump/SKILL.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills.local.yaml.example +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.devague/frames/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.devague/plans/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.flake8 +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.github/workflows/publish.yml +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.github/workflows/tests.yml +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.gitignore +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/.markdownlint-cli2.yaml +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/AGENTS.colleague.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/CLAUDE.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/LICENSE +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/README.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/culture.yaml +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/docs/plans/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/docs/specs/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/apache-relicense/apply.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/apache-relicense/check.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/apache-relicense/files/LICENSE +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/apache-relicense/manifest.toml +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/apply.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/check.sh +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/prompts/explore.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/prompts/review.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/prompts/write.md +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/__init__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/__main__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/__init__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/__init__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/cli.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/doctor.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/explain.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/learn.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/overview.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/plan.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_commands/whoami.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_errors.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/cli/_output.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/__init__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/devex_pr.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/gitops.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/orchestrate.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/preflight.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/recipe.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/report.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/core/targets.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/explain/__init__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/rollout/explain/catalog.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/__init__.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_apache_relicense.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_apply_cmd.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_cli.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_cli_introspection.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_cli_wiring.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_devex_pr.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_e2e_apache_relicense.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_explain_catalog.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_gitops.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_orchestrate.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_plan_cmd.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_preflight.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_recipe.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_report.py +0 -0
- {rollout_cli-0.4.2 → rollout_cli-0.5.1}/tests/test_targets.py +0 -0
|
@@ -10,6 +10,7 @@ description: >
|
|
|
10
10
|
hands off a small implementation, `ask-colleague feedback` grades a finished
|
|
11
11
|
work item (the ROI loop), and `ask-colleague clean` reaps stale/corrupt
|
|
12
12
|
`colleague/*` branches a crashed run left behind (which can break `git fetch`).
|
|
13
|
+
Pilot a running work item with `monitor`/`guide`/`stop`.
|
|
13
14
|
Reach for it REFLEXIVELY, the way you'd lean over to the
|
|
14
15
|
teammate at the next desk — not only when asked: before you present or open a PR
|
|
15
16
|
on a non-trivial committed diff, run `review` for a diverse second opinion; for a
|
|
@@ -154,6 +155,18 @@ keeping stdout valid JSON for a machine consumer while diagnostics stay on stder
|
|
|
154
155
|
(or `colleague clean`) to reap it — start with `--dry-run` to see what it would
|
|
155
156
|
remove. It only ever touches `colleague/*` refs and `.colleague/` artifacts.
|
|
156
157
|
|
|
158
|
+
## Piloting a flight
|
|
159
|
+
|
|
160
|
+
Dispatch a drive with `--watch` (on `explore`, `review`, or `write`) to make the
|
|
161
|
+
work item watchable. While it runs you can:
|
|
162
|
+
|
|
163
|
+
- **`ask-colleague monitor <task-id>`** — watch the flight's live feed
|
|
164
|
+
- **`ask-colleague guide <task-id> "<message>"`** — send mid-flight guidance
|
|
165
|
+
- **`ask-colleague stop <task-id>`** — cooperatively ask the flight to stop
|
|
166
|
+
|
|
167
|
+
Control is applied at the running loop's next turn boundary, so guidance and
|
|
168
|
+
stop requests take effect on the next iteration rather than interrupting mid-step.
|
|
169
|
+
|
|
157
170
|
## Hard rules (do not violate)
|
|
158
171
|
|
|
159
172
|
- **explore and review are read-only.** They run in a throwaway `git worktree`
|
{rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh
RENAMED
|
@@ -84,6 +84,9 @@ Usage:
|
|
|
84
84
|
ask-colleague feedback <id|last> [--rating N] Grade a past drive (ROI loop); with --rating records, without shows
|
|
85
85
|
ask-colleague feedback list List every recorded drive by request + grade (find one by its request)
|
|
86
86
|
ask-colleague clean [--dry-run] Reap stale/corrupt colleague/* branches + orphaned .colleague/ artifacts (#162)
|
|
87
|
+
ask-colleague monitor <task-id> Watch a running flight's live feed
|
|
88
|
+
ask-colleague guide <task-id> "<msg>" Send mid-flight guidance to a running flight
|
|
89
|
+
ask-colleague stop <task-id> Ask a running flight to stop (cooperative)
|
|
87
90
|
|
|
88
91
|
Options:
|
|
89
92
|
--repo PATH Target repo (default: .)
|
|
@@ -100,6 +103,7 @@ Options:
|
|
|
100
103
|
--rating N (feedback) record a 1-5 quality rating for the drive
|
|
101
104
|
--notes "..." (feedback) free-text notes to store with the rating
|
|
102
105
|
--by NAME (feedback) who is grading (default: colleague's resolved identity)
|
|
106
|
+
--watch (explore/review/write) arm a flight so you can monitor/guide/stop it
|
|
103
107
|
--json Machine-readable output: stdout carries only the result JSON,
|
|
104
108
|
every diagnostic/digest line goes to stderr (any verb)
|
|
105
109
|
|
|
@@ -119,11 +123,11 @@ EOF
|
|
|
119
123
|
# ── parse the verb ──────────────────────────────────────────────────────────
|
|
120
124
|
VERB="${1:-}"
|
|
121
125
|
case "$VERB" in
|
|
122
|
-
explore | review | write | feedback | clean) shift ;;
|
|
126
|
+
explore | review | write | feedback | clean | monitor | guide | stop) shift ;;
|
|
123
127
|
-h | --help) usage; exit 0 ;;
|
|
124
128
|
"") usage >&2; exit 1 ;; # missing arg -> user-input error (#161)
|
|
125
129
|
*)
|
|
126
|
-
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean)" >&2
|
|
130
|
+
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean|monitor|guide|stop)" >&2
|
|
127
131
|
echo "hint: run 'ask-colleague --help'" >&2
|
|
128
132
|
exit 1 # bad verb -> user-input error (#161)
|
|
129
133
|
;;
|
|
@@ -173,6 +177,7 @@ ALLOW_DIRTY=0
|
|
|
173
177
|
APPLY=0
|
|
174
178
|
OPEN_PR=0
|
|
175
179
|
DRY_RUN=0
|
|
180
|
+
WATCH=0
|
|
176
181
|
RATING=""
|
|
177
182
|
NOTES=""
|
|
178
183
|
BY=""
|
|
@@ -189,6 +194,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
189
194
|
--max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; shift 2 ;;
|
|
190
195
|
--timeout) need_value "$#" "$1"; TIMEOUT="$2"; shift 2 ;;
|
|
191
196
|
--apply) APPLY=1; shift ;;
|
|
197
|
+
--watch) WATCH=1; shift ;;
|
|
192
198
|
--allow-dirty) ALLOW_DIRTY=1; shift ;;
|
|
193
199
|
--pr) OPEN_PR=1; shift ;;
|
|
194
200
|
--dry-run) DRY_RUN=1; shift ;;
|
|
@@ -211,6 +217,7 @@ done
|
|
|
211
217
|
# throwaway worktree, so it needs no mktemp either.
|
|
212
218
|
case "$VERB" in
|
|
213
219
|
feedback | clean) require_tools git ;;
|
|
220
|
+
monitor | guide | stop) : ;;
|
|
214
221
|
write)
|
|
215
222
|
if [[ "$APPLY" -eq 1 || "$OPEN_PR" -eq 1 ]]; then
|
|
216
223
|
require_tools git python3
|
|
@@ -235,8 +242,15 @@ REPO="$(cd "$REPO" && pwd)"
|
|
|
235
242
|
# it must at least be a real git work tree. Fail fast with a clear message instead
|
|
236
243
|
# of an opaque mid-drive error (read-only verbs add a worktree, write commits a
|
|
237
244
|
# drive branch, clean reaps colleague/* refs).
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
# The pilot verbs (monitor/guide/stop) are pure .colleague/flight/ file I/O — they
|
|
246
|
+
# need no git work tree, so they are exempt from this fail-fast guard.
|
|
247
|
+
case "$VERB" in
|
|
248
|
+
monitor | guide | stop) : ;;
|
|
249
|
+
*)
|
|
250
|
+
git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
|
251
|
+
|| { echo "error: --repo is not a git repository: $REPO" >&2; exit 1; }
|
|
252
|
+
;;
|
|
253
|
+
esac
|
|
240
254
|
|
|
241
255
|
# review interpolates --base into the LLM instruction ("git diff $BASE...HEAD"),
|
|
242
256
|
# so reject a value that is not a real commit/ref before it is rendered into the
|
|
@@ -252,6 +266,9 @@ resolve_colleague || exit 2
|
|
|
252
266
|
# A local model can be slow on a growing context, so default generously.
|
|
253
267
|
export COLLEAGUE_TIMEOUT="$TIMEOUT"
|
|
254
268
|
COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --max-steps "$MAX_STEPS" --json)
|
|
269
|
+
# --watch arms a flight for EVERY drive verb (explore/review/write), so it lives on
|
|
270
|
+
# the shared flag list — not inside one verb's path — so monitor/guide/stop work.
|
|
271
|
+
[[ "${WATCH:-0}" -eq 1 ]] && COMMON_FLAGS+=(--watch)
|
|
255
272
|
|
|
256
273
|
# ── render an instruction from a prompt template ────────────────────────────
|
|
257
274
|
render_prompt() {
|
|
@@ -624,10 +641,35 @@ run_clean() {
|
|
|
624
641
|
"${cmd[@]}"
|
|
625
642
|
}
|
|
626
643
|
|
|
644
|
+
# ── piloting verbs: thin passthroughs to the `colleague flight` noun ─────────
|
|
645
|
+
_flight_json_flag() { [[ "$JSON_OUT" -eq 1 ]] && printf -- '--json'; }
|
|
646
|
+
|
|
647
|
+
run_monitor() {
|
|
648
|
+
local fid="${ARG%% *}"
|
|
649
|
+
[[ -z "$fid" ]] && { echo "error: monitor needs a flight task-id" >&2; exit 1; }
|
|
650
|
+
"${COLLEAGUE[@]}" flight status "$fid" --repo "$REPO" $(_flight_json_flag)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
run_stop() {
|
|
654
|
+
local fid="${ARG%% *}"
|
|
655
|
+
[[ -z "$fid" ]] && { echo "error: stop needs a flight task-id" >&2; exit 1; }
|
|
656
|
+
"${COLLEAGUE[@]}" flight stop "$fid" --repo "$REPO" $(_flight_json_flag)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
run_guide() {
|
|
660
|
+
local fid="${ARG%% *}"
|
|
661
|
+
local msg="${ARG#* }"
|
|
662
|
+
[[ -z "$fid" || "$msg" == "$fid" ]] && { echo "error: guide needs <task-id> <message>" >&2; exit 1; }
|
|
663
|
+
"${COLLEAGUE[@]}" flight guide "$fid" "$msg" --repo "$REPO" $(_flight_json_flag)
|
|
664
|
+
}
|
|
665
|
+
|
|
627
666
|
case "$VERB" in
|
|
628
667
|
explore) run_readonly "$(render_prompt explore)" ;;
|
|
629
668
|
review) run_readonly "$(render_prompt review)" ;;
|
|
630
669
|
write) run_write "$(render_prompt write)" ;;
|
|
631
670
|
feedback) run_feedback "$ARG" ;;
|
|
632
671
|
clean) run_clean ;;
|
|
672
|
+
monitor) run_monitor ;;
|
|
673
|
+
guide) run_guide ;;
|
|
674
|
+
stop) run_stop ;;
|
|
633
675
|
esac
|
|
@@ -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 "
|
|
341
|
-
git -C "$clone_dir" switch -c "
|
|
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 "
|
|
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 "
|
|
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,23 @@ 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.1] - 2026-06-18
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- ask-colleague: re-vendor the wrapper + recipe snapshot to the canonical colleague source (colleague 1.7.0) — adds the `colleague flight` pilot verbs `monitor` / `guide` / `stop` plus the `--watch` flag that arms a drive (explore/review/write) so it can be watched, guided mid-flight, and cooperatively stopped. Updates the bundled `SKILL.md`, the recipe `SKILL.md.tmpl` + wrapper snapshot, the manifest changelog text, and the `docs/skill-sources.md` provenance row (sync date → 2026-06-14). Adds discord-bot-cli to the ask-colleague-revendor consumer ledger.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- rollout apply: date the prepended changelog entry the day the change lands (`date.today()`) instead of the frozen `2026-06-06` placeholder the apply CLI never overrode.
|
|
17
|
+
- `rollout/core/bump.py` `prepend_changelog`: no longer emits consecutive blank lines (markdownlint MD012) when inserting before an existing entry — every cross-repo rollout target previously inherited a lint-failing CHANGELOG (caught on discord-bot-cli#6). Controls the blank lines at the insertion seams and guarantees a single trailing newline (MD047).
|
|
18
|
+
|
|
19
|
+
## [0.5.0] - 2026-06-13
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- 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.)
|
|
24
|
+
|
|
8
25
|
## [0.4.2] - 2026-06-13
|
|
9
26
|
|
|
10
27
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rollout-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -33,7 +33,7 @@ is load-bearing, even where guildmaster's upstream copy omits it.
|
|
|
33
33
|
| `think` | `../guildmaster/.claude/skills/think/` | **devague** (re-broadcast via guildmaster) | idea→spec leg of the devague workflow chain. Verbatim (already carried `type: command` at guildmaster). Origin/broadcast prose left verbatim. | 2026-05-26 (guildmaster 0.6.0) |
|
|
34
34
|
| `spec-to-plan` | `../guildmaster/.claude/skills/spec-to-plan/` | **devague** (re-broadcast via guildmaster) | spec→plan leg of the devague workflow chain. Verbatim (already carried `type: command`). | 2026-05-26 (guildmaster 0.6.0) |
|
|
35
35
|
| `assign-to-workforce` | `../guildmaster/.claude/skills/assign-to-workforce/` | **devague** (re-broadcast via guildmaster) | plan→parallel-implementation leg of the devague workflow chain. Verbatim (already carried `type: command`). | 2026-05-26 (guildmaster 0.6.0) |
|
|
36
|
-
| `ask-colleague` | `../colleague/.claude/skills/ask-colleague/` | **colleague** (renamed from convertible; vendored directly — guildmaster re-broadcast pending) | The first-party front door to the `colleague` CLI: hand a scoped task to a *different* engine/mind via `explore` / `review` / `write`, and grade a finished work item via `feedback` (the ROI loop). `explore`/`review` run isolated in a throwaway `git worktree`; `write` **previews by default** (throwaway worktree, no side effects) and refuses a dirty tree only when applying (`--apply` / `--pr`). Verbatim except one consumer-identifying clause in the Provenance paragraph (`colleague vendors from guildmaster` → `rollout-cli vendors from guildmaster`); already carried `type: command`. Optional runtime dep: **`colleague`** on PATH. | 2026-06-
|
|
36
|
+
| `ask-colleague` | `../colleague/.claude/skills/ask-colleague/` | **colleague** (renamed from convertible; vendored directly — guildmaster re-broadcast pending) | The first-party front door to the `colleague` CLI: hand a scoped task to a *different* engine/mind via `explore` / `review` / `write`, and grade a finished work item via `feedback` (the ROI loop); pilot a running drive with `monitor` / `guide` / `stop` (arm it with `--watch`). `explore`/`review` run isolated in a throwaway `git worktree`; `write` **previews by default** (throwaway worktree, no side effects) and refuses a dirty tree only when applying (`--apply` / `--pr`). Verbatim except one consumer-identifying clause in the Provenance paragraph (`colleague vendors from guildmaster` → `rollout-cli vendors from guildmaster`); already carried `type: command`. Optional runtime dep: **`colleague`** on PATH. | 2026-06-14 (colleague 1.7.0, direct) |
|
|
37
37
|
|
|
38
38
|
## Re-sync procedure
|
|
39
39
|
|
|
@@ -44,6 +44,7 @@ Origin: **colleague** (`agentculture/colleague`,
|
|
|
44
44
|
| rollout-cli | `agentculture/rollout-cli` | re-vendor wrapper | this repo ("self"); carries `which rollout-cli …` token |
|
|
45
45
|
| dgx-spark-cli | `agentculture/dgx-spark-cli` | fresh add | skill was absent |
|
|
46
46
|
| reterminal-cli | `agentculture/reterminal-cli` | re-vendor wrapper | merged at pre-#183 |
|
|
47
|
+
| discord-bot-cli | `agentculture/discord-bot-cli` | fresh add | skill was absent; carries `which discord-bot-cli …` token; landed at flight-verbs snapshot (discord-bot-cli#6) |
|
|
47
48
|
|
|
48
49
|
Keep this table current when a consumer is added or dropped.
|
|
49
50
|
|
|
@@ -10,6 +10,7 @@ description: >
|
|
|
10
10
|
hands off a small implementation, `ask-colleague feedback` grades a finished
|
|
11
11
|
work item (the ROI loop), and `ask-colleague clean` reaps stale/corrupt
|
|
12
12
|
`colleague/*` branches a crashed run left behind (which can break `git fetch`).
|
|
13
|
+
Pilot a running work item with `monitor`/`guide`/`stop`.
|
|
13
14
|
Reach for it REFLEXIVELY, the way you'd lean over to the
|
|
14
15
|
teammate at the next desk — not only when asked: before you present or open a PR
|
|
15
16
|
on a non-trivial committed diff, run `review` for a diverse second opinion; for a
|
|
@@ -154,6 +155,18 @@ keeping stdout valid JSON for a machine consumer while diagnostics stay on stder
|
|
|
154
155
|
(or `colleague clean`) to reap it — start with `--dry-run` to see what it would
|
|
155
156
|
remove. It only ever touches `colleague/*` refs and `.colleague/` artifacts.
|
|
156
157
|
|
|
158
|
+
## Piloting a flight
|
|
159
|
+
|
|
160
|
+
Dispatch a drive with `--watch` (on `explore`, `review`, or `write`) to make the
|
|
161
|
+
work item watchable. While it runs you can:
|
|
162
|
+
|
|
163
|
+
- **`ask-colleague monitor <task-id>`** — watch the flight's live feed
|
|
164
|
+
- **`ask-colleague guide <task-id> "<message>"`** — send mid-flight guidance
|
|
165
|
+
- **`ask-colleague stop <task-id>`** — cooperatively ask the flight to stop
|
|
166
|
+
|
|
167
|
+
Control is applied at the running loop's next turn boundary, so guidance and
|
|
168
|
+
stop requests take effect on the next iteration rather than interrupting mid-step.
|
|
169
|
+
|
|
157
170
|
## Hard rules (do not violate)
|
|
158
171
|
|
|
159
172
|
- **explore and review are read-only.** They run in a throwaway `git worktree`
|
|
@@ -84,6 +84,9 @@ Usage:
|
|
|
84
84
|
ask-colleague feedback <id|last> [--rating N] Grade a past drive (ROI loop); with --rating records, without shows
|
|
85
85
|
ask-colleague feedback list List every recorded drive by request + grade (find one by its request)
|
|
86
86
|
ask-colleague clean [--dry-run] Reap stale/corrupt colleague/* branches + orphaned .colleague/ artifacts (#162)
|
|
87
|
+
ask-colleague monitor <task-id> Watch a running flight's live feed
|
|
88
|
+
ask-colleague guide <task-id> "<msg>" Send mid-flight guidance to a running flight
|
|
89
|
+
ask-colleague stop <task-id> Ask a running flight to stop (cooperative)
|
|
87
90
|
|
|
88
91
|
Options:
|
|
89
92
|
--repo PATH Target repo (default: .)
|
|
@@ -100,6 +103,7 @@ Options:
|
|
|
100
103
|
--rating N (feedback) record a 1-5 quality rating for the drive
|
|
101
104
|
--notes "..." (feedback) free-text notes to store with the rating
|
|
102
105
|
--by NAME (feedback) who is grading (default: colleague's resolved identity)
|
|
106
|
+
--watch (explore/review/write) arm a flight so you can monitor/guide/stop it
|
|
103
107
|
--json Machine-readable output: stdout carries only the result JSON,
|
|
104
108
|
every diagnostic/digest line goes to stderr (any verb)
|
|
105
109
|
|
|
@@ -119,11 +123,11 @@ EOF
|
|
|
119
123
|
# ── parse the verb ──────────────────────────────────────────────────────────
|
|
120
124
|
VERB="${1:-}"
|
|
121
125
|
case "$VERB" in
|
|
122
|
-
explore | review | write | feedback | clean) shift ;;
|
|
126
|
+
explore | review | write | feedback | clean | monitor | guide | stop) shift ;;
|
|
123
127
|
-h | --help) usage; exit 0 ;;
|
|
124
128
|
"") usage >&2; exit 1 ;; # missing arg -> user-input error (#161)
|
|
125
129
|
*)
|
|
126
|
-
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean)" >&2
|
|
130
|
+
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean|monitor|guide|stop)" >&2
|
|
127
131
|
echo "hint: run 'ask-colleague --help'" >&2
|
|
128
132
|
exit 1 # bad verb -> user-input error (#161)
|
|
129
133
|
;;
|
|
@@ -173,6 +177,7 @@ ALLOW_DIRTY=0
|
|
|
173
177
|
APPLY=0
|
|
174
178
|
OPEN_PR=0
|
|
175
179
|
DRY_RUN=0
|
|
180
|
+
WATCH=0
|
|
176
181
|
RATING=""
|
|
177
182
|
NOTES=""
|
|
178
183
|
BY=""
|
|
@@ -189,6 +194,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
189
194
|
--max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; shift 2 ;;
|
|
190
195
|
--timeout) need_value "$#" "$1"; TIMEOUT="$2"; shift 2 ;;
|
|
191
196
|
--apply) APPLY=1; shift ;;
|
|
197
|
+
--watch) WATCH=1; shift ;;
|
|
192
198
|
--allow-dirty) ALLOW_DIRTY=1; shift ;;
|
|
193
199
|
--pr) OPEN_PR=1; shift ;;
|
|
194
200
|
--dry-run) DRY_RUN=1; shift ;;
|
|
@@ -211,6 +217,7 @@ done
|
|
|
211
217
|
# throwaway worktree, so it needs no mktemp either.
|
|
212
218
|
case "$VERB" in
|
|
213
219
|
feedback | clean) require_tools git ;;
|
|
220
|
+
monitor | guide | stop) : ;;
|
|
214
221
|
write)
|
|
215
222
|
if [[ "$APPLY" -eq 1 || "$OPEN_PR" -eq 1 ]]; then
|
|
216
223
|
require_tools git python3
|
|
@@ -235,8 +242,15 @@ REPO="$(cd "$REPO" && pwd)"
|
|
|
235
242
|
# it must at least be a real git work tree. Fail fast with a clear message instead
|
|
236
243
|
# of an opaque mid-drive error (read-only verbs add a worktree, write commits a
|
|
237
244
|
# drive branch, clean reaps colleague/* refs).
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
# The pilot verbs (monitor/guide/stop) are pure .colleague/flight/ file I/O — they
|
|
246
|
+
# need no git work tree, so they are exempt from this fail-fast guard.
|
|
247
|
+
case "$VERB" in
|
|
248
|
+
monitor | guide | stop) : ;;
|
|
249
|
+
*)
|
|
250
|
+
git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
|
251
|
+
|| { echo "error: --repo is not a git repository: $REPO" >&2; exit 1; }
|
|
252
|
+
;;
|
|
253
|
+
esac
|
|
240
254
|
|
|
241
255
|
# review interpolates --base into the LLM instruction ("git diff $BASE...HEAD"),
|
|
242
256
|
# so reject a value that is not a real commit/ref before it is rendered into the
|
|
@@ -252,6 +266,9 @@ resolve_colleague || exit 2
|
|
|
252
266
|
# A local model can be slow on a growing context, so default generously.
|
|
253
267
|
export COLLEAGUE_TIMEOUT="$TIMEOUT"
|
|
254
268
|
COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --max-steps "$MAX_STEPS" --json)
|
|
269
|
+
# --watch arms a flight for EVERY drive verb (explore/review/write), so it lives on
|
|
270
|
+
# the shared flag list — not inside one verb's path — so monitor/guide/stop work.
|
|
271
|
+
[[ "${WATCH:-0}" -eq 1 ]] && COMMON_FLAGS+=(--watch)
|
|
255
272
|
|
|
256
273
|
# ── render an instruction from a prompt template ────────────────────────────
|
|
257
274
|
render_prompt() {
|
|
@@ -624,10 +641,35 @@ run_clean() {
|
|
|
624
641
|
"${cmd[@]}"
|
|
625
642
|
}
|
|
626
643
|
|
|
644
|
+
# ── piloting verbs: thin passthroughs to the `colleague flight` noun ─────────
|
|
645
|
+
_flight_json_flag() { [[ "$JSON_OUT" -eq 1 ]] && printf -- '--json'; }
|
|
646
|
+
|
|
647
|
+
run_monitor() {
|
|
648
|
+
local fid="${ARG%% *}"
|
|
649
|
+
[[ -z "$fid" ]] && { echo "error: monitor needs a flight task-id" >&2; exit 1; }
|
|
650
|
+
"${COLLEAGUE[@]}" flight status "$fid" --repo "$REPO" $(_flight_json_flag)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
run_stop() {
|
|
654
|
+
local fid="${ARG%% *}"
|
|
655
|
+
[[ -z "$fid" ]] && { echo "error: stop needs a flight task-id" >&2; exit 1; }
|
|
656
|
+
"${COLLEAGUE[@]}" flight stop "$fid" --repo "$REPO" $(_flight_json_flag)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
run_guide() {
|
|
660
|
+
local fid="${ARG%% *}"
|
|
661
|
+
local msg="${ARG#* }"
|
|
662
|
+
[[ -z "$fid" || "$msg" == "$fid" ]] && { echo "error: guide needs <task-id> <message>" >&2; exit 1; }
|
|
663
|
+
"${COLLEAGUE[@]}" flight guide "$fid" "$msg" --repo "$REPO" $(_flight_json_flag)
|
|
664
|
+
}
|
|
665
|
+
|
|
627
666
|
case "$VERB" in
|
|
628
667
|
explore) run_readonly "$(render_prompt explore)" ;;
|
|
629
668
|
review) run_readonly "$(render_prompt review)" ;;
|
|
630
669
|
write) run_write "$(render_prompt write)" ;;
|
|
631
670
|
feedback) run_feedback "$ARG" ;;
|
|
632
671
|
clean) run_clean ;;
|
|
672
|
+
monitor) run_monitor ;;
|
|
673
|
+
guide) run_guide ;;
|
|
674
|
+
stop) run_stop ;;
|
|
633
675
|
esac
|
|
@@ -15,12 +15,14 @@ changelog_text = """\
|
|
|
15
15
|
|
|
16
16
|
- **Re-vendored the `ask-colleague` skill wrapper from its canonical colleague
|
|
17
17
|
source** (`.claude/skills/ask-colleague/scripts/ask-colleague.sh`): the wrapper
|
|
18
|
-
now
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
now drives the `colleague flight` pilot verbs — `monitor` / `guide` / `stop`
|
|
19
|
+
plus the `--watch` flag that arms a drive (`explore` / `review` / `write`) so it
|
|
20
|
+
can be watched, guided mid-flight, and cooperatively stopped. This supersedes the
|
|
21
|
+
earlier colleague#186 snapshot (which predated flights). It keeps the `--json`
|
|
22
|
+
machine-readable output added then — stdout carries **only** the result JSON (the
|
|
23
|
+
drive verbs emit the normalized `TaskResult`; `feedback` / `clean` forward
|
|
24
|
+
`--json` to colleague) while every diagnostic/digest line goes to stderr. The
|
|
25
|
+
bundled `SKILL.md` template documents the new verbs/flag; an existing downstream
|
|
24
26
|
`SKILL.md` is left untouched (it carries a repo-specific provenance token and
|
|
25
27
|
may diverge). Where the skill was absent, it is added fresh (wrapper +
|
|
26
28
|
`SKILL.md` + prompts).
|
|
@@ -9,6 +9,7 @@ as "needs-attention"). Never force-apply or merge.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import argparse
|
|
12
|
+
import datetime
|
|
12
13
|
import pathlib
|
|
13
14
|
|
|
14
15
|
from rollout.cli._output import emit_result
|
|
@@ -53,13 +54,16 @@ def cmd_apply(args: argparse.Namespace) -> int:
|
|
|
53
54
|
return 0
|
|
54
55
|
|
|
55
56
|
# --- classify and apply ----------------------------------------------
|
|
57
|
+
# Date the changelog entry the day the change actually lands (not the
|
|
58
|
+
# apply_one frozen-default placeholder, which is only for hermetic tests).
|
|
59
|
+
today = datetime.date.today().isoformat()
|
|
56
60
|
report = Report()
|
|
57
61
|
for name in targets_list:
|
|
58
62
|
repo_path = pathlib.Path(base) / pathlib.Path(name).name
|
|
59
63
|
try:
|
|
60
64
|
status = preflight.classify(repo_path, recipe)
|
|
61
65
|
if status == preflight.Status.CLEAN:
|
|
62
|
-
row = orchestrate.apply_one(repo_path, recipe, repo_slug=name)
|
|
66
|
+
row = orchestrate.apply_one(repo_path, recipe, repo_slug=name, date=today)
|
|
63
67
|
report.add_row(name, row.status, row.pr_url)
|
|
64
68
|
else:
|
|
65
69
|
report.add_row(name, _STATUS_MAP[status])
|
|
@@ -103,16 +103,28 @@ def prepend_changelog(
|
|
|
103
103
|
if f"## [{version}]" in text:
|
|
104
104
|
return
|
|
105
105
|
|
|
106
|
-
# Build the new
|
|
107
|
-
|
|
106
|
+
# Build the new entry. Normalise the body and control the surrounding
|
|
107
|
+
# blank lines here (exactly one before and after) rather than inheriting
|
|
108
|
+
# the caller's / file's trailing whitespace — otherwise the junction with
|
|
109
|
+
# the intro or an existing entry yields consecutive blank lines, which
|
|
110
|
+
# markdownlint rejects (MD012/no-multiple-blanks).
|
|
111
|
+
body_clean = re.sub(r"\n{3,}", "\n\n", body.strip())
|
|
112
|
+
entry = f"## [{version}] - {date}\n\n{body_clean}\n"
|
|
108
113
|
|
|
109
114
|
# Find the first existing release entry (## [X.Y.Z] ...)
|
|
110
115
|
first_entry = _ENTRY_RE.search(text)
|
|
111
116
|
if first_entry:
|
|
112
|
-
# Insert just before the first existing entry
|
|
113
|
-
|
|
117
|
+
# Insert just before the first existing entry, with exactly one blank
|
|
118
|
+
# line separating the intro from the new entry and the new entry from
|
|
119
|
+
# the existing one.
|
|
120
|
+
prefix = text[: first_entry.start()].rstrip()
|
|
121
|
+
suffix = text[first_entry.start() :].lstrip("\n")
|
|
122
|
+
new_text = f"{prefix}\n\n{entry}\n{suffix}"
|
|
114
123
|
else:
|
|
115
124
|
# No existing entries — append after the intro.
|
|
116
|
-
new_text = text.rstrip()
|
|
125
|
+
new_text = f"{text.rstrip()}\n\n{entry}"
|
|
126
|
+
|
|
127
|
+
# Exactly one trailing newline (MD047).
|
|
128
|
+
new_text = new_text.rstrip("\n") + "\n"
|
|
117
129
|
|
|
118
130
|
path.write_text(new_text, encoding="utf-8")
|
|
@@ -16,6 +16,17 @@ sonar.python.version=3.12
|
|
|
16
16
|
# Don't analyze noise.
|
|
17
17
|
sonar.exclusions=**/__pycache__/**,**/.venv/**,uv.lock,.claude/skills/**
|
|
18
18
|
|
|
19
|
+
# False positive: pythonsecurity:S2083 (path injection) fires on
|
|
20
|
+
# prepend_changelog's `path.write_text` in rollout/core/bump.py because it traces
|
|
21
|
+
# the changelog path back to the `--repos` slug. That slug is sanitized at the
|
|
22
|
+
# boundary — apply.py derives the write dir as `<base>/<pathlib.Path(name).name>`,
|
|
23
|
+
# and `.name` strips any traversal — and the sole caller (orchestrate.apply_one)
|
|
24
|
+
# always passes `<repo_path>/CHANGELOG.md`. This is a local, operator-run devtool
|
|
25
|
+
# writing a CHANGELOG, not an attacker-facing surface. Ignore S2083 in bump.py.
|
|
26
|
+
sonar.issue.ignore.multicriteria=e1
|
|
27
|
+
sonar.issue.ignore.multicriteria.e1.ruleKey=pythonsecurity:S2083
|
|
28
|
+
sonar.issue.ignore.multicriteria.e1.resourceKey=**/bump.py
|
|
29
|
+
|
|
19
30
|
# Block CI on a failing quality gate. The scanner polls SonarCloud and exits
|
|
20
31
|
# non-zero on a red gate (coverage regression, new bugs/vulnerabilities). Only
|
|
21
32
|
# takes effect when the SonarCloud Scan step runs — that step is guarded by
|
|
@@ -152,6 +152,19 @@ def test_prepend_changelog_before_existing_entry(tmp_path: pathlib.Path) -> None
|
|
|
152
152
|
assert new_pos < old_pos
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
def test_prepend_changelog_no_consecutive_blank_lines(tmp_path: pathlib.Path) -> None:
|
|
156
|
+
"""Inserting never produces MD012 double blanks at the intro/entry junctions."""
|
|
157
|
+
existing = "## [0.1.0] - 2025-01-01\n\n### Added\n\n- old thing\n"
|
|
158
|
+
path = _write_changelog(tmp_path, existing=existing)
|
|
159
|
+
# A body padded with stray blank lines must still normalise cleanly.
|
|
160
|
+
prepend_changelog(path, "0.2.0", "2026-01-01", "\n### Changed\n\n\n- new thing\n\n")
|
|
161
|
+
content = path.read_text()
|
|
162
|
+
assert "\n\n\n" not in content, "markdownlint MD012: consecutive blank lines"
|
|
163
|
+
assert content.endswith("\n") and not content.endswith("\n\n")
|
|
164
|
+
assert content.index("## [0.2.0]") < content.index("## [0.1.0]")
|
|
165
|
+
assert "- new thing" in content
|
|
166
|
+
|
|
167
|
+
|
|
155
168
|
# ---------------------------------------------------------------------------
|
|
156
169
|
# prepend_changelog — idempotency
|
|
157
170
|
# ---------------------------------------------------------------------------
|
|
@@ -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):
|
|
File without changes
|
{rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rollout_cli-0.4.2 → rollout_cli-0.5.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/prompts/explore.md
RENAMED
|
File without changes
|
{rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/prompts/review.md
RENAMED
|
File without changes
|
{rollout_cli-0.4.2 → rollout_cli-0.5.1}/recipes/ask-colleague-revendor/files/prompts/write.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|