workledger-cli 0.6.1__tar.gz → 0.7.0__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.
Files changed (79) hide show
  1. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/ask-colleague/SKILL.md +31 -7
  2. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/ask-colleague/prompts/explore.md +10 -0
  3. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/ask-colleague/prompts/review.md +8 -7
  4. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/ask-colleague/prompts/write.md +6 -0
  5. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +302 -36
  6. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/CHANGELOG.md +25 -0
  7. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/PKG-INFO +2 -2
  8. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/pyproject.toml +1 -1
  9. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/uv.lock +31 -31
  10. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/agent-config/SKILL.md +0 -0
  11. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  12. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  13. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  14. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  15. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/challenge/SKILL.md +0 -0
  16. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/cicd/SKILL.md +0 -0
  17. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  18. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  19. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  20. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  21. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  22. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/SKILL.md +0 -0
  23. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  24. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  25. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  26. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  27. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  28. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  29. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/deviate/SKILL.md +0 -0
  30. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  31. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  32. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  33. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  34. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/recall/SKILL.md +0 -0
  35. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/recall/scripts/recall.sh +0 -0
  36. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/remember/SKILL.md +0 -0
  37. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/remember/scripts/remember.sh +0 -0
  38. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/run-tests/SKILL.md +0 -0
  39. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  40. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/scope/SKILL.md +0 -0
  41. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  42. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  43. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  44. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  45. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/summarize-delivery/SKILL.md +0 -0
  46. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/think/SKILL.md +0 -0
  47. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/think/scripts/think.sh +0 -0
  48. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/version-bump/SKILL.md +0 -0
  49. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  50. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.claude/skills.local.yaml.example +0 -0
  51. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.flake8 +0 -0
  52. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.github/workflows/publish.yml +0 -0
  53. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.github/workflows/tests.yml +0 -0
  54. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.gitignore +0 -0
  55. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/.markdownlint-cli2.yaml +0 -0
  56. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/AGENTS.colleague.md +0 -0
  57. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/CLAUDE.md +0 -0
  58. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/LICENSE +0 -0
  59. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/README.md +0 -0
  60. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/culture.yaml +0 -0
  61. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/docs/skill-sources.md +0 -0
  62. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/sonar-project.properties +0 -0
  63. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/tests/__init__.py +0 -0
  64. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/tests/test_cli.py +0 -0
  65. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/tests/test_cli_introspection.py +0 -0
  66. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/__init__.py +0 -0
  67. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/__main__.py +0 -0
  68. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/__init__.py +0 -0
  69. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/__init__.py +0 -0
  70. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/cli.py +0 -0
  71. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/doctor.py +0 -0
  72. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/explain.py +0 -0
  73. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/learn.py +0 -0
  74. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/overview.py +0 -0
  75. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_commands/whoami.py +0 -0
  76. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_errors.py +0 -0
  77. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/cli/_output.py +0 -0
  78. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/explain/__init__.py +0 -0
  79. {workledger_cli-0.6.1 → workledger_cli-0.7.0}/workledger/explain/catalog.py +0 -0
@@ -27,7 +27,7 @@ description: >
27
27
  # ask-colleague — lean on colleague as a different mind
28
28
 
29
29
  `ask-colleague` drives the **`colleague`** CLI so a Claude agent can hand a scoped
