rollout-cli 0.2.1__tar.gz → 0.3.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.2.1 → rollout_cli-0.3.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +105 -46
- rollout_cli-0.3.1/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +302 -0
- rollout_cli-0.3.1/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +390 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.gitignore +9 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.markdownlint-cli2.yaml +9 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/CHANGELOG.md +66 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/PKG-INFO +44 -1
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/README.md +43 -0
- rollout_cli-0.3.1/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +109 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/docs/skill-sources.md +6 -0
- rollout_cli-0.3.1/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +63 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/pyproject.toml +2 -2
- rollout_cli-0.3.1/recipes/apache-relicense/apply.sh +25 -0
- rollout_cli-0.3.1/recipes/apache-relicense/check.sh +32 -0
- rollout_cli-0.3.1/recipes/apache-relicense/files/LICENSE +201 -0
- rollout_cli-0.3.1/recipes/apache-relicense/manifest.toml +11 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/README.md +84 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/apply.sh +39 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/check.sh +31 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +196 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/files/prompts/explore.md +25 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/files/prompts/review.md +36 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/files/prompts/write.md +15 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/files/scripts/ask-colleague.sh +568 -0
- rollout_cli-0.3.1/recipes/ask-colleague-revendor/manifest.toml +25 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/__init__.py +4 -0
- rollout_cli-0.3.1/rollout/cli/_commands/apply.py +88 -0
- rollout_cli-0.3.1/rollout/cli/_commands/plan.py +70 -0
- rollout_cli-0.3.1/rollout/core/__init__.py +1 -0
- rollout_cli-0.3.1/rollout/core/bump.py +118 -0
- rollout_cli-0.3.1/rollout/core/devex_pr.py +133 -0
- rollout_cli-0.3.1/rollout/core/gitops.py +93 -0
- rollout_cli-0.3.1/rollout/core/orchestrate.py +114 -0
- rollout_cli-0.3.1/rollout/core/preflight.py +65 -0
- rollout_cli-0.3.1/rollout/core/recipe.py +84 -0
- rollout_cli-0.3.1/rollout/core/report.py +86 -0
- rollout_cli-0.3.1/rollout/core/targets.py +139 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/explain/catalog.py +64 -0
- rollout_cli-0.3.1/tests/test_apache_relicense.py +228 -0
- rollout_cli-0.3.1/tests/test_apply_cmd.py +260 -0
- rollout_cli-0.3.1/tests/test_bump.py +209 -0
- rollout_cli-0.3.1/tests/test_cli_wiring.py +49 -0
- rollout_cli-0.3.1/tests/test_devex_pr.py +242 -0
- rollout_cli-0.3.1/tests/test_e2e_apache_relicense.py +278 -0
- rollout_cli-0.3.1/tests/test_explain_catalog.py +20 -0
- rollout_cli-0.3.1/tests/test_gitops.py +186 -0
- rollout_cli-0.3.1/tests/test_orchestrate.py +363 -0
- rollout_cli-0.3.1/tests/test_plan_cmd.py +233 -0
- rollout_cli-0.3.1/tests/test_preflight.py +165 -0
- rollout_cli-0.3.1/tests/test_recipe.py +140 -0
- rollout_cli-0.3.1/tests/test_report.py +182 -0
- rollout_cli-0.3.1/tests/test_targets.py +114 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/uv.lock +1 -1
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/agent-config/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/cicd/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/think/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/think/scripts/think.sh +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/version-bump/SKILL.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.claude/skills.local.yaml.example +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.flake8 +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.github/workflows/publish.yml +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/.github/workflows/tests.yml +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/CLAUDE.md +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/LICENSE +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/culture.yaml +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/__init__.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/__main__.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/__init__.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/cli.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/doctor.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/explain.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/learn.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/overview.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_commands/whoami.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_errors.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/cli/_output.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/rollout/explain/__init__.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/sonar-project.properties +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/tests/__init__.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/tests/test_cli.py +0 -0
- {rollout_cli-0.2.1 → rollout_cli-0.3.1}/tests/test_cli_introspection.py +0 -0
{rollout_cli-0.2.1 → rollout_cli-0.3.1}/.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
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "rollout-cli-ships-rollout-define-an-org-wide-chang",
|
|
3
|
+
"title": "rollout-cli ships 'rollout': define an org-wide change once as a deterministic recipe, and it propagates across every target agent repo \u2014 branch, apply via scripts (not an agent hand-editing files), bump version, commit, push, and open one PR per repo with CI running, ready for a human squash-merge. The first recipe, apache-relicense, carries the exact MIT->Apache-2.0 change shape from rollout-cli's own #1 across the mesh.",
|
|
4
|
+
"schema_version": 1,
|
|
5
|
+
"status": "exported",
|
|
6
|
+
"created": "2026-06-06T14:17:27Z",
|
|
7
|
+
"updated": "2026-06-06T14:30:37Z",
|
|
8
|
+
"claims": [
|
|
9
|
+
{
|
|
10
|
+
"id": "c1",
|
|
11
|
+
"kind": "announcement",
|
|
12
|
+
"text": "rollout-cli ships 'rollout': define an org-wide change once as a deterministic recipe, and it propagates across every target agent repo \u2014 branch, apply via scripts (not an agent hand-editing files), bump version, commit, push, and open one PR per repo with CI running, ready for a human squash-merge. The first recipe, apache-relicense, carries the exact MIT->Apache-2.0 change shape from rollout-cli's own #1 across the mesh.",
|
|
13
|
+
"origin": "user",
|
|
14
|
+
"status": "confirmed",
|
|
15
|
+
"honesty_conditions": [
|
|
16
|
+
{
|
|
17
|
+
"id": "h7",
|
|
18
|
+
"text": "The first recipe, apache-relicense, run across the confirmed target set, produces one PR per repo whose diff matches d27519f's shape, with CI running and no repo merged automatically.",
|
|
19
|
+
"status": "confirmed"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"hard_questions": [],
|
|
23
|
+
"links": []
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "c2",
|
|
27
|
+
"kind": "audience",
|
|
28
|
+
"text": "The org maintainer (Ori) who decides what change to roll out and does the squash-merges, plus the rollout-cli agent that authors/validates the recipe once and drives the per-repo loop.",
|
|
29
|
+
"origin": "llm",
|
|
30
|
+
"status": "confirmed",
|
|
31
|
+
"honesty_conditions": [
|
|
32
|
+
{
|
|
33
|
+
"id": "h9",
|
|
34
|
+
"text": "The two roles are real and distinct: the maintainer makes the go/no-go and does the squash-merges; the agent authors the recipe once and runs the deterministic loop \u2014 neither hand-edits files per repo.",
|
|
35
|
+
"status": "confirmed"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"hard_questions": [],
|
|
39
|
+
"links": []
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "c3",
|
|
43
|
+
"kind": "after_state",
|
|
44
|
+
"text": "One declarative recipe is applied across an explicit set of target repos, producing one branch + one PR per repo, each with the same reviewable diff shape, CI running, awaiting human squash-merge. Deterministic scripts make the edits; the agent never hand-edits files across repos.",
|
|
45
|
+
"origin": "llm",
|
|
46
|
+
"status": "confirmed",
|
|
47
|
+
"honesty_conditions": [
|
|
48
|
+
{
|
|
49
|
+
"id": "h10",
|
|
50
|
+
"text": "After a run, each clean target has exactly one new branch and one open PR with the same diff shape, and no target's working files were edited by an agent.",
|
|
51
|
+
"status": "confirmed"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"hard_questions": [],
|
|
55
|
+
"links": []
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "c4",
|
|
59
|
+
"kind": "before_state",
|
|
60
|
+
"text": "An org-wide change (e.g. relicensing) means either manually editing each of ~40 repos or having an agent open each repo and hand-edit files \u2014 slow, inconsistent, unreviewable, and error-prone.",
|
|
61
|
+
"origin": "llm",
|
|
62
|
+
"status": "confirmed",
|
|
63
|
+
"honesty_conditions": [
|
|
64
|
+
{
|
|
65
|
+
"id": "h11",
|
|
66
|
+
"text": "Today there is no single command that applies one change across the org; doing it means N manual or agent-driven edits.",
|
|
67
|
+
"status": "confirmed"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"hard_questions": [],
|
|
71
|
+
"links": []
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "c5",
|
|
75
|
+
"kind": "why_it_matters",
|
|
76
|
+
"text": "A deterministic recipe is reviewable once, repeatable, idempotent, and auditable; the agent orchestrates rather than touching every file, so a 40-repo change is as trustworthy as a 1-repo change.",
|
|
77
|
+
"origin": "llm",
|
|
78
|
+
"status": "confirmed",
|
|
79
|
+
"honesty_conditions": [
|
|
80
|
+
{
|
|
81
|
+
"id": "h12",
|
|
82
|
+
"text": "Because the change is a reviewed-once deterministic recipe, a 40-repo run produces diffs a human can audit identically to a 1-repo run.",
|
|
83
|
+
"status": "confirmed"
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"hard_questions": [],
|
|
87
|
+
"links": []
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "c6",
|
|
91
|
+
"kind": "boundary",
|
|
92
|
+
"text": "rollout-cli does NOT merge (human squash-merges), does not invent semantic refactors (deterministic recipes/refactor-cli actions only), is not a CI system (it relies on each repo's existing CI), and does not own the target repos' release policy beyond what the recipe declares.",
|
|
93
|
+
"origin": "llm",
|
|
94
|
+
"status": "confirmed",
|
|
95
|
+
"honesty_conditions": [
|
|
96
|
+
{
|
|
97
|
+
"id": "h13",
|
|
98
|
+
"text": "rollout never merges a PR and never performs an edit not declared by a recipe; CI is each target repo's own.",
|
|
99
|
+
"status": "confirmed"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"hard_questions": [],
|
|
103
|
+
"links": []
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "c7",
|
|
107
|
+
"kind": "success_signal",
|
|
108
|
+
"text": "Running 'rollout apply apache-relicense --targets <set>' yields one branch+PR per target whose diff matches d27519f's shape (LICENSE, pyproject license+classifier, README license line, CHANGELOG entry, version bump), with CI triggered, and a single summary table of PR URLs + statuses.",
|
|
109
|
+
"origin": "llm",
|
|
110
|
+
"status": "confirmed",
|
|
111
|
+
"honesty_conditions": [
|
|
112
|
+
{
|
|
113
|
+
"id": "h14",
|
|
114
|
+
"text": "The apache-relicense run emits a summary table of one PR URL per target, and each PR's diff equals d27519f's shape.",
|
|
115
|
+
"status": "confirmed"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"hard_questions": [],
|
|
119
|
+
"links": []
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "c8",
|
|
123
|
+
"kind": "requirement",
|
|
124
|
+
"text": "A 'recipe' is the unit of change: a self-contained, version-controlled directory (deterministic apply script + manifest declaring touched files, version-bump level, and CHANGELOG text). Authored/reviewed ONCE; the same recipe runs unchanged against every target.",
|
|
125
|
+
"origin": "llm",
|
|
126
|
+
"status": "confirmed",
|
|
127
|
+
"honesty_conditions": [
|
|
128
|
+
{
|
|
129
|
+
"id": "h1",
|
|
130
|
+
"text": "A recipe applies with zero recipe-specific args beyond the target path; the apache-relicense recipe reproduces d27519f's diff shape exactly on a clean MIT repo.",
|
|
131
|
+
"status": "confirmed"
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"hard_questions": [],
|
|
135
|
+
"links": []
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "c9",
|
|
139
|
+
"kind": "requirement",
|
|
140
|
+
"text": "The per-repo loop is pure orchestration: locate/refresh repo -> create branch -> run recipe's apply script -> bump version + prepend CHANGELOG -> commit -> push -> open PR via devex. No LLM file edits in the loop.",
|
|
141
|
+
"origin": "llm",
|
|
142
|
+
"status": "confirmed",
|
|
143
|
+
"honesty_conditions": [
|
|
144
|
+
{
|
|
145
|
+
"id": "h2",
|
|
146
|
+
"text": "The apply loop contains no agent/LLM file-editing step \u2014 every file mutation originates in the recipe script or the deterministic version/CHANGELOG bumper.",
|
|
147
|
+
"status": "confirmed"
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"hard_questions": [],
|
|
151
|
+
"links": []
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "c10",
|
|
155
|
+
"kind": "requirement",
|
|
156
|
+
"text": "Targets are explicit and declared (a repo-list / filter over the org), never implicit. The set is shown and confirmed before any push.",
|
|
157
|
+
"origin": "llm",
|
|
158
|
+
"status": "confirmed",
|
|
159
|
+
"honesty_conditions": [
|
|
160
|
+
{
|
|
161
|
+
"id": "h3",
|
|
162
|
+
"text": "No branch/push/PR happens until the resolved target list has been printed and confirmed by the maintainer.",
|
|
163
|
+
"status": "confirmed"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"hard_questions": [],
|
|
167
|
+
"links": []
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"id": "c11",
|
|
171
|
+
"kind": "requirement",
|
|
172
|
+
"text": "Two-phase execution: 'rollout plan' is a dry-run that produces per-repo diffs locally and a preflight report (applies cleanly? already done? conflicts?) with zero side effects; 'rollout apply' performs branch/commit/push/PR only on repos the plan marked clean.",
|
|
173
|
+
"origin": "llm",
|
|
174
|
+
"status": "confirmed",
|
|
175
|
+
"honesty_conditions": [
|
|
176
|
+
{
|
|
177
|
+
"id": "h4",
|
|
178
|
+
"text": "'rollout plan' creates no branches and writes to no remote; 'rollout apply' only acts on repos the plan marked clean.",
|
|
179
|
+
"status": "confirmed"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"hard_questions": [],
|
|
183
|
+
"links": []
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"id": "c12",
|
|
187
|
+
"kind": "requirement",
|
|
188
|
+
"text": "Idempotent + resumable: a recipe re-run on an already-changed repo is a no-op (detects done-state, skips); a partial run can be resumed; each repo's outcome is independent (one failure does not abort the batch).",
|
|
189
|
+
"origin": "llm",
|
|
190
|
+
"status": "confirmed",
|
|
191
|
+
"honesty_conditions": [
|
|
192
|
+
{
|
|
193
|
+
"id": "h5",
|
|
194
|
+
"text": "Re-running apply on a repo whose change already landed makes no new commit and opens no duplicate PR (done-state detected).",
|
|
195
|
+
"status": "confirmed"
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"hard_questions": [],
|
|
199
|
+
"links": []
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "c13",
|
|
203
|
+
"kind": "requirement",
|
|
204
|
+
"text": "Recipes that don't apply cleanly to a repo are surfaced as a per-repo 'needs attention' item (skipped, reported), NOT silently force-edited \u2014 that is the one place a human/agent steps in.",
|
|
205
|
+
"origin": "llm",
|
|
206
|
+
"status": "confirmed",
|
|
207
|
+
"honesty_conditions": [
|
|
208
|
+
{
|
|
209
|
+
"id": "h6",
|
|
210
|
+
"text": "A repo whose recipe preconditions fail is reported as needs-attention and skipped, never force-applied.",
|
|
211
|
+
"status": "confirmed"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"hard_questions": [],
|
|
215
|
+
"links": []
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "c14",
|
|
219
|
+
"kind": "decision",
|
|
220
|
+
"text": "Layering: refactor-cli owns the in-repo deterministic edit primitives (the apply script calls them); rollout-cli owns cross-repo orchestration (targets, branch, PR via devex, reporting). devex provides the PR lane; agtag/communicate the issue I/O.",
|
|
221
|
+
"origin": "llm",
|
|
222
|
+
"status": "confirmed",
|
|
223
|
+
"honesty_conditions": [],
|
|
224
|
+
"hard_questions": [],
|
|
225
|
+
"links": []
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"id": "c15",
|
|
229
|
+
"kind": "decision",
|
|
230
|
+
"text": "Local-checkout model: each target repo lives as a sibling checkout under /home/spark/git; rollout operates on these working copies and leaves each on its new branch (matching the user's 'each local repo will be on that new branch').",
|
|
231
|
+
"origin": "llm",
|
|
232
|
+
"status": "confirmed",
|
|
233
|
+
"honesty_conditions": [],
|
|
234
|
+
"hard_questions": [],
|
|
235
|
+
"links": []
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"id": "c16",
|
|
239
|
+
"kind": "assumption",
|
|
240
|
+
"text": "Every target repo follows the shared agent baseline (pyproject + CHANGELOG + version-check CI + devex PR lane), so one recipe's file-shape assumptions hold across the set.",
|
|
241
|
+
"origin": "llm",
|
|
242
|
+
"status": "confirmed",
|
|
243
|
+
"honesty_conditions": [],
|
|
244
|
+
"hard_questions": [],
|
|
245
|
+
"links": []
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"id": "c17",
|
|
249
|
+
"kind": "requirement",
|
|
250
|
+
"text": "Branching is always cut from a freshly-fetched default branch (origin/main); a repo with a dirty working tree or unpushed local commits on the wrong branch is aborted and reported, never branched over or clobbered.",
|
|
251
|
+
"origin": "llm",
|
|
252
|
+
"status": "confirmed",
|
|
253
|
+
"honesty_conditions": [
|
|
254
|
+
{
|
|
255
|
+
"id": "h8",
|
|
256
|
+
"text": "On a repo with a dirty tree or local-only commits, 'rollout apply' aborts that one repo with a clear report and makes no branch/commit/push; clean repos in the same batch still proceed.",
|
|
257
|
+
"status": "confirmed"
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
"hard_questions": [],
|
|
261
|
+
"links": []
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"id": "c18",
|
|
265
|
+
"kind": "decision",
|
|
266
|
+
"text": "Target resolution is a CLI input: an org/user plus a selector (all | explicit repo list), e.g. 'rollout apply <recipe> --org agentculture --all' or '--repos a,b,c'. agentculture-owned (excluding upstream forks) is the documented default. The resolved set is always printed for confirmation before any apply.",
|
|
267
|
+
"origin": "llm",
|
|
268
|
+
"status": "confirmed",
|
|
269
|
+
"honesty_conditions": [],
|
|
270
|
+
"hard_questions": [],
|
|
271
|
+
"links": []
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"id": "c19",
|
|
275
|
+
"kind": "decision",
|
|
276
|
+
"text": "A run stops at 'PR opened' and prints a summary table (repo, PR URL, status); the maintainer watches CI and squash-merges. A poll-CI summary mode is a deferred follow-up, not v1.",
|
|
277
|
+
"origin": "llm",
|
|
278
|
+
"status": "confirmed",
|
|
279
|
+
"honesty_conditions": [],
|
|
280
|
+
"hard_questions": [],
|
|
281
|
+
"links": []
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"id": "c20",
|
|
285
|
+
"kind": "decision",
|
|
286
|
+
"text": "The needs-attention seam delegates genuine agent work to the ask-colleague skill (a different backend, isolated git worktree, read-only by default) \u2014 the orchestrator never hand-edits files in place, preserving 'scripts, not an agent editing files all over'.",
|
|
287
|
+
"origin": "llm",
|
|
288
|
+
"status": "confirmed",
|
|
289
|
+
"honesty_conditions": [],
|
|
290
|
+
"hard_questions": [],
|
|
291
|
+
"links": []
|
|
292
|
+
}
|
|
293
|
+
],
|
|
294
|
+
"open_vagueness": [
|
|
295
|
+
{
|
|
296
|
+
"id": "v1",
|
|
297
|
+
"text": "How a recipe's preflight detects target repos that drift from the shared baseline (missing CHANGELOG, different pyproject license-field format) and routes them to needs-attention.",
|
|
298
|
+
"kind": "unknown_nonblocking",
|
|
299
|
+
"claim_id": null
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|