Release 2.1.11 (2026-06-06)¶
Bug fixes¶
- adopt canonical quote-strip and command-position matcher (#1453) Replace the lookbehind anchor with the canonical command-position anchor and upgrade the quote stripper to the escape-aware single alternation, per the command-matcher quoting design spec (vergil-claude-plugin docs/specs/2026-06-05-450-command-matcher-quoting-design.md, sections 2 and 4). This fixes false denies on path-prefixed hits such as 'find . -path ./.git -prune'.
The bash -c recheck is now confined to the quoted argument extracted from the raw command (spec section 4.3) instead of rechecking the whole raw text.
Tests encode the spec section 6.1 table verbatim, including the vrg-hook-guard-only rows 11-12. Shapes the old lookbehind caught but the canonical anchor does not (env-assignment prefixes, env/command wrappers, backtick substitution) are documented as accepted false negatives per spec section 2.4.
Refs vergil-project/vergil-tooling#1443, vergil-claude-plugin#450.
- guard merged-branch sweep against in-flight worktrees (#1455) The ancestry sweep treated any branch whose tip is an ancestor of the target as merged debris, which matches a branch freshly created from the target tip — the starting state of every new issue worktree. A concurrently running finalize could therefore delete an in-flight agent's branch and remove its worktree before the first commit (observed twice on 2026-06-05 during work on #1439).
Two guards now gate the sweep, ahead of both the branch deletion and the worktree removal:
- Skip zero-commit branches: a tip equal to the target's carries no merged work, so deleting it saves nothing and only races branch creation. Decided locally, before any gh lookup.
- Require merge evidence: only sweep branches whose head has a closed or merged PR (new github.closed_pr_for_branch helper). Ancestry alone cannot distinguish a merged branch from one created off an older target tip.
The recency-window guard proposed in the issue is omitted: a branch in its creation-to-first-commit window has no closed PR for its head, so the merge-evidence guard already protects it regardless of age or tip position, without a time heuristic.
The explicit-target cleanup of the just-merged PR branch keeps its current behavior, per the issue.
- validate template linkage against ALLOWED_LINKAGES (#1460) Template mode bypassed the ALLOWED_LINKAGES check, so a forbidden auto-close linkage (Closes/Fixes/Resolves) in .vergil/pr-template.yml flowed straight into the PR body and was only caught post-submission by the security/standards CI gate.
Move ALLOWED_LINKAGES to lib/linkage.py as the shared source of truth and enforce it at every layer:
- pr_template.read_template() and write_template() raise TemplateError with remediation text on a forbidden linkage, so both producer and consumer fail loudly at the earliest point.
- _run_template_mode() reports TemplateError with the template path and re-checks the linkage value used to build the PR body.
-
Document in CLAUDE.md's PR-handoff section that linkage must be Ref.
-
cycle the VM after create/rebuild so sessions see provisioned groups (#1465) Lima establishes its multiplexed SSH ControlMaster as soon as the guest's sshd answers, which is before cloud-init group provisioning (usermod -aG) has run. sshd resolves supplementary groups once, at session establishment, so every subsequent 'limactl shell' multiplexes over that stale session and never sees groups added during provisioning. The vergil-vm#137 libvirt/kvm fix was therefore silently ineffective in exactly the scenario it targets: the first sessions after a rebuild.
Add a final 'cycle-ssh' stage to the create and rebuild pipelines that stops and restarts the VM, dropping the stale master so the first real session re-establishes against the fully provisioned guest. The second boot re-runs no provisioning and is fast. A full power cycle is used rather than dropping the control socket because the latter depends on Lima-version-specific hostagent behavior.
-
unblock close-finalize by making the release cleanup path non-interactive (#1466) vrg-release deadlocked in close-finalize: the orchestrator invoked vrg-finalize-pr with no argument and capture_output=True while stdin stayed on the TTY. The no-arg path is now interactive, so its prompt was written into the captured pipe and blocked forever on an invisible read (issue #1448).
-
Add --cleanup-only to vrg-finalize-pr: skip PR inference and merge entirely, never prompt or read stdin; mutually exclusive with a PR argument.
- Invoke vrg-finalize-pr --cleanup-only from close_and_finalize with stdout inherited (finalize output streams live instead of buffering through the minute-long validation), stdin closed, and stderr captured for ReleaseError.detail and replayed so warnings are not swallowed.
- Harden worktrees.require_tty to require both stdin and stdout TTYs: a captured stdout renders prompts invisible, which is the same prompting-into-a-void failure the guard exists to prevent.
- Update the vrg_finalize_pr module docstring and reference doc, which still described no-arg mode as non-interactive.
Features¶
- stage-aware progress framework for long-running CLI commands (#1456)
- build(deps): add rich as first external dependency
Required by the progress framework TTY renderer (spec: docs/specs/2026-06-05-progress-framework-design.md). Refs #1419
- feat(progress): add core stage types, detection, and formatting
Stage/StageResult/PipelineError dataclasses, renderer auto-detection (TTY->rich, GITHUB_ACTIONS->gha, else plain), and elapsed/total formatters. Refs #1419
- feat(progress): add RunLog with ANSI stripping and prune-on-start
Timestamped .vergil/
- feat(progress): add summary builder and plain/GHA renderers
Shared plain-text final summary; PlainRenderer flat output; GhaRenderer ::group:: sections with ::error annotations on failed stages. Refs #1419
- feat(progress): add RichRenderer rolling-window live display
rich Live display: completed stages collapse to colored status lines, active stage shows spinner plus last-N window; window=0 streams then collapses. Refs #1419
- feat(progress): add session routing, print capture, and streaming subprocess runner
emit() routes lines to the active renderer and log (plain print outside pipelines); _EmitWriter captures redirected stdout/stderr; progress.run() streams child output line-by-line and raises CalledProcessError carrying captured output. Refs #1419
- feat(progress): add run_pipeline and add_progress_args
Pipeline runner with warn/fail_defer/fail_fast modes, skip-flag escape hatches, stdout/stderr capture during stages, KeyboardInterrupt summary with exit 130, and auto-generated CLI flags. Refs #1419
- fix(output): re-point is_ci at GITHUB_ACTIONS env
Detection ownership moves to lib/progress.py. Fixes piped-local and agent-run output receiving GHA ::error:: annotations meant for the Actions log parser. Ecosystem-resolve CI-mode tests now patch lib.output.is_ci directly. Refs #1419
- refactor(git): stream git-command output via progress.run
lib/git.py run() delegates to the progress framework's streaming runner: lines reach the live renderer and run log as they arrive, and CalledProcessError still carries captured output. Refs #1419
- refactor(release): stream CI pollers with retry and drop --verbose plumbing
wait_for_checks/watch_workflow stream through the progress framework via _stream_with_retry, retaining transient-failure retry and _gh_env credential injection; the verbose flag chain (CLI flag, context field, preflight/merge/confirm params) is removed — subsumed by the rolling window and always-on run log. Refs #1419
- refactor(release): extract repo-config audit into a standalone stage function
The audit becomes its own fail_fast pipeline stage with skip_flag=skip_audit; preflight no longer takes skip_audit. check_gh_auth and audit_repo_config go public for stage use. Refs #1419
- refactor(release): replace bespoke phase runner with declarative stage list
build_stages() expresses the release pipeline as progress.Stage entries; tracking-issue comments stay command-local via the _tracked wrapper; audit becomes a skippable fail_fast stage and the back half of the pipeline becomes fail_defer. run_release survives one more commit until vrg-release rewires to run_pipeline. Refs #1419
- feat(release): run vrg-release through the progress pipeline
vrg-release adopts run_pipeline as the reference implementation: stage-aware rendering, always-on .vergil run log, auto-generated --skip-audit. Removes --skip-cd-docs and the legacy run_release phase runner (fail_defer reporting supersedes both). Refs #1419
- feat(validate): run vrg-validate through the progress pipeline
Checks become declarative stages: install is fail_fast, language checks and custom validator are fail_defer so all failures are reported in one run instead of aborting at the first failing check kind. Refs #1419
-
chore(progress): validation fixes for progress framework rollout
-
port vrg-vm lifecycle commands to the progress framework (#1457)
- feat(lima): stream limactl start with in-guest provision monitor
start_vm streams limactl output through progress.run instead of capturing it, and a monitor thread tails the host-side serial logs (~/.lima/
- fix(progress): record SystemExit from a stage as a stage failure
lima helpers signal fatal errors with SystemExit; under a pipeline that must become a recorded, summarized stage failure rather than a process exit that bypasses the summary and log pointer. Refs #1454
- feat(vrg-vm): run create/rebuild/start through the progress pipeline
The three long-running lifecycle commands become declarative Stage lists executed by run_pipeline: stage-aware rendering, always-on .vergil run log (enclosing repo, else home), auto --output-window/--output-format flags. start's tooling update becomes a warn-mode stage, preserving try_update_tooling's warn-and-continue contract. Refs #1454
- chore(vrg-vm): lint and format fixes for the pipeline port
Import-block ordering, unused locals in _cmd_start, test formatting. Refs #1454
- chore(vrg-vm): re-trigger CI after PR body linkage fix
Empty commit: the PR body was corrected from an auto-close keyword to Ref #1454, but the standards check only re-runs on a synchronize event and a manual re-run would replay the stale event payload. Refs #1454, #1458
- auto-size the RichRenderer rolling window to terminal height (#1462) Default --output-window changes from a fixed 5 to auto: at each render the visible tail is computed from the live console height minus the completed-stage lines, the spinner line, and a 2-line margin, clamped to [3, 40]. Querying console.size per render makes the window resize-aware, and the tail deque buffers only up to the cap.
Explicit --output-window N keeps the fixed-window behavior and 0 keeps stream-then-collapse; auto applies only when the flag is omitted.
- restore a safe agent path to fix its own PR body (#1464)
- refactor(lib): extract PR body construction into lib/pr_body
Move _build_pr_body and _resolve_issue_ref out of vrg_submit_pr into a shared lib/pr_body module so every tool that writes a PR body builds it through the same template and validated fields. Groundwork for vrg-pr-fix-body (issue 1459).
- feat(vrg-pr-fix-body): add validated agent path to fix a PR body
New vrg-pr-fix-body command: regenerate a PR body from corrected fields through the shared validated builder (lib/pr_body), so the body can only be replaced by a compliant body. Denied for the audit identity; the user identity may only fix a PR whose head branch matches the session's current branch; humans are unscoped. After the edit the tool pushes an empty commit because pull_request workflows do not trigger on edited and a manual re-run replays the stale event payload (--no-retrigger opts out). Adds github.edit_pr_body, console-script registration, reference docs, and CLAUDE.md/tool-overview entries.