Metadata-Version: 2.4
Name: plaibook
Version: 0.1.25
Summary: Ansible-native AI code-review and bug-fix pipeline
Author-email: Adam Knochowski <aknochow@redhat.com>
License:                                  Apache License
                                   Version 2.0, January 2004
                                http://www.apache.org/licenses/
        
           TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
        
           1. Definitions.
        
              "License" shall mean the terms and conditions for use, reproduction,
              and distribution as defined by Sections 1 through 9 of this document.
        
              "Licensor" shall mean the copyright owner or entity authorized by
              the copyright owner that is granting the License.
        
              "Legal Entity" shall mean the union of the acting entity and all
              other entities that control, are controlled by, or are under common
              control with that entity. For the purposes of this definition,
              "control" means (i) the power, direct or indirect, to cause the
              direction or management of such entity, whether by contract or
              otherwise, or (ii) ownership of fifty percent (50%) or more of the
              outstanding shares, or (iii) beneficial ownership of such entity.
        
              "You" (or "Your") shall mean an individual or Legal Entity
              exercising permissions granted by this License.
        
              "Source" form shall mean the preferred form for making modifications,
              including but not limited to software source code, documentation
              source, and configuration files.
        
              "Object" form shall mean any form resulting from mechanical
              transformation or translation of a Source form, including but
              not limited to compiled object code, generated documentation,
              and conversions to other media types.
        
              "Work" shall mean the work of authorship, whether in Source or
              Object form, made available under the License, as indicated by a
              copyright notice that is included in or attached to the work
              (an example is provided in the Appendix below).
        
              "Derivative Works" shall mean any work, whether in Source or Object
              form, that is based on (or derived from) the Work and for which the
              editorial revisions, annotations, elaborations, or other modifications
              represent, as a whole, an original work of authorship. For the purposes
              of this License, Derivative Works shall not include works that remain
              separable from, or merely link (or bind by name) to the interfaces of,
              the Work and Derivative Works thereof.
        
              "Contribution" shall mean any work of authorship, including
              the original version of the Work and any modifications or additions
              to that Work or Derivative Works thereof, that is intentionally
              submitted to Licensor for inclusion in the Work by the copyright owner
              or by an individual or Legal Entity authorized to submit on behalf of
              the copyright owner. For the purposes of this definition, "submitted"
              means any form of electronic, verbal, or written communication sent
              to the Licensor or its representatives, including but not limited to
              communication on electronic mailing lists, source code control systems,
              and issue tracking systems that are managed by, or on behalf of, the
              Licensor for the purpose of discussing and improving the Work, but
              excluding communication that is conspicuously marked or otherwise
              designated in writing by the copyright owner as "Not a Contribution."
        
              "Contributor" shall mean Licensor and any individual or Legal Entity
              on behalf of whom a Contribution has been received by Licensor and
              subsequently incorporated within the Work.
        
           2. Grant of Copyright License. Subject to the terms and conditions of
              this License, each Contributor hereby grants to You a perpetual,
              worldwide, non-exclusive, no-charge, royalty-free, irrevocable
              copyright license to reproduce, prepare Derivative Works of,
              publicly display, publicly perform, sublicense, and distribute the
              Work and such Derivative Works in Source or Object form.
        
           3. Grant of Patent License. Subject to the terms and conditions of
              this License, each Contributor hereby grants to You a perpetual,
              worldwide, non-exclusive, no-charge, royalty-free, irrevocable
              (except as stated in this section) patent license to make, have made,
              use, offer to sell, sell, import, and otherwise transfer the Work,
              where such license applies only to those patent claims licensable
              by such Contributor that are necessarily infringed by their
              Contribution(s) alone or by combination of their Contribution(s)
              with the Work to which such Contribution(s) was submitted. If You
              institute patent litigation against any entity (including a
              cross-claim or counterclaim in a lawsuit) alleging that the Work
              or a Contribution incorporated within the Work constitutes direct
              or contributory patent infringement, then any patent licenses
              granted to You under this License for that Work shall terminate
              as of the date such litigation is filed.
        
           4. Redistribution. You may reproduce and distribute copies of the
              Work or Derivative Works thereof in any medium, with or without
              modifications, and in Source or Object form, provided that You
              meet the following conditions:
        
              (a) You must give any other recipients of the Work or
                  Derivative Works a copy of this License; and
        
              (b) You must cause any modified files to carry prominent notices
                  stating that You changed the files; and
        
              (c) You must retain, in the Source form of any Derivative Works
                  that You distribute, all copyright, patent, trademark, and
                  attribution notices from the Source form of the Work,
                  excluding those notices that do not pertain to any part of
                  the Derivative Works; and
        
              (d) If the Work includes a "NOTICE" text file as part of its
                  distribution, then any Derivative Works that You distribute must
                  include a readable copy of the attribution notices contained
                  within such NOTICE file, excluding those notices that do not
                  pertain to any part of the Derivative Works, in at least one
                  of the following places: within a NOTICE text file distributed
                  as part of the Derivative Works; within the Source form or
                  documentation, if provided along with the Derivative Works; or,
                  within a display generated by the Derivative Works, if and
                  wherever such third-party notices normally appear. The contents
                  of the NOTICE file are for informational purposes only and
                  do not modify the License. You may add Your own attribution
                  notices within Derivative Works that You distribute, alongside
                  or as an addendum to the NOTICE text from the Work, provided
                  that such additional attribution notices cannot be construed
                  as modifying the License.
        
              You may add Your own copyright statement to Your modifications and
              may provide additional or different license terms and conditions
              for use, reproduction, or distribution of Your modifications, or
              for any such Derivative Works as a whole, provided Your use,
              reproduction, and distribution of the Work otherwise complies with
              the conditions stated in this License.
        
           5. Submission of Contributions. Unless You explicitly state otherwise,
              any Contribution intentionally submitted for inclusion in the Work
              by You to the Licensor shall be under the terms and conditions of
              this License, without any additional terms or conditions.
              Notwithstanding the above, nothing herein shall supersede or modify
              the terms of any separate license agreement you may have executed
              with Licensor regarding such Contributions.
        
           6. Trademarks. This License does not grant permission to use the trade
              names, trademarks, service marks, or product names of the Licensor,
              except as required for reasonable and customary use in describing the
              origin of the Work and reproducing the content of the NOTICE file.
        
           7. Disclaimer of Warranty. Unless required by applicable law or
              agreed to in writing, Licensor provides the Work (and each
              Contributor provides its Contributions) on an "AS IS" BASIS,
              WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
              implied, including, without limitation, any warranties or conditions
              of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
              PARTICULAR PURPOSE. You are solely responsible for determining the
              appropriateness of using or redistributing the Work and assume any
              risks associated with Your exercise of permissions under this License.
        
           8. Limitation of Liability. In no event and under no legal theory,
              whether in tort (including negligence), contract, or otherwise,
              unless required by applicable law (such as deliberate and grossly
              negligent acts) or agreed to in writing, shall any Contributor be
              liable to You for damages, including any direct, indirect, special,
              incidental, or consequential damages of any character arising as a
              result of this License or out of the use or inability to use the
              Work (including but not limited to damages for loss of goodwill,
              work stoppage, computer failure or malfunction, or any and all
              other commercial damages or losses), even if such Contributor
              has been advised of the possibility of such damages.
        
           9. Accepting Warranty or Additional Liability. While redistributing
              the Work or Derivative Works thereof, You may choose to offer,
              and charge a fee for, acceptance of support, warranty, indemnity,
              or other liability obligations and/or rights consistent with this
              License. However, in accepting such obligations, You may act only
              on Your own behalf and on Your sole responsibility, not on behalf
              of any other Contributor, and only if You agree to indemnify,
              defend, and hold each Contributor harmless for any liability
              incurred by, or claims asserted against, such Contributor by reason
              of your accepting any such warranty or additional liability.
        
           END OF TERMS AND CONDITIONS
        
           APPENDIX: How to apply the Apache License to your work.
        
              To apply the Apache License to your work, attach the following
              boilerplate notice, with the fields enclosed by brackets "[]"
              replaced with your own identifying information. (Don't include
              the brackets!)  The text should be enclosed in the appropriate
              comment syntax for the file format. We also recommend that a
              file or class name and description of purpose be included on the
              same "printed page" as the copyright notice for easier
              identification within third-party archives.
        
           Copyright 2026 Adam Knochowski
        
           Licensed under the Apache License, Version 2.0 (the "License");
           you may not use this file except in compliance with the License.
           You may obtain a copy of the License at
        
               http://www.apache.org/licenses/LICENSE-2.0
        
           Unless required by applicable law or agreed to in writing, software
           distributed under the License is distributed on an "AS IS" BASIS,
           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           See the License for the specific language governing permissions and
           limitations under the License.
        
