rollout-cli 0.3.2__tar.gz → 0.4.2__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.3.2 → rollout_cli-0.4.2}/.claude/skills/ask-colleague/SKILL.md +36 -14
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +101 -36
- rollout_cli-0.4.2/.claude/skills/mass-update/SKILL.md +109 -0
- rollout_cli-0.4.2/.claude/skills/mass-update/scripts/mass-update.sh +410 -0
- rollout_cli-0.4.2/.devague/frames/mass-update-now-clones-each-target-repo-into-a-git.json +214 -0
- rollout_cli-0.4.2/.devague/plans/mass-update-now-clones-each-target-repo-into-a-git.json +255 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.gitignore +3 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/CHANGELOG.md +29 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/CLAUDE.md +12 -8
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/PKG-INFO +1 -1
- rollout_cli-0.4.2/docs/plans/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +72 -0
- rollout_cli-0.4.2/docs/specs/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +54 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/pyproject.toml +1 -1
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/apply.sh +6 -6
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/check.sh +1 -1
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +5 -1
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/files/scripts/ask-colleague.sh +101 -36
- rollout_cli-0.4.2/recipes/ask-colleague-revendor/manifest.toml +27 -0
- rollout_cli-0.4.2/tests/test_mass_update.py +489 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/uv.lock +1 -1
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/manifest.toml +0 -25
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/agent-config/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/cicd/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/run-tests/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/think/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/think/scripts/think.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/version-bump/SKILL.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills.local.yaml.example +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.flake8 +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.github/workflows/publish.yml +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.github/workflows/tests.yml +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/.markdownlint-cli2.yaml +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/AGENTS.colleague.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/LICENSE +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/README.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/culture.yaml +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/docs/skill-sources.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/apache-relicense/apply.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/apache-relicense/check.sh +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/apache-relicense/files/LICENSE +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/apache-relicense/manifest.toml +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/README.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/files/prompts/explore.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/files/prompts/review.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/recipes/ask-colleague-revendor/files/prompts/write.md +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/__init__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/__main__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/__init__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/__init__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/apply.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/cli.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/doctor.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/explain.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/learn.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/overview.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/plan.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_commands/whoami.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_errors.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/cli/_output.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/__init__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/bump.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/devex_pr.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/gitops.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/orchestrate.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/preflight.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/recipe.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/report.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/core/targets.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/explain/__init__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/rollout/explain/catalog.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/sonar-project.properties +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/__init__.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_apache_relicense.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_apply_cmd.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_bump.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_cli.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_cli_introspection.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_cli_wiring.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_devex_pr.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_e2e_apache_relicense.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_explain_catalog.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_gitops.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_orchestrate.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_plan_cmd.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_preflight.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_recipe.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_report.py +0 -0
- {rollout_cli-0.3.2 → rollout_cli-0.4.2}/tests/test_targets.py +0 -0
|
@@ -7,18 +7,20 @@ description: >
|
|
|
7
7
|
isn't a stronger model; it's a second, independent mind, and that diversity is
|
|
8
8
|
the value: `ask-colleague review` gets a candid second opinion on a diff,
|
|
9
9
|
`ask-colleague explore` gets a fresh read of an area, `ask-colleague write`
|
|
10
|
-
hands off a small implementation,
|
|
11
|
-
work item (the ROI loop)
|
|
10
|
+
hands off a small implementation, `ask-colleague feedback` grades a finished
|
|
11
|
+
work item (the ROI loop), and `ask-colleague clean` reaps stale/corrupt
|
|
12
|
+
`colleague/*` branches a crashed run left behind (which can break `git fetch`).
|
|
13
|
+
Reach for it REFLEXIVELY, the way you'd lean over to the
|
|
12
14
|
teammate at the next desk — not only when asked: before you present or open a PR
|
|
13
15
|
on a non-trivial committed diff, run `review` for a diverse second opinion; for a
|
|
14
16
|
fresh read of an unfamiliar area whose answer is independent of your current
|
|
15
17
|
context, run `explore`. Both are read-only — isolated in a throwaway git
|
|
16
|
-
worktree, zero side effects — so the reflex is always safe; the
|
|
17
|
-
`write --apply` / `write --pr` still needs the user's go-ahead. Triggers when the
|
|
18
|
+
worktree, zero side effects to your tree/branch — so the reflex is always safe; the
|
|
19
|
+
side-effecting `write --apply` / `write --pr` still needs the user's go-ahead. Triggers when the
|
|
18
20
|
user says "ask colleague", "ask a colleague to review/explore/write this", "have
|
|
19
21
|
colleague take a look", "get a second opinion", "ask the other model", "rate that
|
|
20
|
-
work item" — and still on the legacy "outsource this".
|
|
21
|
-
opinion to verify and own, never authority.
|
|
22
|
+
work item", "clean up a crashed colleague run" — and still on the legacy "outsource this".
|
|
23
|
+
Colleague's output is a second opinion to verify and own, never authority.
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
# ask-colleague — lean on colleague as a different mind
|
|
@@ -95,10 +97,11 @@ else an install hint.
|
|
|
95
97
|
|
|
96
98
|
| Verb | What it does | Side effects |
|
|
97
99
|
|------|--------------|--------------|
|
|
98
|
-
| `explore "<question or area>"` | Read-only investigation of the repo; the model reads and reports findings. | **None** — runs in a throwaway worktree at HEAD. |
|
|
99
|
-
| `review "<what to focus on>" [--base main]` | A diverse second opinion on the **committed** diff (`<base>...HEAD`). | **None** — throwaway worktree
|
|
100
|
-
| `write "<task>" [--apply\|--pr]` | Implement a change. **Previews by default** (throwaway worktree, prints the would-be diff); `--apply` lands a work branch in place; `--pr` pushes + opens a PR. | **None** by default (preview); a `colleague/<id>` work branch / PR only with `--apply` / `--pr`. |
|
|
100
|
+
| `explore "<question or area>"` | Read-only investigation of the repo; the model reads and reports findings. | **None** to your working tree / branch — runs in a throwaway worktree at HEAD; writes only a gradable run artifact under the gitignored `.colleague/` bookkeeping dir. |
|
|
101
|
+
| `review "<what to focus on>" [--base main]` | A diverse second opinion on the **committed** diff (`<base>...HEAD`). | **None** to your working tree / branch — throwaway worktree, committed changes only; writes only a gradable run artifact under the gitignored `.colleague/` bookkeeping dir. |
|
|
102
|
+
| `write "<task>" [--apply\|--pr]` | Implement a change. **Previews by default** (throwaway worktree, prints the would-be diff); `--apply` lands a work branch in place; `--pr` pushes + opens a PR. | **None** to your working tree / branch by default (preview); a `colleague/<id>` work branch / PR only with `--apply` / `--pr`. |
|
|
101
103
|
| `feedback <id\|last> [--rating N]` | **Grade a finished work item** (the ROI loop). With `--rating N` (1–5, plus `--notes`) it records feedback; without, it shows the work item's existing feedback. `last` resolves the most recent work item in `--repo`. | Writes `.colleague/<id>.feedback.json` only when `--rating` is given; read-only otherwise. |
|
|
104
|
+
| `clean [--dry-run]` | **Reap what a crashed run left behind** (#162): stale/corrupt `colleague/*` branches + orphaned 0-byte `.colleague/` artifacts that can wedge `git fetch`. Scoped strictly to `colleague/*` (never touches an unrelated branch); conservative with `.git/objects` (reports 0-byte loose objects + suggests `git prune`, never deletes them). A thin pass-through to `colleague clean`. | Deletes corrupt `colleague/<id>` refs + 0-byte `.colleague/` artifacts in `--repo`; `--dry-run` changes nothing. |
|
|
102
105
|
|
|
103
106
|
### Options
|
|
104
107
|
|
|
@@ -116,10 +119,15 @@ else an install hint.
|
|
|
116
119
|
| `--rating N` | (`feedback`) record a 1–5 quality rating for the work item. |
|
|
117
120
|
| `--notes "..."` | (`feedback`) free-text notes stored with the rating. |
|
|
118
121
|
| `--by NAME` | (`feedback`) who is grading (default: colleague's resolved identity). |
|
|
122
|
+
| `--dry-run` | (`clean`) report what would be reaped without changing anything. |
|
|
123
|
+
| `--json` | (any verb) machine-readable output: stdout carries **only** the result JSON, every diagnostic/digest line goes to stderr. |
|
|
119
124
|
|
|
120
125
|
The result printed to stdout is the work item's `TaskResult.summary` (plus
|
|
121
126
|
`changed_files` / work branch for `write`), parsed from `colleague work
|
|
122
|
-
--json`. Per-step progress streams to stderr while it runs.
|
|
127
|
+
--json`. Per-step progress streams to stderr while it runs. Pass `--json` to get
|
|
128
|
+
the raw `TaskResult` on stdout instead of the human digest (the drive verbs emit
|
|
129
|
+
the normalized `TaskResult`; `feedback` / `clean` forward `--json` to colleague),
|
|
130
|
+
keeping stdout valid JSON for a machine consumer while diagnostics stay on stderr.
|
|
123
131
|
|
|
124
132
|
## When to reach for which verb
|
|
125
133
|
|
|
@@ -140,6 +148,11 @@ The result printed to stdout is the work item's `TaskResult.summary` (plus
|
|
|
140
148
|
says how *good* it was — together they let you compute the **ROI of asking
|
|
141
149
|
colleague** and decide whether to ask again (and which backend). Grade the most
|
|
142
150
|
recent work item with `ask-colleague feedback last --rating 4 --notes "…"`.
|
|
151
|
+
- **clean** — recovery, not routine. A crashed / interrupted `write --apply` can
|
|
152
|
+
leave a dangling `colleague/<id>` branch pointing at half-written (0-byte)
|
|
153
|
+
objects that **breaks `git fetch` / `git pull`**. Run `ask-colleague clean`
|
|
154
|
+
(or `colleague clean`) to reap it — start with `--dry-run` to see what it would
|
|
155
|
+
remove. It only ever touches `colleague/*` refs and `.colleague/` artifacts.
|
|
143
156
|
|
|
144
157
|
## Hard rules (do not violate)
|
|
145
158
|
|
|
@@ -168,11 +181,20 @@ The result printed to stdout is the work item's `TaskResult.summary` (plus
|
|
|
168
181
|
uncommitted work, commit it first.
|
|
169
182
|
- The default backend is whatever single model is running locally; a multi-model
|
|
170
183
|
fleet (different model per verb) is separate infrastructure.
|
|
184
|
+
- **Every verb writes bookkeeping under `.colleague/`** (run artifacts for
|
|
185
|
+
explore/review/write; feedback records; the `last_work` pointer) — none of it
|
|
186
|
+
in your tracked tree, but in a repo that does **not** already gitignore
|
|
187
|
+
`.colleague/` it shows up as untracked files. **Add `.colleague/` to your
|
|
188
|
+
`.gitignore`** (keep `!/.colleague/commands/` if you commit command templates).
|
|
189
|
+
- **A crashed run can wedge `git fetch`.** A `write --apply` interrupted
|
|
190
|
+
mid-commit can leave a dangling `colleague/<id>` branch + 0-byte artifacts;
|
|
191
|
+
`ask-colleague clean` recovers it. A SIGKILL/OOM *during* the commit can still
|
|
192
|
+
corrupt git objects (git/filesystem durability, not the skill's to guarantee)
|
|
193
|
+
— which is exactly what `clean` is for.
|
|
171
194
|
|
|
172
195
|
## Provenance
|
|
173
196
|
|
|
174
197
|
This is a **first-party** colleague skill — colleague is its origin. It is
|
|
175
|
-
the inverse of the other skills under `.claude/skills/`, which
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
symlink or depend on it.
|
|
198
|
+
the inverse of the other skills under `.claude/skills/`, which rollout-cli
|
|
199
|
+
vendors *from* guildmaster. See `docs/skill-sources.md`. The `cite, don't import`
|
|
200
|
+
policy holds: downstream repos copy it, they don't symlink or depend on it.
|
{rollout_cli-0.3.2 → rollout_cli-0.4.2}/.claude/skills/ask-colleague/scripts/ask-colleague.sh
RENAMED
|
@@ -100,6 +100,8 @@ Options:
|
|
|
100
100
|
--rating N (feedback) record a 1-5 quality rating for the drive
|
|
101
101
|
--notes "..." (feedback) free-text notes to store with the rating
|
|
102
102
|
--by NAME (feedback) who is grading (default: colleague's resolved identity)
|
|
103
|
+
--json Machine-readable output: stdout carries only the result JSON,
|
|
104
|
+
every diagnostic/digest line goes to stderr (any verb)
|
|
103
105
|
|
|
104
106
|
explore/review run in a throwaway git worktree at HEAD — they cannot touch your
|
|
105
107
|
working tree or branch. review compares <base>...HEAD (committed changes only).
|
|
@@ -127,16 +129,23 @@ case "$VERB" in
|
|
|
127
129
|
;;
|
|
128
130
|
esac
|
|
129
131
|
|
|
130
|
-
#
|
|
131
|
-
# mid-run error
|
|
132
|
-
|
|
132
|
+
# Verify the external tools a given verb path actually needs are on PATH — fail
|
|
133
|
+
# fast with a clear message, not an opaque mid-run error. The required set is
|
|
134
|
+
# verb-specific (the caller passes it once the verb + flags are known, below):
|
|
135
|
+
# feedback/clean are thin pass-throughs to `colleague` plus the shared git
|
|
136
|
+
# work-tree guard, so they need only git; the drive verbs (explore/review/write)
|
|
137
|
+
# also render a prompt and parse colleague's --json result via python3, and the
|
|
138
|
+
# worktree-isolated paths additionally need mktemp. grep is only used by the
|
|
139
|
+
# uv-fallback resolver, which degrades to the clear "colleague not found" message
|
|
140
|
+
# when absent, so it is not a hard requirement here.
|
|
141
|
+
require_tools() { # $@ = tool names this verb path needs
|
|
133
142
|
local missing=() t
|
|
134
|
-
for t in
|
|
143
|
+
for t in "$@"; do
|
|
135
144
|
command -v "$t" >/dev/null 2>&1 || missing+=("$t")
|
|
136
145
|
done
|
|
137
146
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
138
147
|
echo "error: missing required tool(s): ${missing[*]}" >&2
|
|
139
|
-
echo "hint:
|
|
148
|
+
echo "hint: '$VERB' needs these on PATH: $*" >&2
|
|
140
149
|
exit 2
|
|
141
150
|
fi
|
|
142
151
|
}
|
|
@@ -151,8 +160,6 @@ need_value() { # $1 = remaining arg count ($#), $2 = flag name
|
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
|
|
154
|
-
require_tools
|
|
155
|
-
|
|
156
163
|
# ── defaults + flag parsing ─────────────────────────────────────────────────
|
|
157
164
|
REPO="."
|
|
158
165
|
BASE="main"
|
|
@@ -170,6 +177,7 @@ RATING=""
|
|
|
170
177
|
NOTES=""
|
|
171
178
|
BY=""
|
|
172
179
|
ARG=""
|
|
180
|
+
JSON_OUT=0
|
|
173
181
|
|
|
174
182
|
while [[ $# -gt 0 ]]; do
|
|
175
183
|
case "$1" in
|
|
@@ -187,6 +195,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
187
195
|
--rating) need_value "$#" "$1"; RATING="$2"; shift 2 ;;
|
|
188
196
|
--notes) need_value "$#" "$1"; NOTES="$2"; shift 2 ;;
|
|
189
197
|
--by) need_value "$#" "$1"; BY="$2"; shift 2 ;;
|
|
198
|
+
--json) JSON_OUT=1; shift ;;
|
|
190
199
|
-h | --help) usage; exit 0 ;;
|
|
191
200
|
--) shift; while [[ $# -gt 0 ]]; do ARG="${ARG:+$ARG }$1"; shift; done ;;
|
|
192
201
|
# unknown option -> user-input error (#161)
|
|
@@ -195,6 +204,23 @@ while [[ $# -gt 0 ]]; do
|
|
|
195
204
|
esac
|
|
196
205
|
done
|
|
197
206
|
|
|
207
|
+
# Now that the verb and its flags are known, require only the tools THIS path
|
|
208
|
+
# uses. feedback/clean shell straight to `colleague` (+ the git work-tree guard
|
|
209
|
+
# below), so they need only git — not python3/mktemp (qodo: the old blanket check
|
|
210
|
+
# failed those verbs in minimal envs). write --apply/--pr lands in place with no
|
|
211
|
+
# throwaway worktree, so it needs no mktemp either.
|
|
212
|
+
case "$VERB" in
|
|
213
|
+
feedback | clean) require_tools git ;;
|
|
214
|
+
write)
|
|
215
|
+
if [[ "$APPLY" -eq 1 || "$OPEN_PR" -eq 1 ]]; then
|
|
216
|
+
require_tools git python3
|
|
217
|
+
else
|
|
218
|
+
require_tools git python3 mktemp # preview runs in a throwaway worktree
|
|
219
|
+
fi
|
|
220
|
+
;;
|
|
221
|
+
*) require_tools git python3 mktemp ;; # explore / review
|
|
222
|
+
esac
|
|
223
|
+
|
|
198
224
|
# clean takes no description argument; every other verb requires one. (All of the
|
|
199
225
|
# guards below are user-input errors -> exit 1, per the policy comment at the top.)
|
|
200
226
|
if [[ "$VERB" == "clean" ]]; then
|
|
@@ -262,9 +288,10 @@ print_result() {
|
|
|
262
288
|
# empty (its artifact was discarded with the worktree, so it is not gradable).
|
|
263
289
|
# $3 (optional): exit code from the colleague drive command, propagated to
|
|
264
290
|
# the caller when the drive itself failed.
|
|
265
|
-
ASK_COLLEAGUE_REAL_ARTIFACT_DIR="${1:-}" ASK_COLLEAGUE_GRADABLE="${2:-}" ASK_COLLEAGUE_DRIVE_RC="${3:-}" python3 -c '
|
|
291
|
+
ASK_COLLEAGUE_REAL_ARTIFACT_DIR="${1:-}" ASK_COLLEAGUE_GRADABLE="${2:-}" ASK_COLLEAGUE_DRIVE_RC="${3:-}" ASK_COLLEAGUE_JSON="${JSON_OUT:-0}" python3 -c '
|
|
266
292
|
import sys, json, os
|
|
267
293
|
raw = sys.stdin.read().strip()
|
|
294
|
+
json_mode = os.environ.get("ASK_COLLEAGUE_JSON") == "1"
|
|
268
295
|
if not raw:
|
|
269
296
|
sys.stderr.write("error: colleague produced no result on stdout (see diagnostics above)\n")
|
|
270
297
|
sys.exit(2)
|
|
@@ -275,40 +302,69 @@ except Exception:
|
|
|
275
302
|
sys.stderr.write(raw[:2000] + "\n")
|
|
276
303
|
sys.exit(2)
|
|
277
304
|
ok = d.get("status") == "ok"
|
|
278
|
-
out = sys.stdout if ok else sys.stderr
|
|
279
305
|
tid = d.get("task_id") or ""
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
# Resolve the artifact path to the preserved copy when the drive ran in a
|
|
307
|
+
# throwaway worktree (read-only verbs); the raw JSON points into the now-deleted
|
|
308
|
+
# worktree, so both the digest and the --json output report the real location.
|
|
309
|
+
ap = d.get("artifacts_path")
|
|
310
|
+
real_dir = os.environ.get("ASK_COLLEAGUE_REAL_ARTIFACT_DIR") or ""
|
|
311
|
+
if ap and real_dir:
|
|
312
|
+
ap = os.path.join(real_dir, os.path.basename(ap))
|
|
283
313
|
# A drive that stopped without calling finish (colleague#142) or exhausted its
|
|
284
314
|
# step budget did NOT deliver an authoritative result — its summary is the model
|
|
285
315
|
# trailing off mid-task. Warn so the caller treats it as a partial, not a verdict.
|
|
286
|
-
# The warning is a DIAGNOSTIC -> always stderr (never stdout), so
|
|
287
|
-
#
|
|
316
|
+
# The warning is a DIAGNOSTIC -> always stderr (never stdout), so both the digest
|
|
317
|
+
# and --json keep a clean, machine-readable stdout (no single quotes in this body).
|
|
288
318
|
if d.get("stopped_without_finish"):
|
|
289
319
|
print("warning: drive ended without calling finish — treat the summary as a", file=sys.stderr)
|
|
290
320
|
print(" partial (the model stopped mid-task), not an authoritative result.", file=sys.stderr)
|
|
291
321
|
elif d.get("not_finished"):
|
|
292
322
|
print("warning: drive ran out of steps without finishing — summary is partial.", file=sys.stderr)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
323
|
+
if json_mode:
|
|
324
|
+
# --json contract: stdout carries ONLY the TaskResult JSON; every
|
|
325
|
+
# human/diagnostic line already went to stderr above. The exit code still
|
|
326
|
+
# reflects drive success/failure.
|
|
327
|
+
#
|
|
328
|
+
# artifacts_path mirrors the digest gate (ASK_COLLEAGUE_GRADABLE): it is only
|
|
329
|
+
# meaningful when the artifact SURVIVES. A preview (run_preview passes empty
|
|
330
|
+
# gradable) drives in a throwaway worktree the EXIT trap deletes, so the raw
|
|
331
|
+
# path points at a dir that is gone by the time the caller reads it — drop it
|
|
332
|
+
# rather than hand a machine consumer a dead path (#186 qodo finding-3). When
|
|
333
|
+
# gradable, rewrite it to the preserved copy in the real repo.
|
|
334
|
+
gradable = os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1"
|
|
335
|
+
if not gradable:
|
|
336
|
+
d.pop("artifacts_path", None)
|
|
337
|
+
elif ap:
|
|
338
|
+
d["artifacts_path"] = ap
|
|
339
|
+
json.dump(d, sys.stdout)
|
|
340
|
+
sys.stdout.write("\n")
|
|
341
|
+
# The task:/grade: hints are diagnostics -> stderr (stdout stays pure JSON).
|
|
342
|
+
# task_id is already in the payload, but echoing the copy-paste grade hint
|
|
343
|
+
# keeps the convention every work item follows (rule 907536) without breaking
|
|
344
|
+
# the stdout contract. Gated on gradable, exactly like the digest below.
|
|
345
|
+
if tid and gradable:
|
|
346
|
+
print("task:", tid, file=sys.stderr)
|
|
347
|
+
print("grade:", "ask-colleague feedback", tid, "--rating N", file=sys.stderr)
|
|
348
|
+
else:
|
|
349
|
+
out = sys.stdout if ok else sys.stderr
|
|
350
|
+
print("status:", d.get("status"), file=out)
|
|
351
|
+
if tid:
|
|
352
|
+
print("task:", tid, file=out)
|
|
353
|
+
print(file=out)
|
|
354
|
+
print((d.get("summary") or "").rstrip(), file=out)
|
|
355
|
+
cf = d.get("changed_files") or []
|
|
356
|
+
if cf:
|
|
357
|
+
print("\nchanged files:", ", ".join(cf), file=out)
|
|
358
|
+
if d.get("branch"):
|
|
359
|
+
print("drive branch:", d["branch"], file=out)
|
|
360
|
+
if ap and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1":
|
|
361
|
+
print("artifact:", ap, file=out)
|
|
362
|
+
# A drive is gradable whenever its artifact survives — including a FAILED
|
|
363
|
+
# drive (colleague writes an artifact on failure too, h5): a failure rated
|
|
364
|
+
# 1/5 is exactly the ROI signal, so the hint must not be gated on `ok` (#139
|
|
365
|
+
# qodo). It prints to `out` (stderr on failure), matching the failure digest.
|
|
366
|
+
if tid and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1":
|
|
367
|
+
print("grade:", "ask-colleague feedback", tid, "--rating N", file=out)
|
|
312
368
|
if ok:
|
|
313
369
|
sys.exit(0)
|
|
314
370
|
else:
|
|
@@ -471,11 +527,15 @@ run_preview() {
|
|
|
471
527
|
local prc=0
|
|
472
528
|
printf '%s' "$out" | print_result "" "" "$rc" || prc=$?
|
|
473
529
|
if [[ "$prc" -eq 0 ]]; then
|
|
530
|
+
# In --json mode stdout is reserved for the result JSON print_result just
|
|
531
|
+
# emitted, so the would-be patch is a diagnostic -> stderr (fd 2).
|
|
532
|
+
local diff_fd=1
|
|
533
|
+
[[ "$JSON_OUT" -eq 1 ]] && diff_fd=2
|
|
474
534
|
if [[ -n "$patch" ]]; then
|
|
475
|
-
printf '\n--- preview diff (NOT applied — pass --apply to land it) ---\n'
|
|
476
|
-
printf '%s\n' "$patch"
|
|
535
|
+
printf '\n--- preview diff (NOT applied — pass --apply to land it) ---\n' >&"$diff_fd"
|
|
536
|
+
printf '%s\n' "$patch" >&"$diff_fd"
|
|
477
537
|
else
|
|
478
|
-
printf '\n(preview: no file changes reported; NOT applied)\n'
|
|
538
|
+
printf '\n(preview: no file changes reported; NOT applied)\n' >&"$diff_fd"
|
|
479
539
|
fi
|
|
480
540
|
fi
|
|
481
541
|
return "$prc"
|
|
@@ -543,6 +603,9 @@ run_feedback() {
|
|
|
543
603
|
cmd+=(show "$ref")
|
|
544
604
|
fi
|
|
545
605
|
cmd+=(--repo "$REPO")
|
|
606
|
+
# colleague feedback supports --json natively; forward the operator's request
|
|
607
|
+
# so stdout stays machine-readable end-to-end.
|
|
608
|
+
[[ "$JSON_OUT" -eq 1 ]] && cmd+=(--json)
|
|
546
609
|
"${cmd[@]}"
|
|
547
610
|
}
|
|
548
611
|
|
|
@@ -556,6 +619,8 @@ run_feedback() {
|
|
|
556
619
|
run_clean() {
|
|
557
620
|
local cmd=("${COLLEAGUE[@]}" clean --repo "$REPO")
|
|
558
621
|
[[ "$DRY_RUN" -eq 1 ]] && cmd+=(--dry-run)
|
|
622
|
+
# colleague clean supports --json natively; forward it for machine-readable output.
|
|
623
|
+
[[ "$JSON_OUT" -eq 1 ]] && cmd+=(--json)
|
|
559
624
|
"${cmd[@]}"
|
|
560
625
|
}
|
|
561
626
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mass-update
|
|
3
|
+
type: command
|
|
4
|
+
description: >
|
|
5
|
+
Propagate ONE deterministic change across many GitHub repos at once. Targets
|
|
6
|
+
are GitHub slugs (`owner/repo`), not local paths. For each target, mass-update
|
|
7
|
+
clones fresh into a scratch dir, applies a verbatim file drop (`--file`) or
|
|
8
|
+
anchored edit (`--edit`), and cleans up on exit. Default is PREVIEW (zero
|
|
9
|
+
outward effect); `--pr` pushes a branch, opens a PR, and files a companion
|
|
10
|
+
tracking issue. No LLM, no ask-colleague — increment 1 is deterministic only.
|
|
11
|
+
Triggers when the user says "mass update", "propagate this change across
|
|
12
|
+
repos", "apply X to all these repos", "roll this out to agentculture/foo", or
|
|
13
|
+
"relicense these repos". One call = one PR per repo; run it several times for
|
|
14
|
+
independent changes.
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# mass-update — one deterministic change, many repos
|
|
18
|
+
|
|
19
|
+
`mass-update` is a **deterministic** cross-repo propagator. It clones each target
|
|
20
|
+
repo fresh into a scratch directory, applies verbatim file drops or anchored
|
|
21
|
+
edits, and cleans up on exit. No LLM, no `ask-colleague`.
|
|
22
|
+
|
|
23
|
+
## What changed
|
|
24
|
+
|
|
25
|
+
mass-update previously drove `ask-colleague` against sibling local checkouts
|
|
26
|
+
(worktrees, `colleague/<id>` branches, `--apply`/`--pr` landing). That flow is
|
|
27
|
+
gone. The new mechanism:
|
|
28
|
+
|
|
29
|
+
- **Targets are GitHub slugs** (`owner/repo`), not local paths. A local path is
|
|
30
|
+
rejected.
|
|
31
|
+
- For each target it **clones fresh** into a gitignored scratch dir
|
|
32
|
+
(`.mass-update/clones/<owner>-<repo>/`) inside rollout-cli, branches off the
|
|
33
|
+
default branch, applies a deterministic change in the clone, and cleans the
|
|
34
|
+
clone up on exit (success or failure). `--keep-clone` retains it for debugging.
|
|
35
|
+
- The cardinal rule: **rollout-cli never reaches beyond its own repo.** The
|
|
36
|
+
entire on-disk footprint stays inside rollout-cli; the only outward effects
|
|
37
|
+
are `git push` (the PR) and `/communicate` (the issue).
|
|
38
|
+
- Increment 1 supports only deterministic ops: `--file` (verbatim) and
|
|
39
|
+
`--edit` (anchored unique-occurrence replace). Free-text described changes are
|
|
40
|
+
a deferred follow-up.
|
|
41
|
+
|
|
42
|
+
## How to run
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bash .claude/skills/mass-update/scripts/mass-update.sh \
|
|
46
|
+
--repos <owner/repo[,owner/repo...]> <ops> [--pr] [--keep-clone]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Run it from the rollout-cli repo root. `--repos` takes comma-separated GitHub
|
|
50
|
+
slugs (e.g. `agentculture/widget-cli`).
|
|
51
|
+
|
|
52
|
+
## Op shapes (combinable in one call)
|
|
53
|
+
|
|
54
|
+
| Op | What it does |
|
|
55
|
+
|----|--------------|
|
|
56
|
+
| `--file DEST --from SRC` | Create/replace `DEST` in each clone with **exactly** `SRC`'s bytes (verbatim). Use this for content the model shouldn't regenerate — a `LICENSE`, a CI workflow, a config file. |
|
|
57
|
+
| `--file DEST --content '...'` | Same, but the verbatim content is given inline. |
|
|
58
|
+
| `--edit 'FILE: FIND => REPLACE'` | Replace the **unique** occurrence of `FIND` with `REPLACE` in `FILE`. Errors on 0 or >1 matches. Repeatable. |
|
|
59
|
+
|
|
60
|
+
All ops given in one call are applied to each clone. At least one op is required.
|
|
61
|
+
|
|
62
|
+
## Landing — preview or PR
|
|
63
|
+
|
|
64
|
+
| Flag | Effect |
|
|
65
|
+
|------|--------|
|
|
66
|
+
| *(none)* | **PREVIEW.** Clone, apply ops, print diff, cleanup. **Zero outward effect.** Always start here. |
|
|
67
|
+
| `--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. |
|
|
68
|
+
| `--keep-clone` | Retain the scratch clone on exit (debug). |
|
|
69
|
+
|
|
70
|
+
## Worked example — propagate a LICENSE to a sibling repo
|
|
71
|
+
|
|
72
|
+
Replace `agentculture/widget-cli`'s `LICENSE` with rollout-cli's own `LICENSE`,
|
|
73
|
+
then open a PR and file a tracking issue:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bash .claude/skills/mass-update/scripts/mass-update.sh \
|
|
77
|
+
--repos agentculture/widget-cli \
|
|
78
|
+
--file LICENSE --from ./LICENSE \
|
|
79
|
+
--pr
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Run it **without `--pr` first** to inspect the preview diff; re-run with `--pr`
|
|
83
|
+
once the diff is correct.
|
|
84
|
+
|
|
85
|
+
## Where it fits
|
|
86
|
+
|
|
87
|
+
- **`rollout` engine** — deterministic recipes, reproducible, no model. Reach for
|
|
88
|
+
it for org-wide fan-out (`--org`) and exact, scriptable transforms.
|
|
89
|
+
- **`mass-update`** — *this skill*: deterministic cross-repo propagation via
|
|
90
|
+
fresh clones, for verbatim file drops and anchored edits across a handful of
|
|
91
|
+
GitHub repos.
|
|
92
|
+
|
|
93
|
+
## Honest limits
|
|
94
|
+
|
|
95
|
+
- **Deterministic only (increment 1).** Free-text described changes are NOT
|
|
96
|
+
supported — they are a deferred follow-up that needs an `ask-colleague`
|
|
97
|
+
edit-in-place mode.
|
|
98
|
+
- **One drive per target.** Each target succeeds or fails independently; a
|
|
99
|
+
non-zero exit means at least one target failed (others may have landed).
|
|
100
|
+
Re-run for the failures.
|
|
101
|
+
- **Requires `gh` and `git`.** Both must be on PATH and authenticated
|
|
102
|
+
(`gh auth status` must succeed) before any clone begins.
|
|
103
|
+
- **`--keep-clone`** retains the scratch directory for debugging; without it,
|
|
104
|
+
clones are cleaned up on both success and failure.
|
|
105
|
+
|
|
106
|
+
## Provenance
|
|
107
|
+
|
|
108
|
+
mass-update is a rollout-cli-local skill. The `cite, don't import` policy holds:
|
|
109
|
+
downstream repos copy it, they don't symlink or depend on it.
|