Draft: chore(skills): check in the workstream orchestration skill set
Why
The workstream conductor loop (run-workstream), its per-step build pipeline (ship-mr), and the review-triage methodology (triage-mr) ran as personal skills on one machine through the S17 waves. Checking them in makes the workflow reviewable, versioned with the repo, and runnable by anyone, instead of depending on one person's home directory. The dev-rig commands they validate on (ar-start-dev, ar-redeploy-dev, ar-smoke-test, ar-validate-branch) move in with them for the same reason, and a workstream-conductor agent definition pins the conductor's model and effort so the launch command needs no flags. docs/dev/workstream-orchestration.md is the operator-facing guide to all of it: what each entry point does, what runs without asking, what waits, and what the permission grants admit.
Three behavior changes ride the port:
- The mr-watch sweep gains an opt-in all-substantive admission mode (
MR_WATCH_ADMIT_UNPREFIXED, default off, fails closed) so workstream-dispatched watchers act on ordinary unprefixed reviewer comments. A standalone watch behaves as it does today, pinned by assertions on the off state's output rather than by an equality. A near-miss guard keeps a mis-formatted prefix (_escalate_:,**escalate**:,escalate :, a quoted or list-marked one) out of the committing branch. It does not catch a misspelling, and declining to guess at one is deliberate: a false positive there drops a reviewer's comment for good, which nothing downstream recovers. - Database review evidence gets a deterministic trigger: the
db_review_evidencedanger rule warns on every pipeline when a DB-touching diff has no## Database Review Evidencesection, andneeds-db-review.shgives scripts the same one-token verdict. Checking this by hand was the step that kept getting skipped. - The MR watcher drops from opus to sonnet. This applies to every
/mr-watch, not only conductor-dispatched ones.
The security-relevant part of the diff is .claude/settings.json: it now carries a permissions.allow list, a permissions.deny list, and a subagent-cap env var. Every allow entry pre-approves one tool pattern for every Claude session in this repo, after the operator accepts the workspace-trust dialog that displays the list. Review those entries as grants, and read the deny list as a guard against an accidental destructive command rather than a boundary: rules match command prefixes literally, so glab api:* reaches any REST endpoint the token can, including the writes the porcelain entries deny and the values of CI/CD variables, and no GitLab grant is scoped to this project. Four grants run arbitrary code by design. The setup section of the guide names all of it.
The rereview and bot-reviews references live under triage-mr/references/ rather than in the glab skill, because that skill is vendored in from gitlab-org/ai/skills and a version sync would clobber additions. Fixes to the glab skill itself belong upstream of that relay, in the monolith, which is its source of truth.
Size
7,119 added lines across 46 files. By file group: the seven offline test suites (about 2,210 lines), skill, reference, and agent markdown (about 2,790 across 14 documents), the skills' shell and Python scripts (about 1,500), the new operator guide and the docs it cross-links (about 330), the CI job, pre-commit hooks, and shared suite runner (about 100), settings (about 95), and the danger rule with the root Dangerfile (about 95). The suites are the largest group because the review round that produced this MR's last commits added 56 cases, several of them pinning behaviour a mutation showed was untested. Splitting would separate the skills from the scripts, tests, and CI gates that cite them, leaving dangling paths in every part until the last one merged.
Costs
Everything here runs on API tokens. The spend concentrates in a few deliberate places, and the structure keeps the rest cheap:
- The conductor runs Fable at
effort: max(pinned in the agent definition, rationale in the file). It holds the cross-MR picture, and a wrong call there costs teammate-hours and reviewer round-trips, worth more than tokens. Its volume is capped by design: inline work is single-call reads plus synthesis, and every multi-step task goes to a cheaper teammate. - Model tiers step down with the work. Teammate dispatches default to opus. Builder launches name
--model opus(ship-mr's frontmatter pin is turn-scoped, so the dispatch names it for the whole session). The graph teammate and the MR watcher run sonnet. Only the conductor runs Fable. - Waiting never burns model turns. watch-loop.sh owns the interval sleeps, and the pipeline and bot waits poll GitLab from bash (60 s default) as backgrounded scripts. A quiet watched MR costs almost nothing: the sweep is a Python script whose compact facts block is its only stdout, and the watcher model wakes just to re-arm the loop's call budget until a tick reports
VERDICT: ACTION. - Cheap gates precede expensive dispatches. needs-db-review.sh answers
not-needed,present, orabsentbefore any db-review-prep teammate spawns. The Duo-landed check before a Draft flip is one jq call. - Two review passes per built MR are the deliberate trade. Agent tokens buy fewer reviewer rounds, the workstream's actual bottleneck. review-branch self-tiers: solo below 300 reviewable LOC, subagent groups above.
- The all-substantive watch mode replies to every note it admits, even a bare "LGTM!". The reply is the dedupe marker: without it the sweep re-dispatches the note on every remaining tick, which costs more than a one-line ack. The mode is opt-in per dispatch, and standalone /mr-watch stays prefix-only.
- Full-body dumps beat truncation. glab-mr-bot-reviews and print-discussions print complete bodies because truncated ones cut off suggestion blocks and force a second fetch. The bot dump filters to the two pinned bot authors. The discussion dump drops only system events and labels the rest, because
resolvableis false for every plain MR comment, which is the shape a reviewer's whole review summary arrives in. - The subagent cap (40) is width, not spend. The operative bound is the pacing rule (at most 3 dispatches per block, gated on handbacks), and the lifecycle contract stops retired teammates so idle agents do not pile up context.
- Nothing re-litigates across sessions. The modes file and ledger carry intake answers and promises, persistent teammates resume by name instead of respawning, and references load lazily at need. The largest load, mr-watch's 99 KB SKILL.md, is ≈ $0.08 loaded once per sonnet watcher.
Test plan
sh scripts/skill-scripts-test.shruns the seven offline suites green, 245 tests total: triage_sweep 95, needs-db-review 38, reply-batch 37, run-workstream scripts 27, bot-wait 23, watch-loop 13, discussion-id 13. The same command backs theskill-script-testspre-commit hook and thelint:skill-scriptsCI job, both of which now also fire onrun-workstream/scripts/changes. The new assertions were checked by mutation rather than assumed: reverting the reply-batch body comparison to a substring match, the evidence heading's multiline flag, thearrayspage filter, the IID range expansion, and the AppSec grace guard each fail only the tests written for them. Three assertions that passed against broken code were rewritten. The suites also run against busybox applets, which is what the alpine CI job gives them.sh scripts/danger-rules-test.shruns all suites green, including the 33db_review_evidencecases. Thelint:danger-rulesCI job runs the same suite on this MR, sincedanger/**changed.- shellcheck,
bash -n, andpy_compileare clean on every script (pre-commit's shellcheck excludes.claude/skills/, so this ran by hand). Pre-commit hooks (markdownlint, lychee, vale, editorconfig, gitleaks) passed on every commit. - The skills are prose contracts, so the checks are referential: every cross-cited path resolves, and no home-directory or personal reference survives (grepped clean).