rollout-cli 0.6.0__tar.gz → 0.7.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.6.0 → rollout_cli-0.7.2}/.claude/skills/ask-colleague/SKILL.md +6 -5
- {rollout_cli-0.6.0/recipes/ask-colleague-revendor/files → rollout_cli-0.7.2/.claude/skills/ask-colleague}/prompts/explore.md +4 -0
- {rollout_cli-0.6.0/recipes/ask-colleague-revendor/files → rollout_cli-0.7.2/.claude/skills/ask-colleague}/prompts/review.md +2 -7
- {rollout_cli-0.6.0/recipes/ask-colleague-revendor/files → rollout_cli-0.7.2/.claude/skills/ask-colleague}/scripts/ask-colleague.sh +218 -27
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/assign-to-workforce/SKILL.md +100 -17
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +92 -26
- rollout_cli-0.7.2/.claude/skills/challenge/SKILL.md +290 -0
- rollout_cli-0.7.2/.claude/skills/deviate/SKILL.md +176 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/recall/SKILL.md +13 -9
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/recall/scripts/recall.sh +45 -22
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/remember/SKILL.md +8 -5
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/remember/scripts/remember.sh +50 -22
- rollout_cli-0.7.2/.claude/skills/scope/SKILL.md +167 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/spec-to-plan/SKILL.md +89 -16
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +1 -1
- rollout_cli-0.7.2/.claude/skills/summarize-delivery/SKILL.md +440 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/think/SKILL.md +98 -12
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/think/scripts/think.sh +1 -1
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/CHANGELOG.md +34 -1
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/CLAUDE.md +28 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/PKG-INFO +1 -1
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/docs/skill-sources.md +6 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/pyproject.toml +1 -1
- rollout_cli-0.7.2/recipes/eidetic-memory/README.md +161 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/apply.sh +118 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/check.sh +46 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/files/CLAUDE.snippet.tmpl +25 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/files/SKILL.md.recall.tmpl +185 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/files/SKILL.md.remember.tmpl +121 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/files/scripts/recall.sh +164 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/files/scripts/remember.sh +166 -0
- rollout_cli-0.7.2/recipes/eidetic-memory/manifest.toml +51 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/devex_pr.py +18 -8
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_devex_pr.py +23 -5
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/uv.lock +1 -1
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/agent-config/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/cicd/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/mass-update/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/mass-update/scripts/mass-update.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/run-tests/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/version-bump/SKILL.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.claude/skills.local.yaml.example +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.devague/frames/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.devague/frames/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.devague/plans/mass-update-now-clones-each-target-repo-into-a-git.json +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.devague/plans/rollout-cli-ships-rollout-define-an-org-wide-chang.json +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.flake8 +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.github/workflows/publish.yml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.github/workflows/tests.yml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.gitignore +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/.markdownlint-cli2.yaml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/AGENTS.colleague.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/LICENSE +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/README.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/culture.yaml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/docs/plans/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/docs/plans/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/docs/specs/2026-06-06-rollout-cli-ships-rollout-define-an-org-wide-chang.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/docs/specs/2026-06-12-mass-update-now-clones-each-target-repo-into-a-git.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/apache-relicense/apply.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/apache-relicense/check.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/apache-relicense/files/LICENSE +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/apache-relicense/manifest.toml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/ask-colleague-revendor/README.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/ask-colleague-revendor/apply.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/ask-colleague-revendor/check.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/ask-colleague-revendor/files/SKILL.md.tmpl +0 -0
- {rollout_cli-0.6.0/.claude/skills/ask-colleague → rollout_cli-0.7.2/recipes/ask-colleague-revendor/files}/prompts/explore.md +0 -0
- {rollout_cli-0.6.0/.claude/skills/ask-colleague → rollout_cli-0.7.2/recipes/ask-colleague-revendor/files}/prompts/review.md +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/ask-colleague-revendor/files/prompts/write.md +0 -0
- {rollout_cli-0.6.0/.claude/skills/ask-colleague → rollout_cli-0.7.2/recipes/ask-colleague-revendor/files}/scripts/ask-colleague.sh +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/recipes/ask-colleague-revendor/manifest.toml +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/__init__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/__main__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/__init__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/__init__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/apply.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/cli.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/doctor.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/explain.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/learn.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/overview.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/plan.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_commands/whoami.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_errors.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/cli/_output.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/__init__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/bump.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/gitops.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/orchestrate.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/preflight.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/recipe.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/report.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/core/targets.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/explain/__init__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/rollout/explain/catalog.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/sonar-project.properties +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/__init__.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_apache_relicense.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_apply_cmd.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_bump.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_cli.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_cli_introspection.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_cli_wiring.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_e2e_apache_relicense.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_explain_catalog.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_gitops.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_mass_update.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_orchestrate.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_plan_cmd.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_preflight.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_recipe.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_report.py +0 -0
- {rollout_cli-0.6.0 → rollout_cli-0.7.2}/tests/test_targets.py +0 -0
|
@@ -101,6 +101,7 @@ else an install hint.
|
|
|
101
101
|
| `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. |
|
|
102
102
|
| `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. |
|
|
103
103
|
| `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`. |
|
|
104
|
+
| `plan "<task>"` | Colleague **PLANS** a complex task: it proposes a spec, then a split plan, then fans the waves out to a subagent-colleague workforce (`colleague plan run … --yes`). The *inverse of `/think`* — same arc, but colleague is the planning mind, not Claude. Needs a live backend. | Runs `colleague plan` in `--repo` (not a throwaway worktree): the workforce stage spawns isolated subagent worktrees and can land branches — treat like `write --apply` (gets a user nod). |
|
|
104
105
|
| `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. |
|
|
105
106
|
| `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. |
|
|
106
107
|
|
|
@@ -113,7 +114,7 @@ else an install hint.
|
|
|
113
114
|
| `--engine NAME` | Backend plugin (default: `$COLLEAGUE_ENGINE` or `vllm-openai`). |
|
|
114
115
|
| `--model NAME` | Model (default: `$COLLEAGUE_MODEL` or `sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP`). |
|
|
115
116
|
| `--base-url URL` | OpenAI base URL (default: `$COLLEAGUE_BASE_URL` or `http://localhost:8001/v1`). |
|
|
116
|
-
| `--max-steps N` | Loop step budget (default: 20). |
|
|
117
|
+
| `--max-steps N` | Loop step budget (default: 20). `explore`/`review` select colleague's own native **`explore`**/**`review`** mode profile (`colleague/profiles.py`, applied via `colleague work --mode`) instead of a wrapper-side override — today that profile defaults to 30, since read-only mapping fans out across more files. An explicit `--max-steps N` always overrides the profile's default, in either direction. If the resolved `colleague` predates `--mode` (a stale install on `PATH`), the wrapper falls back to the old caller-side `--max-steps 30` + reserved-steps behavior so it keeps working. |
|
|
117
118
|
| `--apply` | (`write`) apply the change in place (work branch) instead of previewing. |
|
|
118
119
|
| `--allow-dirty` | (`write`) allow running on a dirty tree (only matters with `--apply` / `--pr`). |
|
|
119
120
|
| `--pr` | (`write`) push + open a PR instead of a local work branch (implies `--apply`). |
|
|
@@ -207,7 +208,7 @@ stop requests take effect on the next iteration rather than interrupting mid-ste
|
|
|
207
208
|
|
|
208
209
|
## Provenance
|
|
209
210
|
|
|
210
|
-
This is a **first-party** colleague skill — colleague is its origin.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
This is a **first-party** colleague skill — colleague is its origin. See
|
|
212
|
+
`docs/skill-sources.md` for the consumer's per-repo skill ledger. The `cite,
|
|
213
|
+
don't import` policy holds: downstream repos copy it, they don't symlink or
|
|
214
|
+
depend on it.
|
|
@@ -18,6 +18,10 @@ Rules:
|
|
|
18
18
|
report (or you are within a few steps of the budget), STOP reading and call
|
|
19
19
|
`finish`. Err on the side of finishing early — a focused finding beats endless
|
|
20
20
|
reading.
|
|
21
|
+
- For a WIDE codebase map (many folders/modules), do NOT read every file in series
|
|
22
|
+
— that exhausts the step budget. Partition the surface by folder and delegate the
|
|
23
|
+
per-folder sub-surveys to the `subagents` tool (one child per folder/subtree, each
|
|
24
|
+
returning its findings), then synthesize their results into your report.
|
|
21
25
|
|
|
22
26
|
When you are done, call finish with a structured findings report:
|
|
23
27
|
1. What it is / how it works (with file:line references).
|
|
@@ -5,13 +5,8 @@ Focus the review on:
|
|
|
5
5
|
|
|
6
6
|
$ARGUMENTS
|
|
7
7
|
|
|
8
|
-
The
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
git diff $BASE...HEAD --stat
|
|
12
|
-
git diff $BASE...HEAD
|
|
13
|
-
|
|
14
|
-
then read the touched files for the context you need.
|
|
8
|
+
The diff for this change is ALREADY PROVIDED below the instructions (filtered +
|
|
9
|
+
capped). Read specific files only if you need more context.
|
|
15
10
|
|
|
16
11
|
Rules:
|
|
17
12
|
- READ-ONLY. Do NOT modify, create, or delete any file. Only read and run
|
|
@@ -43,11 +43,19 @@ PROMPTS_DIR="$SKILL_DIR/prompts"
|
|
|
43
43
|
# ── resolve the colleague CLI (installed, then local-dev fallback) ─────────
|
|
44
44
|
COLLEAGUE=()
|
|
45
45
|
|
|
46
|
+
_pyproject_is_colleague() {
|
|
47
|
+
local line
|
|
48
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
49
|
+
[[ "$line" == 'name = "colleague"'* ]] && return 0
|
|
50
|
+
done < "$1"
|
|
51
|
+
return 1
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
_colleague_via_uv() {
|
|
47
55
|
local dir="$1"
|
|
48
56
|
while [[ -n "$dir" ]] && [[ "$dir" != "/" ]]; do
|
|
49
57
|
if [[ -f "$dir/pyproject.toml" ]] \
|
|
50
|
-
&&
|
|
58
|
+
&& _pyproject_is_colleague "$dir/pyproject.toml"; then
|
|
51
59
|
command -v uv >/dev/null 2>&1 || return 1
|
|
52
60
|
COLLEAGUE=(uv run --project "$dir" colleague)
|
|
53
61
|
return 0
|
|
@@ -81,10 +89,11 @@ Usage:
|
|
|
81
89
|
ask-colleague explore "<question or area>" Read-only investigation -> findings (no side effects)
|
|
82
90
|
ask-colleague review "<what to focus on>" Diverse second-opinion on the committed diff (no side effects)
|
|
83
91
|
ask-colleague write "<task>" [--apply|--pr] Implement a change (preview by default; --apply lands it)
|
|
92
|
+
ask-colleague plan "<task>" [--no-workforce] Colleague PLANS a complex task (spec -> plan -> subagent workforce)
|
|
84
93
|
ask-colleague feedback <id|last> [--rating N] Grade a past drive (ROI loop); with --rating records, without shows
|
|
85
94
|
ask-colleague feedback list List every recorded drive by request + grade (find one by its request)
|
|
86
95
|
ask-colleague clean [--dry-run] Reap stale/corrupt colleague/* branches + orphaned .colleague/ artifacts (#162)
|
|
87
|
-
ask-colleague monitor <task-id>
|
|
96
|
+
ask-colleague monitor <task-id> Stream a running flight's live feed (--follow)
|
|
88
97
|
ask-colleague guide <task-id> "<msg>" Send mid-flight guidance to a running flight
|
|
89
98
|
ask-colleague stop <task-id> Ask a running flight to stop (cooperative)
|
|
90
99
|
|
|
@@ -94,9 +103,14 @@ Options:
|
|
|
94
103
|
--base BRANCH Base for `review` diff (default: main)
|
|
95
104
|
--engine NAME Backend plugin (default: $COLLEAGUE_ENGINE or vllm-openai)
|
|
96
105
|
--model NAME Model (default: $COLLEAGUE_MODEL or sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP)
|
|
106
|
+
--role NAME Typed subagent role (e.g. explorer, reviewer, writer)
|
|
97
107
|
--base-url URL OpenAI base URL (default: $COLLEAGUE_BASE_URL or http://localhost:8001/v1)
|
|
98
|
-
--max-steps N Loop step budget (default: 20
|
|
108
|
+
--max-steps N Loop step budget (default: 20; explore/review default from
|
|
109
|
+
colleague's own "explore"/"review" mode profile, today 30 —
|
|
110
|
+
an explicit --max-steps always overrides, lower or higher)
|
|
99
111
|
--timeout N Per-request timeout, seconds (default: $COLLEAGUE_TIMEOUT or 300)
|
|
112
|
+
--no-workforce (plan) deliver the spec+plan only, skip the workforce fan-out (#215)
|
|
113
|
+
--quick / --no-spec (plan) skip the spec stage, plan directly from the request (#199)
|
|
100
114
|
--apply (write) apply the change in place (drive branch) instead of previewing
|
|
101
115
|
--allow-dirty (write) allow running on a dirty tree (only with --apply/--pr)
|
|
102
116
|
--pr (write) push + open a PR instead of a local drive branch (implies --apply)
|
|
@@ -123,11 +137,11 @@ EOF
|
|
|
123
137
|
# ── parse the verb ──────────────────────────────────────────────────────────
|
|
124
138
|
VERB="${1:-}"
|
|
125
139
|
case "$VERB" in
|
|
126
|
-
explore | review | write | feedback | clean | monitor | guide | stop) shift ;;
|
|
140
|
+
explore | review | write | plan | feedback | clean | monitor | guide | stop) shift ;;
|
|
127
141
|
-h | --help) usage; exit 0 ;;
|
|
128
142
|
"") usage >&2; exit 1 ;; # missing arg -> user-input error (#161)
|
|
129
143
|
*)
|
|
130
|
-
echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean|monitor|guide|stop)" >&2
|
|
144
|
+
echo "error: unknown verb '$VERB' (expected explore|review|write|plan|feedback|clean|monitor|guide|stop)" >&2
|
|
131
145
|
echo "hint: run 'ask-colleague --help'" >&2
|
|
132
146
|
exit 1 # bad verb -> user-input error (#161)
|
|
133
147
|
;;
|
|
@@ -139,9 +153,7 @@ esac
|
|
|
139
153
|
# feedback/clean are thin pass-throughs to `colleague` plus the shared git
|
|
140
154
|
# work-tree guard, so they need only git; the drive verbs (explore/review/write)
|
|
141
155
|
# also render a prompt and parse colleague's --json result via python3, and the
|
|
142
|
-
# worktree-isolated paths additionally need mktemp.
|
|
143
|
-
# uv-fallback resolver, which degrades to the clear "colleague not found" message
|
|
144
|
-
# when absent, so it is not a hard requirement here.
|
|
156
|
+
# worktree-isolated paths additionally need mktemp.
|
|
145
157
|
require_tools() { # $@ = tool names this verb path needs
|
|
146
158
|
local missing=() t
|
|
147
159
|
for t in "$@"; do
|
|
@@ -172,6 +184,7 @@ ENGINE="${COLLEAGUE_ENGINE:-${CONVERTIBLE_ENGINE:-vllm-openai}}"
|
|
|
172
184
|
MODEL="${COLLEAGUE_MODEL:-${CONVERTIBLE_MODEL:-sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP}}"
|
|
173
185
|
BASE_URL="${COLLEAGUE_BASE_URL:-${CONVERTIBLE_BASE_URL:-http://localhost:8001/v1}}"
|
|
174
186
|
MAX_STEPS=20
|
|
187
|
+
MAX_STEPS_EXPLICIT=0
|
|
175
188
|
TIMEOUT="${COLLEAGUE_TIMEOUT:-${CONVERTIBLE_TIMEOUT:-300}}"
|
|
176
189
|
ALLOW_DIRTY=0
|
|
177
190
|
APPLY=0
|
|
@@ -183,6 +196,9 @@ NOTES=""
|
|
|
183
196
|
BY=""
|
|
184
197
|
ARG=""
|
|
185
198
|
JSON_OUT=0
|
|
199
|
+
ROLE=""
|
|
200
|
+
QUICK=0
|
|
201
|
+
NO_WORKFORCE=0
|
|
186
202
|
|
|
187
203
|
while [[ $# -gt 0 ]]; do
|
|
188
204
|
case "$1" in
|
|
@@ -190,14 +206,17 @@ while [[ $# -gt 0 ]]; do
|
|
|
190
206
|
--base) need_value "$#" "$1"; BASE="$2"; shift 2 ;;
|
|
191
207
|
--engine) need_value "$#" "$1"; ENGINE="$2"; shift 2 ;;
|
|
192
208
|
--model) need_value "$#" "$1"; MODEL="$2"; shift 2 ;;
|
|
209
|
+
--role) need_value "$#" "$1"; ROLE="$2"; shift 2 ;;
|
|
193
210
|
--base-url) need_value "$#" "$1"; BASE_URL="$2"; shift 2 ;;
|
|
194
|
-
--max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; shift 2 ;;
|
|
211
|
+
--max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; MAX_STEPS_EXPLICIT=1; shift 2 ;;
|
|
195
212
|
--timeout) need_value "$#" "$1"; TIMEOUT="$2"; shift 2 ;;
|
|
196
213
|
--apply) APPLY=1; shift ;;
|
|
197
214
|
--watch) WATCH=1; shift ;;
|
|
198
215
|
--allow-dirty) ALLOW_DIRTY=1; shift ;;
|
|
199
216
|
--pr) OPEN_PR=1; shift ;;
|
|
200
217
|
--dry-run) DRY_RUN=1; shift ;;
|
|
218
|
+
--quick | --no-spec) QUICK=1; shift ;; # plan: skip the spec stage (#199)
|
|
219
|
+
--no-workforce) NO_WORKFORCE=1; shift ;; # plan: deliver spec+plan only (#215)
|
|
201
220
|
--rating) need_value "$#" "$1"; RATING="$2"; shift 2 ;;
|
|
202
221
|
--notes) need_value "$#" "$1"; NOTES="$2"; shift 2 ;;
|
|
203
222
|
--by) need_value "$#" "$1"; BY="$2"; shift 2 ;;
|
|
@@ -210,13 +229,27 @@ while [[ $# -gt 0 ]]; do
|
|
|
210
229
|
esac
|
|
211
230
|
done
|
|
212
231
|
|
|
232
|
+
# Per-verb default: explore and review get a modest higher budget (30) for wider
|
|
233
|
+
# surveys and read-heavy diffs; write keeps the global default (20). Only apply
|
|
234
|
+
# when the user did NOT pass an explicit --max-steps.
|
|
235
|
+
#
|
|
236
|
+
# t4/spec R1: this number now ALSO matches colleague's own native "explore"/
|
|
237
|
+
# "review" mode profile (colleague/profiles.py), which is what actually drives
|
|
238
|
+
# the step budget at runtime once `--mode` is selected below — this wrapper-side
|
|
239
|
+
# value survives only so the "widen --max-steps" re-run hint (print_result)
|
|
240
|
+
# still names the right number; whether --max-steps is actually FORWARDED to
|
|
241
|
+
# colleague is decided later, once mode support is known.
|
|
242
|
+
if [[ ( "$VERB" == "explore" || "$VERB" == "review" ) && "$MAX_STEPS_EXPLICIT" -eq 0 ]]; then
|
|
243
|
+
MAX_STEPS=30
|
|
244
|
+
fi
|
|
245
|
+
|
|
213
246
|
# Now that the verb and its flags are known, require only the tools THIS path
|
|
214
247
|
# uses. feedback/clean shell straight to `colleague` (+ the git work-tree guard
|
|
215
248
|
# below), so they need only git — not python3/mktemp (qodo: the old blanket check
|
|
216
249
|
# failed those verbs in minimal envs). write --apply/--pr lands in place with no
|
|
217
250
|
# throwaway worktree, so it needs no mktemp either.
|
|
218
251
|
case "$VERB" in
|
|
219
|
-
feedback | clean) require_tools git ;;
|
|
252
|
+
feedback | clean | plan) require_tools git ;;
|
|
220
253
|
monitor | guide | stop) : ;;
|
|
221
254
|
write)
|
|
222
255
|
if [[ "$APPLY" -eq 1 || "$OPEN_PR" -eq 1 ]]; then
|
|
@@ -265,10 +298,99 @@ resolve_colleague || exit 2
|
|
|
265
298
|
# Per-request timeout is config (no drive flag); EngineConfig reads it from env.
|
|
266
299
|
# A local model can be slow on a growing context, so default generously.
|
|
267
300
|
export COLLEAGUE_TIMEOUT="$TIMEOUT"
|
|
268
|
-
|
|
301
|
+
|
|
302
|
+
# ── plan: colleague does the planning (the inverse of /think) ────────────────
|
|
303
|
+
# plan delegates the WHOLE planning arc to colleague via the `colleague plan`
|
|
304
|
+
# verb — no throwaway worktree, no prompt template. Gated non-interactively
|
|
305
|
+
# (--yes) so a delegating agent can hand off the arc and read the
|
|
306
|
+
# spec -> plan -> workforce result. colleague is the planning mind; /think keeps
|
|
307
|
+
# Claude as the planner (same arc, a different mind).
|
|
308
|
+
if [[ "$VERB" == "plan" ]]; then
|
|
309
|
+
plan_flags=(--repo "$REPO" --engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --yes)
|
|
310
|
+
# --quick skips the spec stage (#199); --no-workforce delivers spec+plan only,
|
|
311
|
+
# skipping the timeout-prone fan-out (#215). --timeout already applies via the
|
|
312
|
+
# COLLEAGUE_TIMEOUT export above. These are forwarded, never auto-applied.
|
|
313
|
+
[[ "$QUICK" -eq 1 ]] && plan_flags+=(--quick)
|
|
314
|
+
[[ "$NO_WORKFORCE" -eq 1 ]] && plan_flags+=(--no-workforce)
|
|
315
|
+
[[ "${JSON_OUT:-0}" -eq 1 ]] && plan_flags+=(--json)
|
|
316
|
+
plan_rc=0
|
|
317
|
+
"${COLLEAGUE[@]}" plan run "$ARG" "${plan_flags[@]}" || plan_rc=$?
|
|
318
|
+
# No silent auto-degrade: name the recovery levers (those not already set) and
|
|
319
|
+
# let the caller choose. Human hints are TEXT-mode only — in --json mode the
|
|
320
|
+
# consumer is a machine and colleague's own structured {code,message,remediation}
|
|
321
|
+
# error already went to stderr, so suppress the prose there (Qodo #230 F4).
|
|
322
|
+
if [[ "$plan_rc" -ne 0 && "${JSON_OUT:-0}" -ne 1 ]]; then
|
|
323
|
+
echo "hint: plan mode did not complete on this backend. Recovery options (no auto-degrade):" >&2
|
|
324
|
+
[[ "$NO_WORKFORCE" -eq 0 ]] && echo "hint: --no-workforce deliver the spec+plan only, skip the timeout-prone workforce fan-out (#215)" >&2
|
|
325
|
+
[[ "$QUICK" -eq 0 ]] && echo "hint: --quick skip the spec stage, plan directly from the request (#199)" >&2
|
|
326
|
+
echo "hint: --timeout N widen the per-request window (currently ${TIMEOUT}s)" >&2
|
|
327
|
+
fi
|
|
328
|
+
exit "$plan_rc"
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# ── explore/review adopt colleague's native mode profile (t4/spec R1) ───────
|
|
332
|
+
# explore/review's step budget + synthesis-reserve tail used to be caller-side
|
|
333
|
+
# overrides baked into this script (a fixed --max-steps 30 plus an exported
|
|
334
|
+
# COLLEAGUE_SYNTHESIS_RESERVE_STEPS=3). Those numbers now live in colleague's
|
|
335
|
+
# own mode-profile catalog (colleague/profiles.py "explore"/"review") and are
|
|
336
|
+
# applied runtime-side by `colleague work --mode explore|review`
|
|
337
|
+
# (colleague/config.py apply_mode_profile) — so this wrapper's job shrinks to
|
|
338
|
+
# "select the mode" instead of re-deriving the profile's numbers itself; a
|
|
339
|
+
# future retune of the profile then reaches this wrapper for free.
|
|
340
|
+
#
|
|
341
|
+
# Honest limit: this wrapper prefers an installed `colleague` on PATH ahead of
|
|
342
|
+
# a local checkout, and an installed CLI can be older than the one this repo
|
|
343
|
+
# ships (it can predate --mode entirely). Detect that cheaply by checking the
|
|
344
|
+
# resolved CLI's OWN --help text for the literal flag with a plain bash
|
|
345
|
+
# substring match (no external text-search tool — the resolver stays as
|
|
346
|
+
# dependency-free as the uv-fallback path above, #190); fall back to the old
|
|
347
|
+
# caller-side defaults when --mode isn't there, so the wrapper keeps working
|
|
348
|
+
# unmodified against a stale CLI.
|
|
349
|
+
#
|
|
350
|
+
# NOTE: the substring must be anchored past the flag name — "--mode" is
|
|
351
|
+
# itself a literal prefix of "--model" (already a flag on every version), so
|
|
352
|
+
# a naive `*--mode*` match false-positives on a stale --help that only has
|
|
353
|
+
# --model. Requiring the next character NOT be a letter (or end-of-string)
|
|
354
|
+
# distinguishes a genuine "--mode ..."/"--mode]" occurrence from "--model".
|
|
355
|
+
MODE_SUPPORTED=0
|
|
356
|
+
if [[ "$VERB" == "explore" || "$VERB" == "review" ]]; then
|
|
357
|
+
help_out="$("${COLLEAGUE[@]}" work --help 2>/dev/null)" || help_out=""
|
|
358
|
+
if [[ "$help_out" == *"--mode"[!a-zA-Z]* || "$help_out" == *--mode ]]; then
|
|
359
|
+
MODE_SUPPORTED=1
|
|
360
|
+
fi
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
if [[ ( "$VERB" == "explore" || "$VERB" == "review" ) && "$MODE_SUPPORTED" -eq 0 ]]; then
|
|
364
|
+
# Legacy fallback (stale CLI without --mode): reserve a few steps for the
|
|
365
|
+
# final verdict turn so a big-diff review/explore yields findings instead
|
|
366
|
+
# of dying mid-read (#197). Caller env wins.
|
|
367
|
+
: "${COLLEAGUE_SYNTHESIS_RESERVE_STEPS:=3}"
|
|
368
|
+
export COLLEAGUE_SYNTHESIS_RESERVE_STEPS
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --json)
|
|
372
|
+
# --max-steps: forwarded unless this is a native-mode-capable explore/review run
|
|
373
|
+
# with no explicit --max-steps — then the flag is withheld so colleague's own
|
|
374
|
+
# mode profile supplies the step budget as a DEFAULT at runtime. An explicit
|
|
375
|
+
# --max-steps always wins, in EITHER direction (lower or higher than the
|
|
376
|
+
# profile's own number): it is still forwarded here, and the runtime resolves
|
|
377
|
+
# an explicit flag ahead of any profile default (colleague/config.py
|
|
378
|
+
# apply_mode_profile's explicit-knobs precedence).
|
|
379
|
+
if [[ "$MAX_STEPS_EXPLICIT" -eq 1 || "$MODE_SUPPORTED" -eq 0 || ( "$VERB" != "explore" && "$VERB" != "review" ) ]]; then
|
|
380
|
+
COMMON_FLAGS+=(--max-steps "$MAX_STEPS")
|
|
381
|
+
fi
|
|
382
|
+
# --mode: select colleague's native explore/review constraint profile when the
|
|
383
|
+
# resolved CLI supports it (detected above). write carries no --mode — its
|
|
384
|
+
# profile is behavior-neutral (identical to no mode at all), so the numbers
|
|
385
|
+
# stay exactly as documented (--max-steps 20 default, no reserve override).
|
|
386
|
+
if [[ "$MODE_SUPPORTED" -eq 1 && ( "$VERB" == "explore" || "$VERB" == "review" ) ]]; then
|
|
387
|
+
COMMON_FLAGS+=(--mode "$VERB")
|
|
388
|
+
fi
|
|
269
389
|
# --watch arms a flight for EVERY drive verb (explore/review/write), so it lives on
|
|
270
390
|
# the shared flag list — not inside one verb's path — so monitor/guide/stop work.
|
|
271
391
|
[[ "${WATCH:-0}" -eq 1 ]] && COMMON_FLAGS+=(--watch)
|
|
392
|
+
# --role forwards a typed-subagent role to the work item.
|
|
393
|
+
[[ -n "${ROLE:-}" ]] && COMMON_FLAGS+=(--role "$ROLE")
|
|
272
394
|
|
|
273
395
|
# ── render an instruction from a prompt template ────────────────────────────
|
|
274
396
|
render_prompt() {
|
|
@@ -305,20 +427,38 @@ print_result() {
|
|
|
305
427
|
# empty (its artifact was discarded with the worktree, so it is not gradable).
|
|
306
428
|
# $3 (optional): exit code from the colleague drive command, propagated to
|
|
307
429
|
# the caller when the drive itself failed.
|
|
308
|
-
ASK_COLLEAGUE_REAL_ARTIFACT_DIR="${1:-}" ASK_COLLEAGUE_GRADABLE="${2:-}" ASK_COLLEAGUE_DRIVE_RC="${3:-}" ASK_COLLEAGUE_JSON="${JSON_OUT:-0}" python3 -c '
|
|
430
|
+
ASK_COLLEAGUE_REAL_ARTIFACT_DIR="${1:-}" ASK_COLLEAGUE_GRADABLE="${2:-}" ASK_COLLEAGUE_DRIVE_RC="${3:-}" ASK_COLLEAGUE_JSON="${JSON_OUT:-0}" ASK_COLLEAGUE_MAX_STEPS="${MAX_STEPS:-20}" python3 -c '
|
|
309
431
|
import sys, json, os
|
|
310
432
|
raw = sys.stdin.read().strip()
|
|
311
433
|
json_mode = os.environ.get("ASK_COLLEAGUE_JSON") == "1"
|
|
434
|
+
def _fail(code, message, remediation, detail=None):
|
|
435
|
+
# #226: in --json mode emit a structured {code, message, remediation} object
|
|
436
|
+
# on stderr (matching colleague CliError shape) so a machine consumer parses
|
|
437
|
+
# a failure the same way it parses success; in text mode keep the standard
|
|
438
|
+
# error:/hint: contract. stdout stays clean (no result) on every path.
|
|
439
|
+
if json_mode:
|
|
440
|
+
obj = {"code": code, "message": message, "remediation": remediation}
|
|
441
|
+
if detail is not None:
|
|
442
|
+
obj["detail"] = detail
|
|
443
|
+
sys.stderr.write(json.dumps(obj) + "\n")
|
|
444
|
+
else:
|
|
445
|
+
sys.stderr.write("error: " + message + "\n")
|
|
446
|
+
if detail is not None:
|
|
447
|
+
sys.stderr.write(detail + "\n")
|
|
448
|
+
if remediation:
|
|
449
|
+
sys.stderr.write("hint: " + remediation + "\n")
|
|
450
|
+
sys.exit(code)
|
|
312
451
|
if not raw:
|
|
313
|
-
|
|
314
|
-
|
|
452
|
+
_fail(2, "colleague produced no result on stdout (see diagnostics above)",
|
|
453
|
+
"re-run; if it persists, check the backend with colleague doctor --probe")
|
|
315
454
|
try:
|
|
316
455
|
d = json.loads(raw)
|
|
317
456
|
except Exception:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
457
|
+
_fail(2, "could not parse colleague --json output",
|
|
458
|
+
"the backend may have emitted non-JSON; see the raw output and diagnostics above",
|
|
459
|
+
detail=raw[:2000])
|
|
321
460
|
ok = d.get("status") == "ok"
|
|
461
|
+
inc = d.get("incompletion") if isinstance(d.get("incompletion"), dict) else None
|
|
322
462
|
tid = d.get("task_id") or ""
|
|
323
463
|
# Resolve the artifact path to the preserved copy when the drive ran in a
|
|
324
464
|
# throwaway worktree (read-only verbs); the raw JSON points into the now-deleted
|
|
@@ -332,11 +472,37 @@ if ap and real_dir:
|
|
|
332
472
|
# trailing off mid-task. Warn so the caller treats it as a partial, not a verdict.
|
|
333
473
|
# The warning is a DIAGNOSTIC -> always stderr (never stdout), so both the digest
|
|
334
474
|
# and --json keep a clean, machine-readable stdout (no single quotes in this body).
|
|
475
|
+
# Enriched partial warnings: include reached step count and a concrete larger
|
|
476
|
+
# --max-steps to retry with, so the hint is actionable (#194).
|
|
477
|
+
max_steps = int(os.environ.get("ASK_COLLEAGUE_MAX_STEPS", "20"))
|
|
478
|
+
stats = d.get("stats") or {}
|
|
479
|
+
model_turns = stats.get("model_turns")
|
|
480
|
+
step_count = stats.get("step_count")
|
|
335
481
|
if d.get("stopped_without_finish"):
|
|
336
|
-
|
|
337
|
-
|
|
482
|
+
reached = model_turns if model_turns is not None else step_count
|
|
483
|
+
if reached is not None:
|
|
484
|
+
print(f"warning: drive ended without calling finish (reached {reached} model turns of {max_steps}) — re-run with --max-steps {2 * max_steps} to go deeper.", file=sys.stderr)
|
|
485
|
+
else:
|
|
486
|
+
print("warning: drive ended without calling finish — treat the summary as a", file=sys.stderr)
|
|
487
|
+
print(" partial (the model stopped mid-task), not an authoritative result.", file=sys.stderr)
|
|
338
488
|
elif d.get("not_finished"):
|
|
339
|
-
|
|
489
|
+
reached = model_turns if model_turns is not None else step_count
|
|
490
|
+
if reached is not None:
|
|
491
|
+
print(f"warning: drive ran out of steps without finishing (reached {reached} model turns of {max_steps}) — re-run with --max-steps {2 * max_steps} to go deeper.", file=sys.stderr)
|
|
492
|
+
else:
|
|
493
|
+
print("warning: drive ran out of steps without finishing — summary is partial.", file=sys.stderr)
|
|
494
|
+
# No-result sentinel: the drive produced nothing actionable (#192).
|
|
495
|
+
summary = d.get("summary") or ""
|
|
496
|
+
if summary == "__COLLEAGUE_NO_RESULT_PRODUCED__":
|
|
497
|
+
print("warning: no result produced — widen --max-steps or narrow the scope.", file=sys.stderr)
|
|
498
|
+
# Incompletion: when the run did NOT deliver (status != "ok") and colleague
|
|
499
|
+
# carried an incompletion record, surface it as a diagnostic to stderr so the
|
|
500
|
+
# delegating caller knows WHY the run failed and what to do next. Always stderr
|
|
501
|
+
# (never stdout) so --json stdout stays pure.
|
|
502
|
+
if not ok and inc:
|
|
503
|
+
reason = inc.get("reason", "")
|
|
504
|
+
recommendation = inc.get("recommendation", "")
|
|
505
|
+
print(f"incomplete: {reason} — {recommendation}", file=sys.stderr)
|
|
340
506
|
if json_mode:
|
|
341
507
|
# --json contract: stdout carries ONLY the TaskResult JSON; every
|
|
342
508
|
# human/diagnostic line already went to stderr above. The exit code still
|
|
@@ -359,7 +525,10 @@ if json_mode:
|
|
|
359
525
|
# task_id is already in the payload, but echoing the copy-paste grade hint
|
|
360
526
|
# keeps the convention every work item follows (rule 907536) without breaking
|
|
361
527
|
# the stdout contract. Gated on gradable, exactly like the digest below.
|
|
362
|
-
|
|
528
|
+
# Skip the grade hint for the no-result sentinel (#192). A FAILED/incomplete but
|
|
529
|
+
# gradable drive still gets the hint — a failure rated 1/5 is the ROI signal
|
|
530
|
+
# (#139); the incompletion diagnostic above already says it did not succeed.
|
|
531
|
+
if tid and gradable and summary != "__COLLEAGUE_NO_RESULT_PRODUCED__":
|
|
363
532
|
print("task:", tid, file=sys.stderr)
|
|
364
533
|
print("grade:", "ask-colleague feedback", tid, "--rating N", file=sys.stderr)
|
|
365
534
|
else:
|
|
@@ -380,7 +549,10 @@ else:
|
|
|
380
549
|
# drive (colleague writes an artifact on failure too, h5): a failure rated
|
|
381
550
|
# 1/5 is exactly the ROI signal, so the hint must not be gated on `ok` (#139
|
|
382
551
|
# qodo). It prints to `out` (stderr on failure), matching the failure digest.
|
|
383
|
-
|
|
552
|
+
# Skip the grade footer for the no-result sentinel (#192): a drive that
|
|
553
|
+
# produced nothing is not worth grading.
|
|
554
|
+
summary = d.get("summary") or ""
|
|
555
|
+
if tid and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1" and summary != "__COLLEAGUE_NO_RESULT_PRODUCED__":
|
|
384
556
|
print("grade:", "ask-colleague feedback", tid, "--rating N", file=out)
|
|
385
557
|
if ok:
|
|
386
558
|
sys.exit(0)
|
|
@@ -474,7 +646,7 @@ _preserve_artifact() {
|
|
|
474
646
|
local art_name="$1"
|
|
475
647
|
[[ -n "$art_name" && -n "$_WT" ]] || return 1
|
|
476
648
|
if ! _valid_segment "$art_name"; then
|
|
477
|
-
printf '
|
|
649
|
+
printf 'error: refusing to preserve unsafe artifact name %q\n' "$art_name" >&2
|
|
478
650
|
return 1
|
|
479
651
|
fi
|
|
480
652
|
local src="$_WT/.colleague"
|
|
@@ -484,7 +656,7 @@ _preserve_artifact() {
|
|
|
484
656
|
# The JSON artifact is the record of the drive — surface a copy failure rather
|
|
485
657
|
# than swallow it, so the caller can fall back to honest path reporting.
|
|
486
658
|
if ! cp -f "$src/$art_name" "$dst/$art_name"; then
|
|
487
|
-
printf '
|
|
659
|
+
printf 'error: could not preserve artifact %s\n' "$art_name" >&2
|
|
488
660
|
return 1
|
|
489
661
|
fi
|
|
490
662
|
# The trace shares the artifact stem (.json -> .trace.jsonl); a best-effort
|
|
@@ -568,7 +740,7 @@ run_write() {
|
|
|
568
740
|
return
|
|
569
741
|
fi
|
|
570
742
|
if [[ "$ALLOW_DIRTY" -eq 0 ]] \
|
|
571
|
-
&& [[ -n "$(git -C "$REPO" status --porcelain 2>/dev/null)" ]]; then
|
|
743
|
+
&& [[ -n "$(git -C "$REPO" status --porcelain --untracked-files=no 2>/dev/null)" ]]; then
|
|
572
744
|
echo "error: working tree is dirty — commit/stash first, or pass --allow-dirty" >&2
|
|
573
745
|
echo "hint: 'colleague drive --no-pr' commits uncommitted edits onto the drive branch" >&2
|
|
574
746
|
# User-fixable state guard -> exit 1, matching the runtime's own
|
|
@@ -641,13 +813,32 @@ run_clean() {
|
|
|
641
813
|
"${cmd[@]}"
|
|
642
814
|
}
|
|
643
815
|
|
|
816
|
+
# ── front-load a filtered, capped diff into the review instruction ──────────
|
|
817
|
+
# Issue #220a: print a diffstat + filtered diff body so the model does not waste
|
|
818
|
+
# turns running `git diff` itself. Lockfile/vendored noise is excluded; the body
|
|
819
|
+
# is capped at ${COLLEAGUE_MAX_OUTPUT_CHARS:-100000} characters.
|
|
820
|
+
front_load_review_diff() {
|
|
821
|
+
local cap="${COLLEAGUE_MAX_OUTPUT_CHARS:-100000}"
|
|
822
|
+
printf '%s\n' "--- DIFF UNDER REVIEW (filtered + capped; read specific files for more) ---"
|
|
823
|
+
git -C "$REPO" diff "$BASE"...HEAD --stat
|
|
824
|
+
printf '\n'
|
|
825
|
+
local diff_body
|
|
826
|
+
diff_body="$(git -C "$REPO" diff "$BASE"...HEAD -- . ':(exclude)*.lock' ':(exclude)**/*.lock' ':(exclude)package-lock.json' ':(exclude)**/package-lock.json' ':(exclude)*.min.js' ':(exclude)**/*.min.js' 2>/dev/null || true)"
|
|
827
|
+
if [[ "${#diff_body}" -gt "$cap" ]]; then
|
|
828
|
+
printf '%s\n' "${diff_body:0:$cap}"
|
|
829
|
+
printf '%s\n' "[... diff body truncated at ${cap} chars; read specific files for the rest ...]"
|
|
830
|
+
else
|
|
831
|
+
printf '%s\n' "$diff_body"
|
|
832
|
+
fi
|
|
833
|
+
}
|
|
834
|
+
|
|
644
835
|
# ── piloting verbs: thin passthroughs to the `colleague flight` noun ─────────
|
|
645
836
|
_flight_json_flag() { [[ "$JSON_OUT" -eq 1 ]] && printf -- '--json'; }
|
|
646
837
|
|
|
647
838
|
run_monitor() {
|
|
648
839
|
local fid="${ARG%% *}"
|
|
649
840
|
[[ -z "$fid" ]] && { echo "error: monitor needs a flight task-id" >&2; exit 1; }
|
|
650
|
-
"${COLLEAGUE[@]}" flight status "$fid" --repo "$REPO" $(_flight_json_flag)
|
|
841
|
+
"${COLLEAGUE[@]}" flight status "$fid" --repo "$REPO" --follow $(_flight_json_flag)
|
|
651
842
|
}
|
|
652
843
|
|
|
653
844
|
run_stop() {
|
|
@@ -665,7 +856,7 @@ run_guide() {
|
|
|
665
856
|
|
|
666
857
|
case "$VERB" in
|
|
667
858
|
explore) run_readonly "$(render_prompt explore)" ;;
|
|
668
|
-
review) run_readonly "$(render_prompt review)" ;;
|
|
859
|
+
review) run_readonly "$(render_prompt review)"$'\n\n'"$(front_load_review_diff)" ;;
|
|
669
860
|
write) run_write "$(render_prompt write)" ;;
|
|
670
861
|
feedback) run_feedback "$ARG" ;;
|
|
671
862
|
clean) run_clean ;;
|