30
- task to a *different* backend (default: a local vLLM `Qwen3.6-27B` on
30
+ task to a *different* backend (default: a local vLLM `Qwen3.8-27B` on
31
31
  `:8001`). Colleague's model is **not** assumed to be stronger than you — its
32
32
  value is **diversity**. A second, independent mind catches things the author's
33
33
  mind glides past, which is why **review** is the headline verb. Treat it the way
@@ -101,7 +101,9 @@ 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. |
106
+ | `resume <task-id\|last> [--detach]` | **Resume a cut run** — a timed-out / SIGTERM'd / budget-exhausted work item picked back up from its persisted artifact (`colleague work --continue`, lineage on `TaskResult.continued_from`). `last` resolves the most recent work item in `--repo`. `--detach` runs it under `setsid`/`nohup` and returns at once (not `--background`, which drops the continue id — colleague#418). | Continues the ORIGINAL run's `colleague/<id>` work branch; never touches your tree / branch. |
105
107
  | `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
108
 
107
109
  ### Options
@@ -111,9 +113,13 @@ else an install hint.
111
113
  | `--repo PATH` | Target repo (default: `.`). |
112
114
  | `--base BRANCH` | Base for the `review` diff (default: `main`). |
113
115
  | `--engine NAME` | Backend plugin (default: `$COLLEAGUE_ENGINE` or `vllm-openai`). |
114
- | `--model NAME` | Model (default: `$COLLEAGUE_MODEL` or `sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP`). |
116
+ | `--model NAME` | Model (default: `$COLLEAGUE_MODEL` or `unsloth/Qwen3.8-27B-NVFP4`). |
115
117
  | `--base-url URL` | OpenAI base URL (default: `$COLLEAGUE_BASE_URL` or `http://localhost:8001/v1`). |
116
- | `--max-steps N` | Loop step budget (default: 20). |
118
+ | `--role NAME` | Typed subagent role for the run (`explorer`, `reviewer`, `validator`, `planner`, `writer`). Since #416 a top-level `--role explorer` runs at thinking effort **`low`** (off selectable via `--effort off`); other top-level roles keep the acting seat's effort. |
119
+ | `--effort RUNG` | **Thinking effort for the acting seat** (#416): `off` \| `low` \| `medium` \| `high` \| `xhigh` \| `default`. Unset = colleague's own table (acting seat `medium`). `off` sends `enable_thinking:false` — the measured win for small, well-specified briefs (same brief: off 24 s / xhigh 88 s / medium 129 s, all correct — `docs/evidence/2026-08-22-per-seat-thinking-effort-416-results.md`); `xhigh` for open-ended judgement; `default` = the kill-switch (send nothing, the pre-#416 wire). Exported as `COLLEAGUE_CORTEX_REASONING_EFFORT` + `COLLEAGUE_WORKER_REASONING_EFFORT`; validated before the run. |
120
+ | `--seat-effort S=R[,S=R]` | Per-seat override for the other seats (`cortex`, `worker`, `deepthink`, `senses`, `evaluator`, `design`) — e.g. `--seat-effort senses=off,deepthink=xhigh`. Exported as `COLLEAGUE_<SEAT>_REASONING_EFFORT`. Children keep their role table (writer/planner medium, reviewer/validator low, explorer off) unless the parent overrides per delegation. |
121
+ | `--detach` | (`resume`) run detached and return at once; pilot with `monitor` / `guide` / `stop` once the log names the new flight id. |
122
+ | `--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
123
  | `--apply` | (`write`) apply the change in place (work branch) instead of previewing. |
118
124
  | `--allow-dirty` | (`write`) allow running on a dirty tree (only matters with `--apply` / `--pr`). |
119
125
  | `--pr` | (`write`) push + open a PR instead of a local work branch (implies `--apply`). |
@@ -155,6 +161,24 @@ keeping stdout valid JSON for a machine consumer while diagnostics stay on stder
155
161
  (or `colleague clean`) to reap it — start with `--dry-run` to see what it would
156
162
  remove. It only ever touches `colleague/*` refs and `.colleague/` artifacts.
157
163
 
164
+ ## Thinking effort and resuming (#416)
165
+
166
+ Colleague resolves a **per-seat thinking effort** where each seat is built,
167
+ never per turn — the acting seat defaults to `medium`, deepthink `xhigh`,
168
+ senses/Talker `off`, children by role (writer/planner `medium`,
169
+ reviewer/validator `low`, explorer `off`). The wrapper exposes the operator
170
+ overrides as `--effort` (acting seat) and `--seat-effort` (any seat); a typo
171
+ fails fast. Rule of thumb from the measurements: **`--effort off` for small,
172
+ well-specified briefs** (5× faster, same result), **leave the default for
173
+ ordinary work**, **`--effort xhigh` for open-ended judgement** (review of a
174
+ subtle diff, a plan). Effort does not rescue a module-sized brief — split the
175
+ request instead (#415).
176
+
177
+ A run cut by a timeout, SIGTERM or an exhausted budget is **resumable**:
178
+ `ask-colleague resume <task-id|last>` continues it from its artifact on the same
179
+ work branch; `--detach` keeps your shell free. Prefer resume over re-dispatch —
180
+ the continuation carries the prior steps and lineage.
181
+
158
182
  ## Piloting a flight
159
183
 
160
184
  Dispatch a drive with `--watch` (on `explore`, `review`, or `write`) to make the
@@ -207,7 +231,7 @@ stop requests take effect on the next iteration rather than interrupting mid-ste
207
231
 
208
232
  ## Provenance
209
233
 
210
- This is a **first-party** colleague skill — colleague is its origin. It is
211
- the inverse of the other skills under `.claude/skills/`, which workledger-cli
212
- vendors *from* guildmaster. See `docs/skill-sources.md`. The `cite, don't import`
213
- policy holds: downstream repos copy it, they don't symlink or depend on it.
234
+ This is a **first-party** colleague skill — colleague is its origin. See
235
+ `docs/skill-sources.md` for the consumer's per-repo skill ledger. The `cite,
236
+ don't import` policy holds: downstream repos copy it, they don't symlink or
237
+ depend on it.
@@ -18,6 +18,16 @@ 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.
25
+ - NARRATE PROGRESS: with EVERY tool call, write one short line of plain text
26
+ first — what you just learned and what you are checking next. That line rides
27
+ the run's flight feed, so the operator can see where you are instead of a
28
+ silent turn. A long think with nothing written looks like a stall. If you are
29
+ within ~3 steps of the budget, STOP and write the answer-so-far (partial is
30
+ fine, mark it partial) rather than reading one more file.
21
31
 
22
32
  When you are done, call finish with a structured findings report:
23
33
  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 change under review is the committed diff on this branch versus its base
9
- (`$BASE`). Start by running, read-only:
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
@@ -29,6 +24,12 @@ Rules:
29
24
  NOTHING and wastes the entire drive — so the moment you have enough to write a
30
25
  useful review (or you are within a few steps of the budget), STOP reading and
31
26
  call `finish`. Err on the side of finishing early.
27
+ - NARRATE PROGRESS: with EVERY tool call, write one short line of plain text
28
+ first — what you just learned and what you are checking next. That line rides
29
+ the run's flight feed, so the operator can see where you are instead of a
30
+ silent turn. A long think with nothing written looks like a stall. If you are
31
+ within ~3 steps of the budget, STOP and write the answer-so-far (partial is
32
+ fine, mark it partial) rather than reading one more file.
32
33
 
33
34
  When you are done, call finish with a structured review:
34
35
  1. Correctness risks / likely bugs (with file:line).
@@ -10,6 +10,12 @@ Rules:
10
10
  text file with exactly one trailing newline.
11
11
  - You may read, create, modify files, and run commands as needed.
12
12
  - Don't widen the scope: do exactly what was asked, nothing more.
13
+ - NARRATE PROGRESS: with EVERY tool call, write one short line of plain text
14
+ first — what you just learned and what you are checking next. That line rides
15
+ the run's flight feed, so the operator can see where you are instead of a
16
+ silent turn. A long think with nothing written looks like a stall. If you are
17
+ within ~3 steps of the budget, STOP and write the answer-so-far (partial is
18
+ fine, mark it partial) rather than reading one more file.
13
19
 
14
20
  When you are done, call finish with a short summary of exactly what you changed
15
21
  and why.
@@ -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
- && grep -q '^name = "colleague"' "$dir/pyproject.toml" 2>/dev/null; then
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,22 +89,38 @@ 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> Watch a running flight's live feed
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)
99
+ ask-colleague resume <task-id|last> [--detach] Resume a cut/timed-out/SIGTERM'd run from its artifact (work --continue)
90
100
 
91
101
  Options:
92
102
  --repo PATH Target repo (default: .)
93
103
  --dry-run (clean) report what would be reaped without changing anything
94
104
  --base BRANCH Base for `review` diff (default: main)
95
105
  --engine NAME Backend plugin (default: $COLLEAGUE_ENGINE or vllm-openai)
96
- --model NAME Model (default: $COLLEAGUE_MODEL or sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP)
106
+ --model NAME Model (default: $COLLEAGUE_MODEL or unsloth/Qwen3.8-27B-NVFP4)
107
+ --role NAME Typed subagent role (e.g. explorer, reviewer, writer); a top-level
108
+ explorer runs at thinking effort "low" by default (#416)
109
+ --effort RUNG Thinking effort for the ACTING seat: off|low|medium|high|xhigh|default
110
+ (#416; default = colleague's table, medium for the acting seat;
111
+ "default" = the kill-switch, send nothing). Exported as
112
+ COLLEAGUE_CORTEX_REASONING_EFFORT + COLLEAGUE_WORKER_REASONING_EFFORT.
113
+ --seat-effort S=R Per-seat override, comma-separated (seats: cortex, worker, deepthink,
114
+ senses, evaluator, design), e.g. --seat-effort senses=off,deepthink=xhigh
115
+ --detach (resume) run detached (setsid/nohup) and return at once; pilot it with
116
+ monitor/guide/stop once the new flight id appears in the log
97
117
  --base-url URL OpenAI base URL (default: $COLLEAGUE_BASE_URL or http://localhost:8001/v1)
98
- --max-steps N Loop step budget (default: 20)
118
+ --max-steps N Loop step budget (default: 20; explore/review default from
119
+ colleague's own "explore"/"review" mode profile, today 30 —
120
+ an explicit --max-steps always overrides, lower or higher)
99
121
  --timeout N Per-request timeout, seconds (default: $COLLEAGUE_TIMEOUT or 300)
122
+ --no-workforce (plan) deliver the spec+plan only, skip the workforce fan-out (#215)
123
+ --quick / --no-spec (plan) skip the spec stage, plan directly from the request (#199)
100
124
  --apply (write) apply the change in place (drive branch) instead of previewing
101
125
  --allow-dirty (write) allow running on a dirty tree (only with --apply/--pr)
102
126
  --pr (write) push + open a PR instead of a local drive branch (implies --apply)
@@ -123,11 +147,11 @@ EOF
123
147
  # ── parse the verb ──────────────────────────────────────────────────────────
124
148
  VERB="${1:-}"
125
149
  case "$VERB" in
126
- explore | review | write | feedback | clean | monitor | guide | stop) shift ;;
150
+ explore | review | write | plan | feedback | clean | monitor | guide | stop | resume) shift ;;
127
151
  -h | --help) usage; exit 0 ;;
128
152
  "") usage >&2; exit 1 ;; # missing arg -> user-input error (#161)
129
153
  *)
130
- echo "error: unknown verb '$VERB' (expected explore|review|write|feedback|clean|monitor|guide|stop)" >&2
154
+ echo "error: unknown verb '$VERB' (expected explore|review|write|plan|feedback|clean|monitor|guide|stop|resume)" >&2
131
155
  echo "hint: run 'ask-colleague --help'" >&2
132
156
  exit 1 # bad verb -> user-input error (#161)
133
157
  ;;
@@ -139,9 +163,7 @@ esac
139
163
  # feedback/clean are thin pass-throughs to `colleague` plus the shared git
140
164
  # work-tree guard, so they need only git; the drive verbs (explore/review/write)
141
165
  # also render a prompt and parse colleague's --json result via python3, and the
142
- # worktree-isolated paths additionally need mktemp. grep is only used by the
143
- # uv-fallback resolver, which degrades to the clear "colleague not found" message
144
- # when absent, so it is not a hard requirement here.
166
+ # worktree-isolated paths additionally need mktemp.
145
167
  require_tools() { # $@ = tool names this verb path needs
146
168
  local missing=() t
147
169
  for t in "$@"; do
@@ -169,9 +191,10 @@ REPO="."
169
191
  BASE="main"
170
192
  # COLLEAGUE_* wins; the legacy CONVERTIBLE_* names are honored as a deprecated fallback.
171
193
  ENGINE="${COLLEAGUE_ENGINE:-${CONVERTIBLE_ENGINE:-vllm-openai}}"
172
- MODEL="${COLLEAGUE_MODEL:-${CONVERTIBLE_MODEL:-sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP}}"
194
+ MODEL="${COLLEAGUE_MODEL:-${CONVERTIBLE_MODEL:-unsloth/Qwen3.8-27B-NVFP4}}"
173
195
  BASE_URL="${COLLEAGUE_BASE_URL:-${CONVERTIBLE_BASE_URL:-http://localhost:8001/v1}}"
174
196
  MAX_STEPS=20
197
+ MAX_STEPS_EXPLICIT=0
175
198
  TIMEOUT="${COLLEAGUE_TIMEOUT:-${CONVERTIBLE_TIMEOUT:-300}}"
176
199
  ALLOW_DIRTY=0
177
200
  APPLY=0
@@ -183,6 +206,12 @@ NOTES=""
183
206
  BY=""
184
207
  ARG=""
185
208
  JSON_OUT=0
209
+ ROLE=""
210
+ EFFORT=""
211
+ SEAT_EFFORT=""
212
+ DETACH=0
213
+ QUICK=0
214
+ NO_WORKFORCE=0
186
215
 
187
216
  while [[ $# -gt 0 ]]; do
188
217
  case "$1" in
@@ -190,14 +219,20 @@ while [[ $# -gt 0 ]]; do
190
219
  --base) need_value "$#" "$1"; BASE="$2"; shift 2 ;;
191
220
  --engine) need_value "$#" "$1"; ENGINE="$2"; shift 2 ;;
192
221
  --model) need_value "$#" "$1"; MODEL="$2"; shift 2 ;;
222
+ --role) need_value "$#" "$1"; ROLE="$2"; shift 2 ;;
223
+ --effort) need_value "$#" "$1"; EFFORT="$2"; shift 2 ;;
224
+ --seat-effort) need_value "$#" "$1"; SEAT_EFFORT="$2"; shift 2 ;;
225
+ --detach) DETACH=1; shift ;;
193
226
  --base-url) need_value "$#" "$1"; BASE_URL="$2"; shift 2 ;;
194
- --max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; shift 2 ;;
227
+ --max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; MAX_STEPS_EXPLICIT=1; shift 2 ;;
195
228
  --timeout) need_value "$#" "$1"; TIMEOUT="$2"; shift 2 ;;
196
229
  --apply) APPLY=1; shift ;;
197
230
  --watch) WATCH=1; shift ;;
198
231
  --allow-dirty) ALLOW_DIRTY=1; shift ;;
199
232
  --pr) OPEN_PR=1; shift ;;
200
233
  --dry-run) DRY_RUN=1; shift ;;
234
+ --quick | --no-spec) QUICK=1; shift ;; # plan: skip the spec stage (#199)
235
+ --no-workforce) NO_WORKFORCE=1; shift ;; # plan: deliver spec+plan only (#215)
201
236
  --rating) need_value "$#" "$1"; RATING="$2"; shift 2 ;;
202
237
  --notes) need_value "$#" "$1"; NOTES="$2"; shift 2 ;;
203
238
  --by) need_value "$#" "$1"; BY="$2"; shift 2 ;;
@@ -210,14 +245,28 @@ while [[ $# -gt 0 ]]; do
210
245
  esac
211
246
  done
212
247
 
248
+ # Per-verb default: explore and review get a modest higher budget (30) for wider
249
+ # surveys and read-heavy diffs; write keeps the global default (20). Only apply
250
+ # when the user did NOT pass an explicit --max-steps.
251
+ #
252
+ # t4/spec R1: this number now ALSO matches colleague's own native "explore"/
253
+ # "review" mode profile (colleague/profiles.py), which is what actually drives
254
+ # the step budget at runtime once `--mode` is selected below — this wrapper-side
255
+ # value survives only so the "widen --max-steps" re-run hint (print_result)
256
+ # still names the right number; whether --max-steps is actually FORWARDED to
257
+ # colleague is decided later, once mode support is known.
258
+ if [[ ( "$VERB" == "explore" || "$VERB" == "review" ) && "$MAX_STEPS_EXPLICIT" -eq 0 ]]; then
259
+ MAX_STEPS=30
260
+ fi
261
+
213
262
  # Now that the verb and its flags are known, require only the tools THIS path
214
263
  # uses. feedback/clean shell straight to `colleague` (+ the git work-tree guard
215
264
  # below), so they need only git — not python3/mktemp (qodo: the old blanket check
216
265
  # failed those verbs in minimal envs). write --apply/--pr lands in place with no
217
266
  # throwaway worktree, so it needs no mktemp either.
218
267
  case "$VERB" in
219
- feedback | clean) require_tools git ;;
220
- monitor | guide | stop) : ;;
268
+ feedback | clean | plan) require_tools git ;;
269
+ monitor | guide | stop) : ;; # resume needs the engine path like write, so it is NOT listed here
221
270
  write)
222
271
  if [[ "$APPLY" -eq 1 || "$OPEN_PR" -eq 1 ]]; then
223
272
  require_tools git python3
@@ -245,7 +294,7 @@ REPO="$(cd "$REPO" && pwd)"
245
294
  # The pilot verbs (monitor/guide/stop) are pure .colleague/flight/ file I/O — they
246
295
  # need no git work tree, so they are exempt from this fail-fast guard.
247
296
  case "$VERB" in
248
- monitor | guide | stop) : ;;
297
+ monitor | guide | stop) : ;; # resume needs the engine path like write, so it is NOT listed here
249
298
  *)
250
299
  git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 \
251
300
  || { echo "error: --repo is not a git repository: $REPO" >&2; exit 1; }
@@ -265,10 +314,128 @@ resolve_colleague || exit 2
265
314
  # Per-request timeout is config (no drive flag); EngineConfig reads it from env.
266
315
  # A local model can be slow on a growing context, so default generously.
267
316
  export COLLEAGUE_TIMEOUT="$TIMEOUT"
268
- COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --max-steps "$MAX_STEPS" --json)
317
+
318
+ # ── thinking effort (#416): per-seat, resolved by colleague where each seat is
319
+ # built — the wrapper only sets the operator override env vars colleague reads
320
+ # (flag > env > config.json > table). Validated here so a typo fails fast
321
+ # (user-input error, #161) instead of as a refused run.
322
+ _EFFORT_RUNGS="off|low|medium|high|xhigh|default"
323
+ _check_rung() {
324
+ case "$1" in
325
+ off | low | medium | high | xhigh | default) : ;;
326
+ *) echo "error: unknown thinking effort '$1' (expected one of: ${_EFFORT_RUNGS//|/, })" >&2; exit 1 ;;
327
+ esac
328
+ }
329
+ if [[ -n "$EFFORT" ]]; then
330
+ _check_rung "$EFFORT"
331
+ export COLLEAGUE_CORTEX_REASONING_EFFORT="$EFFORT"
332
+ export COLLEAGUE_WORKER_REASONING_EFFORT="$EFFORT"
333
+ fi
334
+ if [[ -n "$SEAT_EFFORT" ]]; then
335
+ IFS=',' read -r -a _pairs <<< "$SEAT_EFFORT"
336
+ for _pair in "${_pairs[@]}"; do
337
+ _seat="${_pair%%=*}"; _rung="${_pair#*=}"
338
+ case "$_seat" in
339
+ cortex | worker | deepthink | senses | evaluator | design) : ;;
340
+ *) echo "error: unknown seat '$_seat' in --seat-effort (expected cortex|worker|deepthink|senses|evaluator|design)" >&2; exit 1 ;;
341
+ esac
342
+ _check_rung "$_rung"
343
+ export "COLLEAGUE_$(printf '%s' "$_seat" | tr '[:lower:]' '[:upper:]')_REASONING_EFFORT=$_rung"
344
+ done
345
+ fi
346
+
347
+ # ── plan: colleague does the planning (the inverse of /think) ────────────────
348
+ # plan delegates the WHOLE planning arc to colleague via the `colleague plan`
349
+ # verb — no throwaway worktree, no prompt template. Gated non-interactively
350
+ # (--yes) so a delegating agent can hand off the arc and read the
351
+ # spec -> plan -> workforce result. colleague is the planning mind; /think keeps
352
+ # Claude as the planner (same arc, a different mind).
353
+ if [[ "$VERB" == "plan" ]]; then
354
+ plan_flags=(--repo "$REPO" --engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --yes)
355
+ # --quick skips the spec stage (#199); --no-workforce delivers spec+plan only,
356
+ # skipping the timeout-prone fan-out (#215). --timeout already applies via the
357
+ # COLLEAGUE_TIMEOUT export above. These are forwarded, never auto-applied.
358
+ [[ "$QUICK" -eq 1 ]] && plan_flags+=(--quick)
359
+ [[ "$NO_WORKFORCE" -eq 1 ]] && plan_flags+=(--no-workforce)
360
+ [[ "${JSON_OUT:-0}" -eq 1 ]] && plan_flags+=(--json)
361
+ plan_rc=0
362
+ "${COLLEAGUE[@]}" plan run "$ARG" "${plan_flags[@]}" || plan_rc=$?
363
+ # No silent auto-degrade: name the recovery levers (those not already set) and
364
+ # let the caller choose. Human hints are TEXT-mode only — in --json mode the
365
+ # consumer is a machine and colleague's own structured {code,message,remediation}
366
+ # error already went to stderr, so suppress the prose there (Qodo #230 F4).
367
+ if [[ "$plan_rc" -ne 0 && "${JSON_OUT:-0}" -ne 1 ]]; then
368
+ echo "hint: plan mode did not complete on this backend. Recovery options (no auto-degrade):" >&2
369
+ [[ "$NO_WORKFORCE" -eq 0 ]] && echo "hint: --no-workforce deliver the spec+plan only, skip the timeout-prone workforce fan-out (#215)" >&2
370
+ [[ "$QUICK" -eq 0 ]] && echo "hint: --quick skip the spec stage, plan directly from the request (#199)" >&2
371
+ echo "hint: --timeout N widen the per-request window (currently ${TIMEOUT}s)" >&2
372
+ fi
373
+ exit "$plan_rc"
374
+ fi
375
+
376
+ # ── explore/review adopt colleague's native mode profile (t4/spec R1) ───────
377
+ # explore/review's step budget + synthesis-reserve tail used to be caller-side
378
+ # overrides baked into this script (a fixed --max-steps 30 plus an exported
379
+ # COLLEAGUE_SYNTHESIS_RESERVE_STEPS=3). Those numbers now live in colleague's
380
+ # own mode-profile catalog (colleague/profiles.py "explore"/"review") and are
381
+ # applied runtime-side by `colleague work --mode explore|review`
382
+ # (colleague/config.py apply_mode_profile) — so this wrapper's job shrinks to
383
+ # "select the mode" instead of re-deriving the profile's numbers itself; a
384
+ # future retune of the profile then reaches this wrapper for free.
385
+ #
386
+ # Honest limit: this wrapper prefers an installed `colleague` on PATH ahead of
387
+ # a local checkout, and an installed CLI can be older than the one this repo
388
+ # ships (it can predate --mode entirely). Detect that cheaply by checking the
389
+ # resolved CLI's OWN --help text for the literal flag with a plain bash
390
+ # substring match (no external text-search tool — the resolver stays as
391
+ # dependency-free as the uv-fallback path above, #190); fall back to the old
392
+ # caller-side defaults when --mode isn't there, so the wrapper keeps working
393
+ # unmodified against a stale CLI.
394
+ #
395
+ # NOTE: the substring must be anchored past the flag name — "--mode" is
396
+ # itself a literal prefix of "--model" (already a flag on every version), so
397
+ # a naive `*--mode*` match false-positives on a stale --help that only has
398
+ # --model. Requiring the next character NOT be a letter (or end-of-string)
399
+ # distinguishes a genuine "--mode ..."/"--mode]" occurrence from "--model".
400
+ MODE_SUPPORTED=0
401
+ if [[ "$VERB" == "explore" || "$VERB" == "review" ]]; then
402
+ help_out="$("${COLLEAGUE[@]}" work --help 2>/dev/null)" || help_out=""
403
+ if [[ "$help_out" == *"--mode"[!a-zA-Z]* || "$help_out" == *--mode ]]; then
404
+ MODE_SUPPORTED=1
405
+ fi
406
+ fi
407
+
408
+ if [[ ( "$VERB" == "explore" || "$VERB" == "review" ) && "$MODE_SUPPORTED" -eq 0 ]]; then
409
+ # Legacy fallback (stale CLI without --mode): reserve a few steps for the
410
+ # final verdict turn so a big-diff review/explore yields findings instead
411
+ # of dying mid-read (#197). Caller env wins.
412
+ : "${COLLEAGUE_SYNTHESIS_RESERVE_STEPS:=3}"
413
+ export COLLEAGUE_SYNTHESIS_RESERVE_STEPS
414
+ fi
415
+
416
+ COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --json)
417
+ # --max-steps: forwarded unless this is a native-mode-capable explore/review run
418
+ # with no explicit --max-steps — then the flag is withheld so colleague's own
419
+ # mode profile supplies the step budget as a DEFAULT at runtime. An explicit
420
+ # --max-steps always wins, in EITHER direction (lower or higher than the
421
+ # profile's own number): it is still forwarded here, and the runtime resolves
422
+ # an explicit flag ahead of any profile default (colleague/config.py
423
+ # apply_mode_profile's explicit-knobs precedence).
424
+ if [[ "$MAX_STEPS_EXPLICIT" -eq 1 || "$MODE_SUPPORTED" -eq 0 || ( "$VERB" != "explore" && "$VERB" != "review" ) ]]; then
425
+ COMMON_FLAGS+=(--max-steps "$MAX_STEPS")
426
+ fi
427
+ # --mode: select colleague's native explore/review constraint profile when the
428
+ # resolved CLI supports it (detected above). write carries no --mode — its
429
+ # profile is behavior-neutral (identical to no mode at all), so the numbers
430
+ # stay exactly as documented (--max-steps 20 default, no reserve override).
431
+ if [[ "$MODE_SUPPORTED" -eq 1 && ( "$VERB" == "explore" || "$VERB" == "review" ) ]]; then
432
+ COMMON_FLAGS+=(--mode "$VERB")
433
+ fi
269
434
  # --watch arms a flight for EVERY drive verb (explore/review/write), so it lives on
270
435
  # the shared flag list — not inside one verb's path — so monitor/guide/stop work.
271
436
  [[ "${WATCH:-0}" -eq 1 ]] && COMMON_FLAGS+=(--watch)
437
+ # --role forwards a typed-subagent role to the work item.
438
+ [[ -n "${ROLE:-}" ]] && COMMON_FLAGS+=(--role "$ROLE")
272
439
 
273
440
  # ── render an instruction from a prompt template ────────────────────────────
274
441
  render_prompt() {
@@ -305,20 +472,38 @@ print_result() {
305
472
  # empty (its artifact was discarded with the worktree, so it is not gradable).
306
473
  # $3 (optional): exit code from the colleague drive command, propagated to
307
474
  # 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 '
475
+ 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
476
  import sys, json, os
310
477
  raw = sys.stdin.read().strip()
311
478
  json_mode = os.environ.get("ASK_COLLEAGUE_JSON") == "1"
479
+ def _fail(code, message, remediation, detail=None):
480
+ # #226: in --json mode emit a structured {code, message, remediation} object
481
+ # on stderr (matching colleague CliError shape) so a machine consumer parses
482
+ # a failure the same way it parses success; in text mode keep the standard
483
+ # error:/hint: contract. stdout stays clean (no result) on every path.
484
+ if json_mode:
485
+ obj = {"code": code, "message": message, "remediation": remediation}
486
+ if detail is not None:
487
+ obj["detail"] = detail
488
+ sys.stderr.write(json.dumps(obj) + "\n")
489
+ else:
490
+ sys.stderr.write("error: " + message + "\n")
491
+ if detail is not None:
492
+ sys.stderr.write(detail + "\n")
493
+ if remediation:
494
+ sys.stderr.write("hint: " + remediation + "\n")
495
+ sys.exit(code)
312
496
  if not raw:
313
- sys.stderr.write("error: colleague produced no result on stdout (see diagnostics above)\n")
314
- sys.exit(2)
497
+ _fail(2, "colleague produced no result on stdout (see diagnostics above)",
498
+ "re-run; if it persists, check the backend with colleague doctor --probe")
315
499
  try:
316
500
  d = json.loads(raw)
317
501
  except Exception:
318
- sys.stderr.write("error: could not parse colleague --json output:\n")
319
- sys.stderr.write(raw[:2000] + "\n")
320
- sys.exit(2)
502
+ _fail(2, "could not parse colleague --json output",
503
+ "the backend may have emitted non-JSON; see the raw output and diagnostics above",
504
+ detail=raw[:2000])
321
505
  ok = d.get("status") == "ok"
506
+ inc = d.get("incompletion") if isinstance(d.get("incompletion"), dict) else None
322
507
  tid = d.get("task_id") or ""
323
508
  # Resolve the artifact path to the preserved copy when the drive ran in a
324
509
  # throwaway worktree (read-only verbs); the raw JSON points into the now-deleted
@@ -332,11 +517,37 @@ if ap and real_dir:
332
517
  # trailing off mid-task. Warn so the caller treats it as a partial, not a verdict.
333
518
  # The warning is a DIAGNOSTIC -> always stderr (never stdout), so both the digest
334
519
  # and --json keep a clean, machine-readable stdout (no single quotes in this body).
520
+ # Enriched partial warnings: include reached step count and a concrete larger
521
+ # --max-steps to retry with, so the hint is actionable (#194).
522
+ max_steps = int(os.environ.get("ASK_COLLEAGUE_MAX_STEPS", "20"))
523
+ stats = d.get("stats") or {}
524
+ model_turns = stats.get("model_turns")
525
+ step_count = stats.get("step_count")
335
526
  if d.get("stopped_without_finish"):
336
- print("warning: drive ended without calling finish — treat the summary as a", file=sys.stderr)
337
- print(" partial (the model stopped mid-task), not an authoritative result.", file=sys.stderr)
527
+ reached = model_turns if model_turns is not None else step_count
528
+ if reached is not None:
529
+ 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)
530
+ else:
531
+ print("warning: drive ended without calling finish — treat the summary as a", file=sys.stderr)
532
+ print(" partial (the model stopped mid-task), not an authoritative result.", file=sys.stderr)
338
533
  elif d.get("not_finished"):
339
- print("warning: drive ran out of steps without finishing — summary is partial.", file=sys.stderr)
534
+ reached = model_turns if model_turns is not None else step_count
535
+ if reached is not None:
536
+ 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)
537
+ else:
538
+ print("warning: drive ran out of steps without finishing — summary is partial.", file=sys.stderr)
539
+ # No-result sentinel: the drive produced nothing actionable (#192).
540
+ summary = d.get("summary") or ""
541
+ if summary == "__COLLEAGUE_NO_RESULT_PRODUCED__":
542
+ print("warning: no result produced — widen --max-steps or narrow the scope.", file=sys.stderr)
543
+ # Incompletion: when the run did NOT deliver (status != "ok") and colleague
544
+ # carried an incompletion record, surface it as a diagnostic to stderr so the
545
+ # delegating caller knows WHY the run failed and what to do next. Always stderr
546
+ # (never stdout) so --json stdout stays pure.
547
+ if not ok and inc:
548
+ reason = inc.get("reason", "")
549
+ recommendation = inc.get("recommendation", "")
550
+ print(f"incomplete: {reason} — {recommendation}", file=sys.stderr)
340
551
  if json_mode:
341
552
  # --json contract: stdout carries ONLY the TaskResult JSON; every
342
553
  # human/diagnostic line already went to stderr above. The exit code still
@@ -359,7 +570,10 @@ if json_mode:
359
570
  # task_id is already in the payload, but echoing the copy-paste grade hint
360
571
  # keeps the convention every work item follows (rule 907536) without breaking
361
572
  # the stdout contract. Gated on gradable, exactly like the digest below.
362
- if tid and gradable:
573
+ # Skip the grade hint for the no-result sentinel (#192). A FAILED/incomplete but
574
+ # gradable drive still gets the hint — a failure rated 1/5 is the ROI signal
575
+ # (#139); the incompletion diagnostic above already says it did not succeed.
576
+ if tid and gradable and summary != "__COLLEAGUE_NO_RESULT_PRODUCED__":
363
577
  print("task:", tid, file=sys.stderr)
364
578
  print("grade:", "ask-colleague feedback", tid, "--rating N", file=sys.stderr)
365
579
  else:
@@ -380,7 +594,10 @@ else:
380
594
  # drive (colleague writes an artifact on failure too, h5): a failure rated
381
595
  # 1/5 is exactly the ROI signal, so the hint must not be gated on `ok` (#139
382
596
  # qodo). It prints to `out` (stderr on failure), matching the failure digest.
383
- if tid and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1":
597
+ # Skip the grade footer for the no-result sentinel (#192): a drive that
598
+ # produced nothing is not worth grading.
599
+ summary = d.get("summary") or ""
600
+ if tid and os.environ.get("ASK_COLLEAGUE_GRADABLE") == "1" and summary != "__COLLEAGUE_NO_RESULT_PRODUCED__":
384
601
  print("grade:", "ask-colleague feedback", tid, "--rating N", file=out)
385
602
  if ok:
386
603
  sys.exit(0)
@@ -474,7 +691,7 @@ _preserve_artifact() {
474
691
  local art_name="$1"
475
692
  [[ -n "$art_name" && -n "$_WT" ]] || return 1
476
693
  if ! _valid_segment "$art_name"; then
477
- printf 'ask-colleague: refusing to preserve unsafe artifact name %q\n' "$art_name" >&2
694
+ printf 'error: refusing to preserve unsafe artifact name %q\n' "$art_name" >&2
478
695
  return 1
479
696
  fi
480
697
  local src="$_WT/.colleague"
@@ -484,7 +701,7 @@ _preserve_artifact() {
484
701
  # The JSON artifact is the record of the drive — surface a copy failure rather
485
702
  # than swallow it, so the caller can fall back to honest path reporting.
486
703
  if ! cp -f "$src/$art_name" "$dst/$art_name"; then
487
- printf 'ask-colleague: could not preserve artifact %s\n' "$art_name" >&2
704
+ printf 'error: could not preserve artifact %s\n' "$art_name" >&2
488
705
  return 1
489
706
  fi
490
707
  # The trace shares the artifact stem (.json -> .trace.jsonl); a best-effort
@@ -508,7 +725,7 @@ run_readonly() {
508
725
  local instruction="$1"
509
726
  _add_worktree
510
727
  local out rc=0
511
- out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
728
+ out="$("${COLLEAGUE[@]}" work "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
512
729
  _DRIVE_BRANCH="$(printf '%s' "$out" | _extract_branch)"
513
730
  # Preserve the artifact to the real repo BEFORE the EXIT trap removes the
514
731
  # worktree, so the drive can be graded by its task-id (`ask-colleague feedback
@@ -532,7 +749,7 @@ run_preview() {
532
749
  local instruction="$1"
533
750
  _add_worktree
534
751
  local out rc=0
535
- out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
752
+ out="$("${COLLEAGUE[@]}" work "$instruction" --repo "$_WT" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
536
753
  _DRIVE_BRANCH="$(printf '%s' "$out" | _extract_branch)"
537
754
 
538
755
  # Capture the would-be patch before _cleanup_worktree deletes the drive branch.
@@ -568,9 +785,9 @@ run_write() {
568
785
  return
569
786
  fi
570
787
  if [[ "$ALLOW_DIRTY" -eq 0 ]] \
571
- && [[ -n "$(git -C "$REPO" status --porcelain 2>/dev/null)" ]]; then
788
+ && [[ -n "$(git -C "$REPO" status --porcelain --untracked-files=no 2>/dev/null)" ]]; then
572
789
  echo "error: working tree is dirty — commit/stash first, or pass --allow-dirty" >&2
573
- echo "hint: 'colleague drive --no-pr' commits uncommitted edits onto the drive branch" >&2
790
+ echo "hint: 'colleague work --no-pr' commits uncommitted edits onto the work branch" >&2
574
791
  # User-fixable state guard -> exit 1, matching the runtime's own
575
792
  # dirty-tree guard (colleague/handoff.py _guard_clean_tree =
576
793
  # EXIT_USER_ERROR), not exit 2 (#161).
@@ -591,9 +808,9 @@ run_write() {
591
808
  # read-only / preview paths, which guard this way.
592
809
  local out rc=0
593
810
  if [[ "$OPEN_PR" -eq 1 ]]; then
594
- out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$REPO" "${COMMON_FLAGS[@]}")" || rc=$?
811
+ out="$("${COLLEAGUE[@]}" work "$instruction" --repo "$REPO" "${COMMON_FLAGS[@]}")" || rc=$?
595
812
  else
596
- out="$("${COLLEAGUE[@]}" drive "$instruction" --repo "$REPO" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
813
+ out="$("${COLLEAGUE[@]}" work "$instruction" --repo "$REPO" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
597
814
  fi
598
815
  # A landed write persists its artifact in the real repo and moves `last`, so it
599
816
  # is gradable — print the `grade:` hint (with the explicit task-id).
@@ -641,13 +858,61 @@ run_clean() {
641
858
  "${cmd[@]}"
642
859
  }
643
860
 
861
+ # ── front-load a filtered, capped diff into the review instruction ──────────
862
+ # Issue #220a: print a diffstat + filtered diff body so the model does not waste
863
+ # turns running `git diff` itself. Lockfile/vendored noise is excluded; the body
864
+ # is capped at ${COLLEAGUE_MAX_OUTPUT_CHARS:-100000} characters.
865
+ front_load_review_diff() {
866
+ local cap="${COLLEAGUE_MAX_OUTPUT_CHARS:-100000}"
867
+ printf '%s\n' "--- DIFF UNDER REVIEW (filtered + capped; read specific files for more) ---"
868
+ git -C "$REPO" diff "$BASE"...HEAD --stat
869
+ printf '\n'
870
+ local diff_body
871
+ # #324: bound the buffering itself, not just the printed output — `head -c`
872
+ # caps what the substitution ever holds (cap+1 bytes, just enough to detect
873
+ # truncation) and SIGPIPEs git early on a pathological diff, instead of
874
+ # materializing the full diff in memory before the length check. `|| true`
875
+ # also absorbs the SIGPIPE exit under `set -o pipefail`.
876
+ 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 | head -c "$((cap + 1))" || true)"
877
+ if [[ "${#diff_body}" -gt "$cap" ]]; then
878
+ printf '%s\n' "${diff_body:0:$cap}"
879
+ printf '%s\n' "[... diff body truncated at ${cap} chars; read specific files for the rest ...]"
880
+ else
881
+ printf '%s\n' "$diff_body"
882
+ fi
883
+ }
884
+
644
885
  # ── piloting verbs: thin passthroughs to the `colleague flight` noun ─────────
645
886
  _flight_json_flag() { [[ "$JSON_OUT" -eq 1 ]] && printf -- '--json'; }
646
887
 
888
+ # ── resume verb: pick a cut run back up from its artifact ───────────────────
889
+ # `colleague work --continue <id|last>` resumes a timed-out / SIGTERM'd /
890
+ # budget-exhausted run from its persisted artifact (continuation lineage lands on
891
+ # TaskResult.continued_from). The wrapper adds nothing but the shared flags and
892
+ # the effort/role env already exported above. `--detach` runs it under
893
+ # setsid/nohup — NOT `colleague --background`, which drops the continue id
894
+ # (colleague#418) — and returns at once; the log names the new flight id.
895
+ run_resume() {
896
+ local fid="${ARG%% *}"
897
+ [[ -z "$fid" ]] && { echo "error: resume needs a task-id (or 'last')" >&2; exit 1; }
898
+ if [[ "$DETACH" -eq 1 ]]; then
899
+ local log="$REPO/.colleague/resume-$fid.log"
900
+ mkdir -p "$REPO/.colleague"
901
+ setsid nohup "${COLLEAGUE[@]}" work --continue "$fid" --repo "$REPO" --no-pr "${COMMON_FLAGS[@]}" \
902
+ > "$log" 2>&1 < /dev/null &
903
+ echo "resume: detached (pid $!) — log: $log" >&2
904
+ echo "hint: the new flight id appears in the log as 'flight: <id>'; then ask-colleague monitor <id>" >&2
905
+ return 0
906
+ fi
907
+ local out rc=0
908
+ out="$("${COLLEAGUE[@]}" work --continue "$fid" --repo "$REPO" --no-pr "${COMMON_FLAGS[@]}")" || rc=$?
909
+ printf '%s' "$out" | print_result "" "1" "$rc"
910
+ }
911
+
647
912
  run_monitor() {
648
913
  local fid="${ARG%% *}"
649
914
  [[ -z "$fid" ]] && { echo "error: monitor needs a flight task-id" >&2; exit 1; }
650
- "${COLLEAGUE[@]}" flight status "$fid" --repo "$REPO" $(_flight_json_flag)
915
+ "${COLLEAGUE[@]}" flight status "$fid" --repo "$REPO" --follow $(_flight_json_flag)
651
916
  }
652
917
 
653
918
  run_stop() {
@@ -665,11 +930,12 @@ run_guide() {
665
930
 
666
931
  case "$VERB" in
667
932
  explore) run_readonly "$(render_prompt explore)" ;;
668
- review) run_readonly "$(render_prompt review)" ;;
933
+ review) run_readonly "$(render_prompt review)"$'\n\n'"$(front_load_review_diff)" ;;
669
934
  write) run_write "$(render_prompt write)" ;;
670
935
  feedback) run_feedback "$ARG" ;;
671
936
  clean) run_clean ;;
672
937
  monitor) run_monitor ;;
673
938
  guide) run_guide ;;
674
939
  stop) run_stop ;;
940
+ resume) run_resume ;;
675
941
  esac
@@ -5,6 +5,31 @@ 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.7.0] - 2026-08-24
9
+
10
+ ### Added
11
+
12
+ - **`ask-colleague resume <task-id|last> [--detach]`** — pick a cut /
13
+ timed-out / SIGTERM'd run back up from its persisted artifact, continuing
14
+ on the original `colleague/<id>` work branch. `--detach` runs it under
15
+ `setsid`/`nohup` and returns at once.
16
+ - **Per-seat thinking effort** for `ask-colleague` (colleague#416) —
17
+ `--effort RUNG` (acting seat), `--seat-effort S=R` (any seat),
18
+ `--role NAME`. `off` for small well-specified briefs, `xhigh` for
19
+ open-ended judgement, `default` as the kill-switch.
20
+
21
+ ### Changed
22
+
23
+ - **`ask-colleague` re-vendored byte-verbatim from `agentculture/colleague`
24
+ @ 1.63.0** — all five files (`SKILL.md`, `scripts/ask-colleague.sh`,
25
+ `prompts/{explore,review,write}.md`) match
26
+ `diff -r ../colleague/.claude/skills/ask-colleague`.
27
+ - **Default colleague model is now `unsloth/Qwen3.8-27B-NVFP4`** (was the
28
+ Qwen3.6 pin) — the lobes gateway on `:8001` no longer serves 3.6, so the
29
+ old default only worked via colleague's auto-refresh warning path.
30
+ - `ask-colleague review` front-loads a filtered, capped diff into the review
31
+ instruction so the model does not spend turns running `git diff` itself.
32
+
8
33
  ## [0.6.1] - 2026-07-20
9
34
 
10
35
  ### Added
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: workledger-cli
3
- Version: 0.6.1
3
+ Version: 0.7.0
4
4
  Summary: Tracks work in a ledger so agents can prove what they did. An agent records its work as append-only ledger entries, hands the ledger down to subagents, and gets it back with their entries folded in, so the whole tree of work and its results stays traceable and checkable.
5
5
  Project-URL: Homepage, https://github.com/agentculture/workledger-cli
6
6
  Project-URL: Issues, https://github.com/agentculture/workledger-cli/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "workledger-cli"
3
- version = "0.6.1"
3
+ version = "0.7.0"
4
4
  description = "Tracks work in a ledger so agents can prove what they did. An agent records its work as append-only ledger entries, hands the ledger down to subagents, and gets it back with their entries folded in, so the whole tree of work and its results stays traceable and checkable."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -154,37 +154,6 @@ wheels = [
154
154
  { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" },
155
155
  ]
156
156
 
157
- [[package]]
158
- name = "workledger-cli"
159
- version = "0.6.1"
160
- source = { editable = "." }
161
-
162
- [package.dev-dependencies]
163
- dev = [
164
- { name = "bandit" },
165
- { name = "black" },
166
- { name = "flake8" },
167
- { name = "isort" },
168
- { name = "pytest" },
169
- { name = "pytest-cov" },
170
- { name = "pytest-xdist" },
171
- { name = "teken" },
172
- ]
173
-
174
- [package.metadata]
175
-
176
- [package.metadata.requires-dev]
177
- dev = [
178
- { name = "bandit", specifier = ">=1.7.5" },
179
- { name = "black", specifier = ">=23.7.0" },
180
- { name = "flake8", specifier = ">=6.1" },
181
- { name = "isort", specifier = ">=5.12.0" },
182
- { name = "pytest", specifier = ">=8.0" },
183
- { name = "pytest-cov", specifier = ">=4.1" },
184
- { name = "pytest-xdist", specifier = ">=3.0" },
185
- { name = "teken", specifier = ">=0.8" },
186
- ]
187
-
188
157
  [[package]]
189
158
  name = "execnet"
190
159
  version = "2.1.2"
@@ -476,3 +445,34 @@ sdist = { url = "https://files.pythonhosted.org/packages/25/95/3c3eff8e550c1347c
476
445
  wheels = [
477
446
  { url = "https://files.pythonhosted.org/packages/00/2c/ca005d32831d883b10215a1ea31c32197a3a410f6d4ea3516b3d3c6a1183/teken-0.8.0-py3-none-any.whl", hash = "sha256:385a529df5a76adfff56ba5ae191a1bf8a5573f3698da4cc5bf7a2b5ca7cc311", size = 73189, upload-time = "2026-05-22T19:47:53.959Z" },
478
447
  ]
448
+
449
+ [[package]]
450
+ name = "workledger-cli"
451
+ version = "0.7.0"
452
+ source = { editable = "." }
453
+
454
+ [package.dev-dependencies]
455
+ dev = [
456
+ { name = "bandit" },
457
+ { name = "black" },
458
+ { name = "flake8" },
459
+ { name = "isort" },
460
+ { name = "pytest" },
461
+ { name = "pytest-cov" },
462
+ { name = "pytest-xdist" },
463
+ { name = "teken" },
464
+ ]
465
+
466
+ [package.metadata]
467
+
468
+ [package.metadata.requires-dev]
469
+ dev = [
470
+ { name = "bandit", specifier = ">=1.7.5" },
471
+ { name = "black", specifier = ">=23.7.0" },
472
+ { name = "flake8", specifier = ">=6.1" },
473
+ { name = "isort", specifier = ">=5.12.0" },
474
+ { name = "pytest", specifier = ">=8.0" },
475
+ { name = "pytest-cov", specifier = ">=4.1" },
476
+ { name = "pytest-xdist", specifier = ">=3.0" },
477
+ { name = "teken", specifier = ">=0.8" },
478
+ ]
File without changes
File without changes
File without changes
File without changes