rollout-cli 0.3.0__tar.gz → 0.3.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.0 → rollout_cli-0.3.2}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +105 -46
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.markdownlint-cli2.yaml +3 -0
- rollout_cli-0.3.2/AGENTS.colleague.md +11 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/CHANGELOG.md +44 -0
- rollout_cli-0.3.2/CLAUDE.md +79 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/PKG-INFO +1 -1
- rollout_cli-0.3.2/culture.yaml +4 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/docs/skill-sources.md +6 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/pyproject.toml +1 -1
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/README.md +84 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/apply.sh +39 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/check.sh +31 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +196 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/files/prompts/explore.md +25 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/files/prompts/review.md +36 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/files/prompts/write.md +15 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/files/scripts/ask-colleague.sh +568 -0
- rollout_cli-0.3.2/recipes/ask-colleague-revendor/manifest.toml +25 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_cli.py +2 -2
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/uv.lock +1 -1
- rollout_cli-0.3.0/CLAUDE.md +0 -28
- rollout_cli-0.3.0/culture.yaml +0 -3
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/agent-config/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/cicd/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/run-tests/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/think/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/think/scripts/think.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/version-bump/SKILL.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills.local.yaml.example +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.flake8 +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.github/workflows/publish.yml +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.github/workflows/tests.yml +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/.gitignore +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/LICENSE +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/README.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/recipes/apache-relicense/apply.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/recipes/apache-relicense/check.sh +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/recipes/apache-relicense/files/LICENSE +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/recipes/apache-relicense/manifest.toml +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/__init__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/__main__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/__init__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/__init__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/apply.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/cli.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/doctor.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/explain.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/learn.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/overview.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/plan.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_commands/whoami.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_errors.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/cli/_output.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/__init__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/bump.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/devex_pr.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/gitops.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/orchestrate.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/preflight.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/recipe.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/report.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/core/targets.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/explain/__init__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/rollout/explain/catalog.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/sonar-project.properties +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/__init__.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_apache_relicense.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_apply_cmd.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_bump.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_cli_introspection.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_cli_wiring.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_devex_pr.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_e2e_apache_relicense.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_explain_catalog.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_gitops.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_orchestrate.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_plan_cmd.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_preflight.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_recipe.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_report.py +0 -0
- {rollout_cli-0.3.0 → rollout_cli-0.3.2}/tests/test_targets.py +0 -0
{rollout_cli-0.3.0 → rollout_cli-0.3.2}/.claude/skills/ask-colleague/scripts/ask-colleague.sh
RENAMED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# ask-colleague write "<task>" [--apply] implement a change (preview by default)
|
|
12
12
|
# ask-colleague feedback <id|last> --rating N grade a past drive (ROI loop); no rating -> show
|
|
13
13
|
# ask-colleague feedback list list every recorded drive by request + grade
|
|
14
|
+
# ask-colleague clean reap stale colleague/* branches + artifacts (#162)
|
|
14
15
|
#
|
|
15
16
|
# explore/review run in a throwaway `git worktree` at HEAD, so they can never
|
|
16
17
|
# touch your working tree or branch (any stray write is discarded). write also
|
|
@@ -22,6 +23,18 @@
|
|
|
22
23
|
# most recent consequential write. Grade a probe by its printed task-id (every
|
|
23
24
|
# drive prints `task:` and a `grade:` hint), or find it with `feedback list`.
|
|
24
25
|
#
|
|
26
|
+
# A crashed/interrupted `write --apply` can leave a dangling colleague/<id>
|
|
27
|
+
# branch (and 0-byte .colleague/ artifacts) that breaks `git fetch`. The
|
|
28
|
+
# EXIT-trap cleanup below only reaps the *current* read-only run's worktree, not
|
|
29
|
+
# a *prior* crashed run — `ask-colleague clean` (which shells out to `colleague
|
|
30
|
+
# clean`) reaps those, scoped strictly to colleague/* so an unrelated branch is
|
|
31
|
+
# never touched.
|
|
32
|
+
#
|
|
33
|
+
# Exit-code policy (matches colleague's CLI contract, #161): 0 success · 1
|
|
34
|
+
# user-input error (bad/missing verb, flag, arg, or path; dirty-tree state guard
|
|
35
|
+
# — same class as the runtime's EXIT_USER_ERROR guard) · 2 environment/setup
|
|
36
|
+
# error (missing required tool, colleague CLI not found, missing prompt template).
|
|
37
|
+
#
|
|
25
38
|
set -euo pipefail
|
|
26
39
|
|
|
27
40
|
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
@@ -29,24 +42,28 @@ PROMPTS_DIR="$SKILL_DIR/prompts"
|
|
|
29
42
|
|
|
30
43
|
# ── resolve the colleague CLI (installed, then local-dev fallback) ─────────
|
|
31
44
|
COLLEAGUE=()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return 0
|
|
36
|
-
fi
|
|
37
|
-
# Local-dev fallback: inside the colleague checkout, run via uv.
|
|
38
|
-
local dir="$PWD"
|
|
45
|
+
|
|
46
|
+
_colleague_via_uv() {
|
|
47
|
+
local dir="$1"
|
|
39
48
|
while [[ -n "$dir" ]] && [[ "$dir" != "/" ]]; do
|
|
40
49
|
if [[ -f "$dir/pyproject.toml" ]] \
|
|
41
50
|
&& grep -q '^name = "colleague"' "$dir/pyproject.toml" 2>/dev/null; then
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
fi
|
|
46
|
-
break
|
|
51
|
+
command -v uv >/dev/null 2>&1 || return 1
|
|
52
|
+
COLLEAGUE=(uv run --project "$dir" colleague)
|
|
53
|
+
return 0
|
|
47
54
|
fi
|
|
48
55
|
dir=$(dirname "$dir")
|
|
49
56
|
done
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resolve_colleague() {
|
|
61
|
+
if command -v colleague >/dev/null 2>&1; then
|
|
62
|
+
COLLEAGUE=(colleague) # installed tool — the normal case
|
|
63
|
+
return 0
|
|
64
|
+
fi
|
|
65
|
+
_colleague_via_uv "$PWD" && return 0
|
|
66
|
+
_colleague_via_uv "$REPO" && return 0
|
|
50
67
|
cat >&2 <<'EOF'
|
|
51
68
|
error: colleague CLI not found.
|
|
52
69
|
hint: install it with `uv tool install colleague` (or `pipx install colleague`),
|
|
@@ -66,9 +83,11 @@ Usage:
|
|
|
66
83
|
ask-colleague write "<task>" [--apply|--pr] Implement a change (preview by default; --apply lands it)
|
|
67
84
|
ask-colleague feedback <id|last> [--rating N] Grade a past drive (ROI loop); with --rating records, without shows
|
|
68
85
|
ask-colleague feedback list List every recorded drive by request + grade (find one by its request)
|
|
86
|
+
ask-colleague clean [--dry-run] Reap stale/corrupt colleague/* branches + orphaned .colleague/ artifacts (#162)
|
|
69
87
|
|
|
70
88
|
Options:
|
|
71
89
|
--repo PATH Target repo (default: .)
|
|
90
|
+
--dry-run (clean) report what would be reaped without changing anything
|
|
72
91
|
--base BRANCH Base for `review` diff (default: main)
|
|
73
92
|
--engine NAME Backend plugin (default: $COLLEAGUE_ENGINE or vllm-openai)
|
|
74
93
|
--model NAME Model (default: $COLLEAGUE_MODEL or sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP)
|
|
@@ -89,19 +108,22 @@ feedback grades a finished drive: stats (in the artifact) say what it cost,
|
|
|
89
108
|
feedback says how good it was — together, the ROI of outsourcing. explore/review
|
|
90
109
|
do not move `last` (they are read-only) — grade them by their printed task-id, or
|
|
91
110
|
run `ask-colleague feedback list` to find a drive by its request.
|
|
111
|
+
clean recovers a repo a crashed run wedged: it reaps stale/corrupt colleague/*
|
|
112
|
+
branches + orphaned .colleague/ artifacts (scoped to colleague/* only). For the
|
|
113
|
+
full flag set (--merged / --older-than) call `colleague clean` directly.
|
|
92
114
|
EOF
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
# ── parse the verb ──────────────────────────────────────────────────────────
|
|
96
118
|
VERB="${1:-}"
|
|
97
119
|
case "$VERB" in
|
|
98
|
-
explore | review | write | feedback) shift ;;
|
|
120
|
+
explore | review | write | feedback | clean) shift ;;
|
|
99
121
|
-h | --help) usage; exit 0 ;;
|
|
100
|
-
"") usage >&2; exit
|
|
122
|
+
"") usage >&2; exit 1 ;; # missing arg -> user-input error (#161)
|
|
101
123
|
*)
|
|
102
|
-
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback)" >&2
|
|
124
|
+
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean)" >&2
|
|
103
125
|
echo "hint: run 'ask-colleague --help'" >&2
|
|
104
|
-
exit
|
|
126
|
+
exit 1 # bad verb -> user-input error (#161)
|
|
105
127
|
;;
|
|
106
128
|
esac
|
|
107
129
|
|
|
@@ -125,7 +147,7 @@ need_value() { # $1 = remaining arg count ($#), $2 = flag name
|
|
|
125
147
|
[[ "$1" -ge 2 ]] || {
|
|
126
148
|
echo "error: $2 requires a value" >&2
|
|
127
149
|
echo "hint: run 'ask-colleague --help'" >&2
|
|
128
|
-
exit
|
|
150
|
+
exit 1 # missing flag value -> user-input error (#161)
|
|
129
151
|
}
|
|
130
152
|
}
|
|
131
153
|
|
|
@@ -143,6 +165,7 @@ TIMEOUT="${COLLEAGUE_TIMEOUT:-${CONVERTIBLE_TIMEOUT:-300}}"
|
|
|
143
165
|
ALLOW_DIRTY=0
|
|
144
166
|
APPLY=0
|
|
145
167
|
OPEN_PR=0
|
|
168
|
+
DRY_RUN=0
|
|
146
169
|
RATING=""
|
|
147
170
|
NOTES=""
|
|
148
171
|
BY=""
|
|
@@ -160,33 +183,41 @@ while [[ $# -gt 0 ]]; do
|
|
|
160
183
|
--apply) APPLY=1; shift ;;
|
|
161
184
|
--allow-dirty) ALLOW_DIRTY=1; shift ;;
|
|
162
185
|
--pr) OPEN_PR=1; shift ;;
|
|
186
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
163
187
|
--rating) need_value "$#" "$1"; RATING="$2"; shift 2 ;;
|
|
164
188
|
--notes) need_value "$#" "$1"; NOTES="$2"; shift 2 ;;
|
|
165
189
|
--by) need_value "$#" "$1"; BY="$2"; shift 2 ;;
|
|
166
190
|
-h | --help) usage; exit 0 ;;
|
|
167
191
|
--) shift; while [[ $# -gt 0 ]]; do ARG="${ARG:+$ARG }$1"; shift; done ;;
|
|
168
|
-
|
|
192
|
+
# unknown option -> user-input error (#161)
|
|
193
|
+
-*) echo "error: unknown option '$1'" >&2; echo "hint: run 'ask-colleague --help'" >&2; exit 1 ;;
|
|
169
194
|
*) ARG="${ARG:+$ARG }$1"; shift ;;
|
|
170
195
|
esac
|
|
171
196
|
done
|
|
172
197
|
|
|
173
|
-
|
|
174
|
-
|
|
198
|
+
# clean takes no description argument; every other verb requires one. (All of the
|
|
199
|
+
# guards below are user-input errors -> exit 1, per the policy comment at the top.)
|
|
200
|
+
if [[ "$VERB" == "clean" ]]; then
|
|
201
|
+
[[ -z "$ARG" ]] || { echo "error: clean takes no description argument" >&2; exit 1; }
|
|
202
|
+
else
|
|
203
|
+
[[ -n "$ARG" ]] || { echo "error: $VERB needs a description argument" >&2; usage >&2; exit 1; }
|
|
204
|
+
fi
|
|
205
|
+
[[ -d "$REPO" ]] || { echo "error: --repo is not a directory: $REPO" >&2; exit 1; }
|
|
175
206
|
REPO="$(cd "$REPO" && pwd)"
|
|
176
207
|
|
|
177
208
|
# One git-repo guard for every verb: --repo is a runtime target like `git -C`, but
|
|
178
209
|
# it must at least be a real git work tree. Fail fast with a clear message instead
|
|
179
|
-
# of an opaque mid-drive error (
|
|
180
|
-
#
|
|
210
|
+
# of an opaque mid-drive error (read-only verbs add a worktree, write commits a
|
|
211
|
+
# drive branch, clean reaps colleague/* refs).
|
|
181
212
|
git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
|
182
|
-
|| { echo "error: --repo is not a git repository: $REPO" >&2; exit
|
|
213
|
+
|| { echo "error: --repo is not a git repository: $REPO" >&2; exit 1; }
|
|
183
214
|
|
|
184
215
|
# review interpolates --base into the LLM instruction ("git diff $BASE...HEAD"),
|
|
185
216
|
# so reject a value that is not a real commit/ref before it is rendered into the
|
|
186
217
|
# prompt — fail fast rather than hand the model a bogus (or injected) ref.
|
|
187
218
|
if [[ "$VERB" == "review" ]]; then
|
|
188
219
|
git -C "$REPO" rev-parse --verify --quiet "${BASE}^{commit}" >/dev/null 2>&1 \
|
|
189
|
-
|| { echo "error: --base is not a valid commit/ref in $REPO: $BASE" >&2; exit
|
|
220
|
+
|| { echo "error: --base is not a valid commit/ref in $REPO: $BASE" >&2; exit 1; }
|
|
190
221
|
fi
|
|
191
222
|
|
|
192
223
|
resolve_colleague || exit 2
|
|
@@ -229,7 +260,9 @@ print_result() {
|
|
|
229
260
|
# real repo) -> print a copy-paste `grade:` hint with the explicit task-id, so
|
|
230
261
|
# the caller never has to rely on `last` (issue #132). A preview leaves it
|
|
231
262
|
# empty (its artifact was discarded with the worktree, so it is not gradable).
|
|
232
|
-
|
|
263
|
+
# $3 (optional): exit code from the colleague drive command, propagated to
|
|
264
|
+
# 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 '
|
|
233
266
|
import sys, json, os
|
|
234
267
|
raw = sys.stdin.read().strip()
|
|
235
268
|
if not raw:
|
|
@@ -268,7 +301,7 @@ ap = d.get("artifacts_path")
|
|
|
268
301
|
real_dir = os.environ.get("ASK_COLLEAGUE_REAL_ARTIFACT_DIR") or ""
|
|
269
302
|
if ap and real_dir:
|
|
270
303
|
ap = os.path.join(real_dir, os.path.basename(ap))
|
|
271
|
-
if ap:
|
|
304
|
+
if ap and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1":
|
|
272
305
|
print("artifact:", ap, file=out)
|
|
273
306
|
# A drive is gradable whenever its artifact survives — including a FAILED drive
|
|
274
307
|
# (colleague writes an artifact on failure too, h5): a failure rated 1/5 is exactly
|
|
@@ -276,7 +309,14 @@ if ap:
|
|
|
276
309
|
# `out` (stderr on failure), matching the rest of the failure digest.
|
|
277
310
|
if tid and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1":
|
|
278
311
|
print("grade:", "ask-colleague feedback", tid, "--rating N", file=out)
|
|
279
|
-
|
|
312
|
+
if ok:
|
|
313
|
+
sys.exit(0)
|
|
314
|
+
else:
|
|
315
|
+
rc = os.environ.get("ASK_COLLEAGUE_DRIVE_RC")
|
|
316
|
+
if rc in ("1", "2"):
|
|
317
|
+
sys.exit(int(rc))
|
|
318
|
+
else:
|
|
319
|
+
sys.exit(1)
|
|
280
320
|
'
|
|
281
321
|
}
|
|
282
322
|
|
|
@@ -394,8 +434,8 @@ _add_worktree() {
|
|
|
394
434
|
run_readonly() {
|
|
395
435
|
local instruction="$1"
|
|
396
436
|
_add_worktree
|
|
397
|
-
local out
|
|
398
|
-
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" ||
|
|
437
|
+
local out rc=0
|
|
438
|
+
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
|
|
399
439
|
_DRIVE_BRANCH="$(printf '%s' "$out" | _extract_branch)"
|
|
400
440
|
# Preserve the artifact to the real repo BEFORE the EXIT trap removes the
|
|
401
441
|
# worktree, so the drive can be graded by its task-id (`ask-colleague feedback
|
|
@@ -409,7 +449,7 @@ run_readonly() {
|
|
|
409
449
|
real_dir="$REPO/.colleague"
|
|
410
450
|
gradable="1"
|
|
411
451
|
fi
|
|
412
|
-
printf '%s' "$out" | print_result "$real_dir" "$gradable"
|
|
452
|
+
printf '%s' "$out" | print_result "$real_dir" "$gradable" "$rc"
|
|
413
453
|
}
|
|
414
454
|
|
|
415
455
|
# ── write preview (default): drive in a throwaway worktree, show the would-be ──
|
|
@@ -418,8 +458,8 @@ run_readonly() {
|
|
|
418
458
|
run_preview() {
|
|
419
459
|
local instruction="$1"
|
|
420
460
|
_add_worktree
|
|
421
|
-
local out
|
|
422
|
-
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" ||
|
|
461
|
+
local out rc=0
|
|
462
|
+
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
|
|
423
463
|
_DRIVE_BRANCH="$(printf '%s' "$out" | _extract_branch)"
|
|
424
464
|
|
|
425
465
|
# Capture the would-be patch before _cleanup_worktree deletes the drive branch.
|
|
@@ -428,9 +468,9 @@ run_preview() {
|
|
|
428
468
|
patch="$(git -C "$REPO" diff "HEAD..$_DRIVE_BRANCH" 2>/dev/null || true)"
|
|
429
469
|
fi
|
|
430
470
|
|
|
431
|
-
local
|
|
432
|
-
printf '%s' "$out" | print_result ||
|
|
433
|
-
if [[ "$
|
|
471
|
+
local prc=0
|
|
472
|
+
printf '%s' "$out" | print_result "" "" "$rc" || prc=$?
|
|
473
|
+
if [[ "$prc" -eq 0 ]]; then
|
|
434
474
|
if [[ -n "$patch" ]]; then
|
|
435
475
|
printf '\n--- preview diff (NOT applied — pass --apply to land it) ---\n'
|
|
436
476
|
printf '%s\n' "$patch"
|
|
@@ -438,7 +478,7 @@ run_preview() {
|
|
|
438
478
|
printf '\n(preview: no file changes reported; NOT applied)\n'
|
|
439
479
|
fi
|
|
440
480
|
fi
|
|
441
|
-
return "$
|
|
481
|
+
return "$prc"
|
|
442
482
|
}
|
|
443
483
|
|
|
444
484
|
# ── write verb: preview by default; --apply lands a drive branch; --pr opens a ─
|
|
@@ -454,7 +494,10 @@ run_write() {
|
|
|
454
494
|
&& [[ -n "$(git -C "$REPO" status --porcelain 2>/dev/null)" ]]; then
|
|
455
495
|
echo "error: working tree is dirty — commit/stash first, or pass --allow-dirty" >&2
|
|
456
496
|
echo "hint: 'colleague drive --no-pr' commits uncommitted edits onto the drive branch" >&2
|
|
457
|
-
exit
|
|
497
|
+
# User-fixable state guard -> exit 1, matching the runtime's own
|
|
498
|
+
# dirty-tree guard (colleague/handoff.py _guard_clean_tree =
|
|
499
|
+
# EXIT_USER_ERROR), not exit 2 (#161).
|
|
500
|
+
exit 1
|
|
458
501
|
fi
|
|
459
502
|
# The runtime now enforces its own dirty-tree guard (colleague#149); pass
|
|
460
503
|
# --allow-dirty through so an operator opt-in here isn't re-refused by the
|
|
@@ -462,20 +505,22 @@ run_write() {
|
|
|
462
505
|
# top — read-only/preview verbs run in a clean worktree and must not get it),
|
|
463
506
|
# which sidesteps the `set -u` empty-array expansion hazard.
|
|
464
507
|
[[ "$ALLOW_DIRTY" -eq 1 ]] && COMMON_FLAGS+=(--allow-dirty)
|
|
465
|
-
# `||
|
|
466
|
-
#
|
|
467
|
-
#
|
|
468
|
-
#
|
|
469
|
-
#
|
|
470
|
-
|
|
508
|
+
# `|| rc=$?`: a failed drive (`colleague drive` exits non-zero, printing the
|
|
509
|
+
# result JSON to stdout) must still flow into print_result so the digest is
|
|
510
|
+
# emitted (to stderr) and the wrapper propagates the drive's real exit code.
|
|
511
|
+
# The rc is captured (declaration split from the assignment so `local` doesn't
|
|
512
|
+
# mask it) rather than discarded with `|| true`, which would have collapsed the
|
|
513
|
+
# tri-state rc; `set -e` does not abort at the assignment. Matches the
|
|
514
|
+
# read-only / preview paths, which guard this way.
|
|
515
|
+
local out rc=0
|
|
471
516
|
if [[ "$OPEN_PR" -eq 1 ]]; then
|
|
472
|
-
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$REPO" "${COMMON_FLAGS[@]}")" ||
|
|
517
|
+
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$REPO" "${COMMON_FLAGS[@]}")" || rc=$?
|
|
473
518
|
else
|
|
474
|
-
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$REPO" --no-pr "${COMMON_FLAGS[@]}")" ||
|
|
519
|
+
out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$REPO" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
|
|
475
520
|
fi
|
|
476
521
|
# A landed write persists its artifact in the real repo and moves `last`, so it
|
|
477
522
|
# is gradable — print the `grade:` hint (with the explicit task-id).
|
|
478
|
-
printf '%s' "$out" | print_result "" "1"
|
|
523
|
+
printf '%s' "$out" | print_result "" "1" "$rc"
|
|
479
524
|
}
|
|
480
525
|
|
|
481
526
|
# ── feedback verb: grade a finished drive (the ROI loop) ────────────────────
|
|
@@ -501,9 +546,23 @@ run_feedback() {
|
|
|
501
546
|
"${cmd[@]}"
|
|
502
547
|
}
|
|
503
548
|
|
|
549
|
+
# ── clean verb: recover a repo a crashed run wedged (#162) ──────────────────
|
|
550
|
+
# A thin pass-through to `colleague clean`: the runtime reaps stale/corrupt
|
|
551
|
+
# colleague/* branches + orphaned .colleague/ artifacts, scoped strictly to
|
|
552
|
+
# colleague/* (so an unrelated branch is never touched) and conservative with
|
|
553
|
+
# .git/objects. No worktree, no engine — colleague owns the reap + its own
|
|
554
|
+
# stdout/stderr/exit. The full flag set (--merged / --older-than) lives on
|
|
555
|
+
# `colleague clean`; the skill forwards the common --dry-run.
|
|
556
|
+
run_clean() {
|
|
557
|
+
local cmd=("${COLLEAGUE[@]}" clean --repo "$REPO")
|
|
558
|
+
[[ "$DRY_RUN" -eq 1 ]] && cmd+=(--dry-run)
|
|
559
|
+
"${cmd[@]}"
|
|
560
|
+
}
|
|
561
|
+
|
|
504
562
|
case "$VERB" in
|
|
505
563
|
explore) run_readonly "$(render_prompt explore)" ;;
|
|
506
564
|
review) run_readonly "$(render_prompt review)" ;;
|
|
507
565
|
write) run_write "$(render_prompt write)" ;;
|
|
508
566
|
feedback) run_feedback "$ARG" ;;
|
|
567
|
+
clean) run_clean ;;
|
|
509
568
|
esac
|
|
@@ -23,6 +23,9 @@ ignores:
|
|
|
23
23
|
- ".teken/**"
|
|
24
24
|
# Vendored skills are cited verbatim from guildmaster — do not reformat them.
|
|
25
25
|
- ".claude/skills/**"
|
|
26
|
+
# Recipe payload snapshots are cited verbatim (cite-don't-import) and copied
|
|
27
|
+
# into target repos byte-for-byte — do not reformat them.
|
|
28
|
+
- "recipes/**/files/**"
|
|
26
29
|
# devague-generated convergence artifacts (the exact `devague export` output;
|
|
27
30
|
# headings carry the announcement verbatim and claims embed <placeholder>s).
|
|
28
31
|
- "docs/specs/**"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Colleague Resident
|
|
2
|
+
|
|
3
|
+
You are a colleague resident — a long-lived mesh peer that works alongside
|
|
4
|
+
other agents in the AgentCulture IRC mesh. Your job is to assist with
|
|
5
|
+
scoped tasks delegated by the operator or peer agents, using the colleague
|
|
6
|
+
tool-loop (read_file / write_file / edit_file / list_dir / run_command /
|
|
7
|
+
finish).
|
|
8
|
+
|
|
9
|
+
Follow the operator's AGENTS.md instructions and the skills loaded from
|
|
10
|
+
.colleague/skills/ when present. Prefer small, reversible steps; handoff
|
|
11
|
+
via finish when done.
|
|
@@ -5,6 +5,50 @@ 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.3.2] - 2026-06-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Promoted into the Culture mesh as colleague resident `rollout-cli` (culture.yaml backend colleague, AGENTS.colleague.md)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Rewrote CLAUDE.md from the /init seed into a full runtime prompt: cardinal no-foreign-edits rule (ship outside only via /mass-update or /communicate), mechanism-picking guidance, and the devex/agex .gitignore rollout playbook
|
|
17
|
+
|
|
18
|
+
## [0.3.1] - 2026-06-12
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`ask-colleague-revendor` recipe** (`recipes/ask-colleague-revendor/`) —
|
|
23
|
+
propagates an upstream `ask-colleague` wrapper change from its origin
|
|
24
|
+
(colleague) to downstream consumers, and adds the skill fresh where absent.
|
|
25
|
+
Existing installs get `scripts/ask-colleague.sh` overwritten **only**
|
|
26
|
+
(wrapper-only, matching colleague#183; a consumer's `SKILL.md` provenance
|
|
27
|
+
token is never clobbered); absent installs get the full skill with the repo
|
|
28
|
+
nick substituted in. The recipe `README.md` carries the **downstream-consumer
|
|
29
|
+
ledger** and the **reconciliation procedure**, making rollout-cli the owner of
|
|
30
|
+
that propagation contract — closes #3.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Re-vendored rollout-cli's own `ask-colleague` wrapper from colleague#183
|
|
35
|
+
(`resolve_colleague()` honors `--repo`; `drive` tri-state exit propagated;
|
|
36
|
+
no dead `artifact:` line). `SKILL.md`/prompts untouched.
|
|
37
|
+
- `.markdownlint-cli2.yaml` ignores `recipes/**/files/**` — recipe payload
|
|
38
|
+
snapshots are cited verbatim and must not be reformatted.
|
|
39
|
+
- `docs/skill-sources.md` points to the recipe as the owner of downstream
|
|
40
|
+
`ask-colleague` propagation + consumer tracking.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- Hardened the `ask-colleague-revendor` recipe after review (Qodo on #5):
|
|
45
|
+
`check.sh` now requires a **quoted semver** `version` line and an existing
|
|
46
|
+
`CHANGELOG.md` (matching what `apply` actually needs, so a repo can't pass
|
|
47
|
+
`plan` then fail mid-`apply`; also drops a commented-`version` false positive);
|
|
48
|
+
`apply.sh` escapes `sed` replacement metacharacters in the repo nick; and
|
|
49
|
+
`manifest.toml` `touched_files` now declares the `SKILL.md`/prompt paths that
|
|
50
|
+
a fresh add writes.
|
|
51
|
+
|
|
8
52
|
## [0.3.0] - 2026-06-06
|
|
9
53
|
|
|
10
54
|
### Added
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# CLAUDE.md — rollout-cli
|
|
2
|
+
|
|
3
|
+
This repository hosts the **rollout-cli** agent: a cross-repo
|
|
4
|
+
**change-propagation engine**. Its job is to take one scoped change and land it
|
|
5
|
+
across many sibling repos — deterministically where the change is scriptable,
|
|
6
|
+
or via a different-backend colleague where the change is easier to describe than
|
|
7
|
+
to script.
|
|
8
|
+
|
|
9
|
+
## The cardinal rule — never touch another repo directly
|
|
10
|
+
|
|
11
|
+
**rollout-cli only ever edits its own working tree.** It does **not** open files
|
|
12
|
+
in sibling repos and edit them, run `Write`/`Edit` against paths outside this
|
|
13
|
+
repo, or `git commit` in another checkout. Making a change *outside this repo*
|
|
14
|
+
has exactly **two** acceptable channels, and nothing else:
|
|
15
|
+
|
|
16
|
+
| Channel | Skill | Use it to… |
|
|
17
|
+
|---------|-------|------------|
|
|
18
|
+
| **Ship code** | **`/mass-update`** | Propagate an actual code/file change to one or more sibling repos. It drives `ask-colleague` per target in a throwaway worktree (preview), then lands a `colleague/<id>` branch (`--apply`) or opens a PR (`--pr`). One call → one PR per repo. |
|
|
19
|
+
| **Ship a request** | **`/communicate`** | File a tracked GitHub issue on a sibling repo, pull a sibling issue into context, or send a message to a Culture mesh channel — when the right move is to *ask the owning repo* rather than push a diff (e.g. the change can't land as a PR there, or needs that repo's judgment). |
|
|
20
|
+
|
|
21
|
+
In-repo work (this `CLAUDE.md`, recipes under the rollout engine, skills under
|
|
22
|
+
`.claude/skills/`, docs, tests) is normal — edit freely. The rule is only about
|
|
23
|
+
**reaching outside**: if a change belongs in another repo, it goes through
|
|
24
|
+
`/mass-update` or `/communicate`. No exceptions, no "just this once" direct edits.
|
|
25
|
+
|
|
26
|
+
## Picking a mechanism
|
|
27
|
+
|
|
28
|
+
- **Deterministic `rollout` recipe engine** — for a *fixed, scriptable* transform
|
|
29
|
+
(drop an exact file, flip a config line) reproduced identically across repos.
|
|
30
|
+
Preferred when the content needs no model judgment; reproducible, no LLM. This
|
|
31
|
+
is rollout-cli's core engine (`rollout` recipes + `plan`/`apply`).
|
|
32
|
+
- **`/mass-update`** — the LLM-driven layer on `ask-colleague`, for a *described*
|
|
33
|
+
change across a handful of named repos (`--repos a,b,c`). Even here, prefer a
|
|
34
|
+
deterministic `--file DEST --content/--from` op (verbatim bytes the model must
|
|
35
|
+
not regenerate) over free-text when the content is fixed.
|
|
36
|
+
- **`/communicate`** — when no diff should land (the target can't accept it, or it
|
|
37
|
+
needs the owner's call). File an issue instead of forcing a PR.
|
|
38
|
+
|
|
39
|
+
Always **preview first** (mass-update/ask-colleague default: throwaway worktree,
|
|
40
|
+
zero side effects), then `--pr`. Colleague's output is a second opinion to
|
|
41
|
+
verify and own, never authority. Confirm before opening PRs across many repos.
|
|
42
|
+
|
|
43
|
+
## Identity
|
|
44
|
+
|
|
45
|
+
`culture.yaml` declares this agent as `suffix: rollout-cli`, `backend: colleague`
|
|
46
|
+
(promoted into the Culture mesh via `colleague promote`; owns `#rollout-cli`). The
|
|
47
|
+
resident runtime prompt is `AGENTS.colleague.md`.
|
|
48
|
+
|
|
49
|
+
## Rollout playbook: devex/agex data-dir `.gitignore`
|
|
50
|
+
|
|
51
|
+
A worked example of the discovery-before-fan-out discipline.
|
|
52
|
+
|
|
53
|
+
`devex doctor` warns when a repo's devex data dir is missing its `.gitignore`
|
|
54
|
+
(`⚠️ .devex/.gitignore missing — data/ may end up tracked`). The fix is a fixed
|
|
55
|
+
two-line file:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
# Managed by devex — do not edit.
|
|
59
|
+
data/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Do not blind-fan-out.** The data dir is `.devex/` in some repos and `.agex/`
|
|
63
|
+
in others, and the fix is only real where the data dir is **git-tracked**. Scan
|
|
64
|
+
first: for each sibling with a `<datadir>/data/` but no `<datadir>/.gitignore`,
|
|
65
|
+
check whether the *target file path* is already ignored —
|
|
66
|
+
`git check-ignore -q <datadir>/.gitignore`:
|
|
67
|
+
|
|
68
|
+
- **Not ignored → REAL target.** The `.gitignore` is a genuine, trackable fix;
|
|
69
|
+
ship it via `/mass-update --file <datadir>/.gitignore --content '…'`
|
|
70
|
+
(one mass-update call per distinct `<datadir>`, since the DEST differs).
|
|
71
|
+
- **Already ignored → not a target.** The whole data dir is ignored at the repo
|
|
72
|
+
root, so `data/` is *already* protected and git refuses to track a
|
|
73
|
+
`<datadir>/.gitignore` there — a PR would land nothing. The doctor warning is a
|
|
74
|
+
false positive. Don't PR it; if it's worth flagging, `/communicate` an issue to
|
|
75
|
+
that repo (e.g. guildmaster, where `.gitignore` already ignores `.agex/`).
|
|
76
|
+
|
|
77
|
+
Note: `devex`'s own self-heal (`devex overview`/`devex init`) bootstraps `.devex/`
|
|
78
|
+
only — it will **not** restore an `.agex/.gitignore`, so for `.agex` repos the
|
|
79
|
+
file must be supplied verbatim.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rollout-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -105,6 +105,12 @@ cp -R ../colleague/.claude/skills/ask-colleague .claude/skills/
|
|
|
105
105
|
Once guildmaster re-broadcasts `ask-colleague`, switch the upstream column back
|
|
106
106
|
to `../guildmaster/.claude/skills/ask-colleague/` and re-sync from there.
|
|
107
107
|
|
|
108
|
+
The procedure above re-syncs *this* repo's own copy. Propagating an upstream
|
|
109
|
+
`ask-colleague` change **out to the other downstream consumers** — and the
|
|
110
|
+
ledger of who those consumers are — is owned by the
|
|
111
|
+
[`ask-colleague-revendor`](../recipes/ask-colleague-revendor/README.md) rollout
|
|
112
|
+
recipe (rollout-cli#3).
|
|
113
|
+
|
|
108
114
|
## Tooling prerequisites
|
|
109
115
|
|
|
110
116
|
- **`devex`** (>=0.21) on PATH — `cicd` delegates the PR lifecycle to `devex pr`.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ask-colleague-revendor
|
|
2
|
+
|
|
3
|
+
Propagate an upstream `ask-colleague` wrapper change from its origin repo
|
|
4
|
+
(**colleague**) to every downstream repo that vendors the skill, and add the
|
|
5
|
+
skill fresh where it is absent.
|
|
6
|
+
|
|
7
|
+
This recipe is also rollout-cli's answer to
|
|
8
|
+
[agentculture/rollout-cli#3](https://github.com/agentculture/rollout-cli/issues/3):
|
|
9
|
+
the **reconciliation procedure** and the **downstream-consumer ledger** live
|
|
10
|
+
here, in the propagation tool, rather than as an outbound ledger forced into the
|
|
11
|
+
origin repo (colleague).
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
`ask-colleague` is first-party to **colleague**; downstream repos vendor it
|
|
16
|
+
`cite, don't import`. The wrapper (`scripts/ask-colleague.sh`) is meant to be
|
|
17
|
+
**verbatim** across consumers; the `SKILL.md` is **not** — each consumer
|
|
18
|
+
substitutes its own nick into one provenance clause
|
|
19
|
+
(`which <nick> vendors *from* guildmaster`) and may lag the origin on prose.
|
|
20
|
+
|
|
21
|
+
So the recipe is deliberately split:
|
|
22
|
+
|
|
23
|
+
- **Existing install** → overwrite `scripts/ask-colleague.sh` **only**. This
|
|
24
|
+
matches how upstream changes land (colleague#183 was wrapper-only) and never
|
|
25
|
+
clobbers a consumer's `SKILL.md` provenance token or local divergence.
|
|
26
|
+
- **Skill absent** → lay down the full skill (`wrapper` + `SKILL.md` +
|
|
27
|
+
`prompts/`), substituting the target repo's nick into the `SKILL.md`
|
|
28
|
+
provenance line via the `__REPO_NICK__` token in `files/SKILL.md.tmpl`.
|
|
29
|
+
|
|
30
|
+
`check.sh` classifies a repo `done` (wrapper already byte-matches the vendored
|
|
31
|
+
snapshot), `ok` (wrapper missing or stale → apply), or `needs-attention` (no
|
|
32
|
+
version-bumpable `pyproject.toml`).
|
|
33
|
+
|
|
34
|
+
## Downstream-consumer ledger
|
|
35
|
+
|
|
36
|
+
Origin: **colleague** (`agentculture/colleague`,
|
|
37
|
+
`.claude/skills/ask-colleague/`). Known consumers that re-vendor the wrapper:
|
|
38
|
+
|
|
39
|
+
| Consumer | Repo | Vendoring | Notes |
|
|
40
|
+
|----------|------|-----------|-------|
|
|
41
|
+
| culture | `agentculture/culture` | re-vendor wrapper | merged at pre-#183; carries `which culture …` token |
|
|
42
|
+
| agentirc | `agentculture/agentirc` | re-vendor wrapper | merged at pre-#183; no `culture.yaml` (identity differs) |
|
|
43
|
+
| steward | `agentculture/steward` | re-vendor wrapper | merged at pre-#183; carries `which steward …` token |
|
|
44
|
+
| rollout-cli | `agentculture/rollout-cli` | re-vendor wrapper | this repo ("self"); carries `which rollout-cli …` token |
|
|
45
|
+
| dgx-spark-cli | `agentculture/dgx-spark-cli` | fresh add | skill was absent |
|
|
46
|
+
| reterminal-cli | `agentculture/reterminal-cli` | re-vendor wrapper | merged at pre-#183 |
|
|
47
|
+
|
|
48
|
+
Keep this table current when a consumer is added or dropped.
|
|
49
|
+
|
|
50
|
+
## Reconciliation procedure (when colleague lands an upstream change)
|
|
51
|
+
|
|
52
|
+
1. **Refresh the snapshot** from the origin checkout so the recipe carries the
|
|
53
|
+
new wrapper (and, for fresh-adds, current `SKILL.md`/prompts):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
O=../colleague/.claude/skills/ask-colleague
|
|
57
|
+
cp "$O/scripts/ask-colleague.sh" recipes/ask-colleague-revendor/files/scripts/
|
|
58
|
+
cp "$O"/prompts/*.md recipes/ask-colleague-revendor/files/prompts/
|
|
59
|
+
cp "$O/SKILL.md" recipes/ask-colleague-revendor/files/SKILL.md.tmpl
|
|
60
|
+
# re-apply the token: in SKILL.md.tmpl, `which colleague vendors *from*`
|
|
61
|
+
# → `which __REPO_NICK__ vendors *from*`
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. **Dry-run** the classification across consumers:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv run rollout-cli plan ask-colleague-revendor \
|
|
68
|
+
--repos culture,agentirc,steward,dgx-spark-cli,reterminal-cli --base <clones-dir>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. **Apply** — branch, patch wrapper, bump version, open one PR per repo
|
|
72
|
+
(operator squash-merges; rollout never merges):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv run rollout-cli apply ask-colleague-revendor \
|
|
76
|
+
--repos culture,agentirc,steward,dgx-spark-cli,reterminal-cli --base <clones-dir> --yes
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Run against **fresh clones** (not live agent checkouts) so a running agent is
|
|
80
|
+
never disrupted; rollout requires each target clean and on `main`.
|
|
81
|
+
|
|
82
|
+
4. Update the consumer ledger above if the set changed.
|
|
83
|
+
|
|
84
|
+
Refs: colleague#183 (wrapper fixes), colleague#180/#181, rollout-cli#3.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# apply.sh — re-vendor the ask-colleague skill wrapper from colleague (#183).
|
|
3
|
+
# Usage: apply.sh [REPO_DIR] (default: current directory)
|
|
4
|
+
#
|
|
5
|
+
# Idempotent. Two cases, one code path:
|
|
6
|
+
# * Existing install — overwrite scripts/ask-colleague.sh ONLY. This mirrors
|
|
7
|
+
# colleague#183, which was wrapper-only; a downstream's SKILL.md carries a
|
|
8
|
+
# repo-specific provenance token (and may intentionally diverge), so it is
|
|
9
|
+
# never clobbered here.
|
|
10
|
+
# * Skill absent — lay down the full skill (wrapper + SKILL.md + prompts),
|
|
11
|
+
# substituting the target repo's nick into SKILL.md's provenance line.
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
REPO="${1:-.}"
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
SRC="${SCRIPT_DIR}/files"
|
|
17
|
+
DEST="${REPO}/.claude/skills/ask-colleague"
|
|
18
|
+
|
|
19
|
+
mkdir -p "${DEST}/scripts" "${DEST}/prompts"
|
|
20
|
+
|
|
21
|
+
# (1) Wrapper — always overwrite verbatim (the #183 payload).
|
|
22
|
+
cp "${SRC}/scripts/ask-colleague.sh" "${DEST}/scripts/ask-colleague.sh"
|
|
23
|
+
chmod 0755 "${DEST}/scripts/ask-colleague.sh"
|
|
24
|
+
|
|
25
|
+
# (2) SKILL.md — only when absent (fresh add); preserve an existing copy.
|
|
26
|
+
if [ ! -f "${DEST}/SKILL.md" ]; then
|
|
27
|
+
nick="$(basename "$(cd "${REPO}" && pwd)")"
|
|
28
|
+
# Escape sed replacement metacharacters (& \ and the / delimiter) so a repo
|
|
29
|
+
# basename containing them can't corrupt or break the substitution.
|
|
30
|
+
nick_esc="$(printf '%s' "${nick}" | sed -e 's/[&/\\]/\\&/g')"
|
|
31
|
+
sed "s/__REPO_NICK__/${nick_esc}/g" "${SRC}/SKILL.md.tmpl" > "${DEST}/SKILL.md"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# (3) Prompts — only when absent (fresh add); never disturb existing ones.
|
|
35
|
+
for p in explore review write; do
|
|
36
|
+
if [ ! -f "${DEST}/prompts/${p}.md" ]; then
|
|
37
|
+
cp "${SRC}/prompts/${p}.md" "${DEST}/prompts/${p}.md"
|
|
38
|
+
fi
|
|
39
|
+
done
|