Project-URL: Homepage, https://github.com/aknochow/ansible-plaibook
Project-URL: Issues, https://github.com/aknochow/ansible-plaibook/issues
Project-URL: Source, https://github.com/aknochow/ansible-plaibook
Project-URL: Documentation, https://github.com/aknochow/ansible-plaibook/blob/main/docs/getting-started.md
Keywords: ansible,code-review,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ansible-core<2.17.0,>=2.16.19; python_version < "3.11"
Requires-Dist: ansible-core<2.20.0,>=2.16.0; python_version >= "3.11"
Requires-Dist: pyyaml>=6.0.0
Requires-Dist: jinja2>=3.1.0
Requires-Dist: cursor-sdk<2.0.0,>=1.0.31
Provides-Extra: dev
Requires-Dist: pytest<10.0.0,>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov<8.0.0,>=4.0.0; extra == "dev"
Requires-Dist: ruff>=0.9.0; extra == "dev"
Requires-Dist: ansible-lint>=24.0.0; extra == "dev"
Provides-Extra: cursor
Requires-Dist: cursor-sdk<2.0.0,>=1.0.31; extra == "cursor"
Dynamic: license-file

# ansible-plaibook

[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/aknochow/ansible-plaibook/badge)](https://scorecard.dev/viewer/?uri=github.com/aknochow/ansible-plaibook)

AI-powered code review, running as a deterministic, unattended Ansible
playbook, not a chat-based agent session. It ports the methodology of
an interactive `code-review` skill into pure Ansible: dispatch
independent LLM lenses over a diff, merge and score their findings with
plain deterministic math (never trusted self-report), and render a
report, the same review a human would give, produced the same way
every time, invocable from a terminal, a git hook, or an AAP job
template. Expect more playbooks here over time as other harness skills
get the same treatment.

Locally, **`pip install plaibook`** then **`plai review`**. The package
name is `plaibook` (not `plai`, taken on PyPI, and not
`ansible-plaibook`). `plaibook` and `plai` share one entry point. The
wheel vendors `review.yml`; first `plai review` installs Galaxy
collections into `~/.cache/ansible-plaibook/collections` (never
`~/.ansible`). No checkout, no `ansible-galaxy` against your home
tree. See [`plaibook/README.md`](plaibook/README.md). `plai review`
with no arguments reviews `HEAD` in the current directory;
`plai review org/repo/123` is the PR/MR form. `ansible-playbook
review.yml` remains the AAP path. Until this version is on PyPI, the
same wheel is `pip install .` from this checkout. Put that venv under
`$HOME`, not `/tmp` (macOS XProtect). Contributors still
`uv sync --extra dev` — see [CONTRIBUTING](CONTRIBUTING.md).

## Why Ansible?

The question everyone asks first. A few concrete reasons this isn't
just "a Python script that happens to be YAML":

- **It ships as an AAP Job Template for free.** This isn't
  hypothetical. `review.yml` already runs as a real job template
  against real MRs (see the "Running it in AAP" section below).
  Whatever RBAC, credential management, scheduling, and audit logging
  your team already runs its infra automation through, your AI code
  review runs through too. No bespoke service to stand up, no new
  auth model to build.
- **Every step is a named, inspectable, replayable task**, not opaque
  agent-framework internals. `ansible.posix.profile_tasks` timing is
  already wired in; every review's cost/token usage is tracked per
  agent call. When something looks wrong, you read the task list, not
  a stack trace from a framework you don't own.
- **Scores are never self-reported.** `merge.yml`/`verify.yml` recompute
  every score deterministically (0-100 scale) from the surviving
  findings list via a shared Python action plugin. A lens can talk
  itself out of a finding in prose, but that doesn't change the score
  unless the finding is actually dropped from the list. That's a
  discipline Ansible's plain, auditable task model makes natural; a more
  "clever" agent framework makes it easy to accidentally trust a
  model's self-assessment.
- **Sandboxing is a first-class primitive, not bolted on.** Every
  script-touching task takes its execution target from a
  `review_delegate_host` var. Sandbox lifecycle lives once in the
  top-level playbook, and the `review` role has zero knowledge OpenShell
  even exists. That's just `delegate_to`, not custom isolation plumbing.
- **Cost.** No persistent agent service, no idle infra. This runs
  on-demand and you pay for exactly the LLM calls a review actually
  makes. The pipeline reports token totals and an estimate for every run;
  the amount depends on provider, model, prompt-cache hits, and exploration
  depth.

## More reasons for Ansible

Beyond the review pipeline's own design, building this as a plain
Ansible playbook and role means the entire mature `ansible-*` tooling
ecosystem comes along for free. None of it is built or maintained by
this project, and all of it is already production-hardened at
enterprise scale elsewhere.

- **`ansible-lint`** enforces real code quality automatically, not by
  convention. This repo runs at the `production` profile, Ansible's
  strictest ("meets requirements for inclusion as validated or
  certified content"), wired into a pre-commit hook via
  `.ansible-lint`.
- **`ansible-builder`** turns `execution-environment.yml` into a
  runnable, portable container image with the right Python and
  collection dependencies baked in. No hand-maintained Dockerfile
  (see "Running it in AAP" below).
- **`ansible-galaxy`** handles collection dependency resolution and
  distribution. The provider collections this pipeline depends on
  install the same way any other Galaxy collection does, via
  `collections-requirements.yml`. No custom package manager to build.
- **`ansible-navigator`** runs and inspects playbooks against the exact
  execution environment `ansible-builder` produces, without a separate
  install-and-debug step. Compatible out of the box, since
  `execution-environment.yml` already exists.
- **AWX and Ansible Automation Platform** provide the actual production
  job runner: RBAC, credential injection, scheduling, and audit
  logging, all already covered above. `ansible-runner` is what AAP
  itself uses under the hood to execute each job.
- **`ansible-doc`** documents every module, plugin, and role variable
  this project ships, the same discoverability any other collection
  gets, with no separate docs generator to maintain.

## How `review.yml` works

One playbook, `review_type` selects the mode (`pr` | `branch` |
`commit`), one or more targets, five stages:

1. **Briefing** (`briefing.yml`), deterministic, zero-LLM: diffing,
   domain detection, prior-review lookup, and checklist execution, all
   real Ansible tasks and action plugins (no vendored harness scripts
   left in this stage as of the action-plugin migration).
2. **Lenses** (`lenses.yml`), two independent `aknochow.claude.message`
   calls dispatched in parallel: a Security lens and a
   Functionality+Quality lens. This is the only genuinely LLM-driven
   step in the base pipeline.
3. **Merge** (`merge.yml`), dedup findings across lenses, apply a
   self-refutation filter (models sometimes talk themselves out of a
   finding in prose without dropping it from the list, this backstops
   that), and compute scores/verdict deterministically. Any surviving
   Critical or Major finding forces `NEEDS_CHANGES`, full stop.
4. **Explore** (`explore.yml`), a bounded, tool-calling pass
   (`read_file`/`search`, read-only) that looks beyond the diff itself
   for related context before finalizing findings.
5. **Persist** (`persist.yml`), renders the human-readable report and
   writes a structured `summary.json`, plus a stable
   `~/.cache/ansible-plaibook/last_run.json` every run overwrites (so tooling
   always knows where to look, no timestamp-guessing).

```bash
plai review org/repo/123
plaibook review org/repo/123
plai review https://gitlab.example.com/org/repo/-/merge_requests/45
# AAP / EE / power-user path:
ansible-playbook review.yml -e review_targets_raw="org/repo/123"
```

Runs in an OpenShell sandbox by default (`use_sandbox: true`) since
briefing execution runs commands parsed out of source-branch-controlled
input. `post_results` defaults to `false`, reviewing is safe to
automate; posting back to a real PR/MR is a write to shared state and
needs explicit opt-in.

## `review_type: commit`: fast local checks

A lightweight mode for a tight feedback loop right after a local
commit: no sandbox by default, no exploration pass, no PR/MR API
calls, just the Security + Functionality/Quality lenses against your
working tree.

```bash
plai review --commit
plaibook review --commit
plai review --commit --sha abc1234 --repo /path/to/repo
```

Unlike `review_type: pr`/`branch` (which never fail the Ansible run
based on verdict, a `NEEDS_CHANGES` review is still a "successful"
run), `review_type: commit` **fails its exit code** when the verdict
is `NEEDS_CHANGES` with a real Critical/Major finding, built for
hooking into something like a Claude Code `PostToolUse` hook on `git
commit`, so a coding agent gets live regression feedback on every
commit it makes, not just at PR time.

This used to be a separate `commit_review.yml` playbook, duplicating a
fair amount of `review.yml`'s tail logic (cache-dir handling,
`last_run.json` writing, stats) and target-resolution logic. Both are
now unified: one playbook, `review_type` drives the mode-specific
defaults (sandboxing, exploration depth, fail-on-verdict behavior) via
plain variable defaults, not separate control flow.

## Architecture

One role, `review`, powers `review.yml`. It used to be two (`review` for
target resolution handing off via `include_role` to a separate
`code_review` engine), merged into one role once that split had proven
itself as a valid intermediate step, per the same "don't leave a
deliberate-but-temporary shape in place longer than it needs to be"
discipline this repo applies elsewhere. `roles/review/tasks/main.yml`
dispatches on `review_type` (`pr` | `branch` | `commit`) to
`resolve_target_{{ review_type }}.yml`, which resolves the target into
the output-variable contract the shared review engine expects
(`review_repo_path`, `review_org_repo`, `review_branch`,
`review_context`, `review_short_sha`, etc.), then calls the engine
itself (`run_review_engine.yml`: briefing, lens dispatch, merge/dedup,
explore, verify, persist). Target-resolution files and engine files
are distinguished by naming convention now that they're not separated
by a role boundary: `resolve_target_*.yml` contains ONLY logic
specific to that target type; `briefing.yml`/`lenses.yml`/`merge.yml`/
`explore.yml`/`verify.yml`/`persist.yml` are the shared engine, agnostic
to which `resolve_target_*.yml` produced its inputs.

- `review_type: pr` detects GitHub vs. GitLab from the target
  identifier, fetches PR/MR context, and resolves the head ref; it
  optionally posts results back via `post_review_comment.py` (gated behind
  `post_results: false` by default), this is the one target type with
  its own post-engine step, which is why each `resolve_target_*.yml`
  calls the engine itself rather than the top-level dispatcher calling
  it uniformly for all three.
- `review_type: commit` resolves/validates a local commit directly via
  git, no PR/MR API involved.
- `review_type: branch` resolves a whole local-path/GitHub/GitLab branch
  target (`plai review --branch ...`, or `-e review_type=branch
  -e branch_review_target=...` on the playbook path).

Domain-specific review steering (tech-stack-specific guidance blocks,
ported from harness's `domain_steering.md`) live as individual Jinja
templates under `roles/review/templates/domain_steering/`, one file
per domain, independently editable/reviewable.

Phase 2 (sandboxed) runs stand up an OpenShell sandbox for isolated
script execution and register it as a real Ansible inventory host over
SSH, delegating individual tasks to it as needed, while every
Claude/Gemini/OpenAI call still runs on the controller, never inside the
sandbox. See [`docs/sandbox-and-agent-safety.md`](docs/sandbox-and-agent-safety.md)
for the full pattern (and the community pattern we deliberately don't
use instead).

### Bug-fix pipeline: parked, not on `main` right now

A second tenant (`bug_context`/`bug_plan`/`bug_fix` roles +
`bug_pipeline.yml`) is deliberately parked on the `wip/bug-fix-pipeline`
branch while the review pipeline's foundation (this repo's actual star)
gets an independent verification stage and an evals suite first. It
comes back once that foundation is solid, review + fix together is
the next logical milestone.

### Verification auditor: in flight

An independent, adversarial re-check of Critical/Major findings
(read-only tool-calling agent, given only one finding at a time,
instructed to try to *refute* it) is under active development on
`feat/verification-auditor`, catches the class of false positive that
survives self-verification (a model confidently misremembering an SDK
signature or stdlib default) by actually reading the source it depends
on. Not yet merged.

## Running it in AAP

`execution-environment.yml` builds a purpose-built EE with both sibling
collections and their Python dependencies baked in, plus `gh`/`glab`
for PR/MR API access. This design has already run as a real AAP Job
Template against real MRs. The maintainer publishes a prebuilt image at
`quay.io/aknochow/plaibook-ee` (a CI pipeline to build and publish it
automatically is coming in a follow-up PR); to build and host your own
instead, tag and push to a registry you control:

```bash
ansible-builder build -t <your-registry>/plaibook-ee:latest -f execution-environment.yml
podman push <your-registry>/plaibook-ee:latest
```

Credentials (GitHub/GitLab tokens, OpenShell mTLS) are injected via
AAP credentials at job-run time, never baked into the image.

## Verified so far

- ✅ Full pipeline against a real local diff, unsandboxed, and
  identically delegated into a real OpenShell sandbox
- ✅ Looping over multiple targets in one run
- ✅ Real PR/MR review via `review` (`review_type: pr`) against real
  GitHub and GitLab targets
- ✅ Running as a real AAP Job Template against a real MR
- ⏳ `post_results: true` (posting back to a real PR/MR)
- 🚧 Verification auditor (`feat/verification-auditor`, not yet merged)
- 🚧 Bug-fix pipeline, parked on `wip/bug-fix-pipeline`, comes back
  once the review pipeline's foundation is fully solid

## Known gotchas (hit during development, worth knowing before debugging blind)

- **Vertex org policy**: `constraints/vertexai.allowedPartnerModelFeatures`
  can block `output_config` (native structured output) on some GCP
  projects while plain messages/forced-tool-use still work fine on the
  *same* project/model. This pipeline uses `tool_choice`-forced
  structured output specifically to sidestep that. See
  `roles/review/tasks/lenses.yml`.
- **Sandbox network policy is separate from provider attachment.**
  Don't assume a sandbox can reach an internal GitLab or an arbitrary
  API just because a provider is attached; check the gateway's network
  policy.
- **EE collection tarballs are pinned, not live.** `execution-environment.yml`
  bakes in `build/collections/*.tar.gz` snapshots of the sibling
  collections, not their live git state. Rebuild both
  (`ansible-galaxy collection build ~/code/ansible-openshell --output-path build/collections -f`,
  same for `ansible-claude`) and rebuild/push the EE whenever either
  collection changes, or AAP jobs will run against stale module code.
- **`sandbox_gateway` must be `host.openshell.internal`, not
  `127.0.0.1`/`localhost`, and that requires a manual `/etc/hosts`
  entry.** OpenShell's podman driver splits the gateway into two
  listeners: a primary (full API, including sandbox creation) and a
  restricted compute-driver-callback-only listener. On a dual-stack
  host they land on different addresses (primary on `[::1]`, callback
  on `127.0.0.1`), so `127.0.0.1`/`localhost` non-deterministically
  hits the wrong one and sandbox creation fails with
  `PERMISSION_DENIED: compute-driver callback listeners accept
  sandbox callback RPCs only`. `host.openshell.internal` is already
  one of the SANs on OpenShell's own generated gateway TLS cert and
  always resolves to the primary listener, but nothing makes it
  actually resolve unless you add it to `/etc/hosts` yourself:
  `echo "::1 host.openshell.internal" | sudo tee -a /etc/hosts`.
  Also: OpenShell 0.0.106's own documented default registration
  (`openshell gateway add https://[::1]:17670`, exactly what the
  Homebrew formula's install caveat recommends) fails outright with
  `invalid dns name`, a raw IPv6 literal isn't accepted as a TLS SNI
  name by their CLI, with or without `--gateway-insecure`. This is an
  upstream OpenShell bug, not something fixable from here; the
  `/etc/hosts` route above is the least-bad workaround until it's
  fixed upstream.
