Repository navigation
feat(review): integrate OAuth, presets and verified agent handoffs - #37
Conversation
…_json The file transcription path (review, transcribe, every chunk of a long recording) hardcoded response_format=verbose_json. OpenAI's gpt-transcribe / gpt-4o-transcribe family answers HTTP 400 (param=response_format, code=unsupported_value), which killed a 12-minute review at chunk 1/32 with "Speech-to-text failed (HTTP 400)". - retry that one request with json when the 400 names response_format (OpenAI envelope or flattened message); any other 400 still raises - remember the refusal per (endpoint, model) for the process, so a chunked run pays the rejected request once, not once per chunk - json carries no segments: the existing synthetic-segment path marks the timeline as synthetic; whisper-family models keep verbose_json and real segment timing - RED-first tests: fallback, memory per endpoint+model, unrelated 400 not swallowed, flat error body, verbose_json kept when accepted - live check: 20 s clip through `screenscribe transcribe` on a gpt-transcribe config delivered a transcript after one rejected request Gates: ruff, mypy, bandit clean; unit suite 1268 passed, 6 review-app DOM tests fail in this checkout for an environment reason (they pass on a clean worktree with this diff applied). Authored-By: claude <agents@vetcoders.io> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…clients, gpt-transcribe model table
- providers/xai.py: POST /v1/stt (words → real segments), POST /v1/tts, GET /v1/tts/voices (docs.x.ai 2026-09-10)
- stt_stream.py: StreamingSttClient over websockets; LibraxisStreamClient (session.start, u64-be seq + PCM16LE, flush/end) and XaiStreamClient (interim_results, Finalize/audio.done)
- transcribe.py: gpt-transcribe*/gpt-4o-transcribe* request json directly; whisper keeps verbose_json + 400 fallback; api.x.ai host routes to providers.xai (incl. chunked)
- config.py: provider_preset("xai"), tts_*/stt_live_endpoint fields + SCREENSCRIBE_* env, validate() knows api.x.ai; account-bearer hooks (Lane B) share this file
- cli.py: screenscribe tts, screenscribe transcribe --live, setup option xAI; auth subcommand mount (Lane B)
- 52 new unit tests, no network (MockTransport, loopback websockets.serve)
Authored-By: claude <agents@vetcoders.io>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…I (Codex flow) and xAI (RFC 8628) - account_auth/: provider registry, PKCE S256, device_code flows ported from codescribe core/llm/account_auth - token store ~/.config/screenscribe/accounts.json (0700/0600, atomic), refresh, account_status - resolve_bearer: explicit key wins; xai account token usable (api:access); openai token identity-only with warning (api.openai.com rejects ChatGPT tokens, codescribe 2026-09-08) - CLI: screenscribe auth login|status|logout - NOTICE discloses shipped public client ids - 29 unit tests against httpx.MockTransport Authored-By: claude <agents@vetcoders.io> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pec, take Founder's untracked docs
- docs/plans/PLAN_SS_PROVIDERS_OAUTH_TAFLA_20260910.md with Lane A/B reports
- docs/plans/SS_TAFLA_WEBKIT_HOST_SPEC.md: port spec from dragon vibecrafted-app @449af692
- AGENTS.md, .ai-agents/, docs/plans/* (portal plans 2026-06-05) were untracked in the Founder's tree; taken as-is (trailing whitespace fixed by hook)
- tests/package.json {"type":"commonjs"}: review DOM harness no longer breaks under a parent ESM package.json
Authored-By: claude <agents@vetcoders.io>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ders-oauth-tafla-20260910
- Upgrade locked dependencies, including mypy 2.3.1 and Rich 15.0.0.\n- Align development version metadata and changelog at 0.1.20.dev0.\n- Format the transcription example for the updated Ruff checks.\n- Validated with uv sync --dev, make verify, uv lock --check and uv pip check. Authored-By: junie <agents@vetcoders.io> Co-authored-by: Junie <junie@jetbrains.com>
…t pass the pre-flight key gate - Why: `screenscribe auth login xai` + xai preset with no key reports READY in `config --show`, but `review` died at "Validating configuration... API Key Error: No API key configured" — validate_models() checked the raw api_key/stt/llm/vision fields instead of the per-endpoint accessors that already fall back to the account bearer (`_key_or_account_bearer`). - What: the presence gate resolves get_stt/llm/vision_api_key(); openai stays identity-only because that accessor returns "" for api.openai.com. The error text now also points at `screenscribe auth login xai`. - Proof: new regression test (xai preset, empty key, stored account token → validate_models passes and sends `Bearer at-xai`); tests/test_validation.py 28 passed, contracts + review-core 93 passed; ruff check/format clean. Authored-By: claude <agents@vetcoders.io>
- Why: founder could only push/pop/clear; drawn rects also drifted after layout change. - What: stable annotation ids, select/move/resize/delete, toolbar color/stroke/fontSize, live overlay resync via getActualImageRect. - Proof: tests/test_annotation_model.py (ids, hit-test, move/resize, delete/properties, overlay-after-scroll). Authored-By: cursor <agents@vetcoders.io>
- Why: new select/delete/property labels must go through i18n; demo HTML is generated, not hand-edited. - What: lightbox anchors in the i18n guard, regenerate site/demo/report.html from render_html_report_pro, append Unreleased changelog. - Proof: test_i18n_no_hardcoded_dom.py; demo contains data-tool=select and overlayLayoutFromImage. Authored-By: cursor <agents@vetcoders.io>
…R [w1-02-transcript-sources] - why: review died at _require_audio_or_exit before the model saw anything, so --prompt never reached the model for silent recordings (founder case: deaf operator, explanations written on screen) - what: transcript becomes an abstraction over segment sources — new transcript_sources.py (protocol + auto/audio/ocr routing, --no-audio alias, registry hook for future .srt source) and frame_ocr.py (frames every --frame-interval s via the shared ffmpeg helper, consecutive-dedupe on an md5 probe of downscaled gray frames, VLM-OCR through the Responses API reusing unified.wire payload mechanics, per-frame-content cache); segments come out in the exact STT shape (start/end/text) so semantic prefilter, chaining, screenshots and reports run unchanged - cli.review: --transcript-source/--no-audio/--frame-interval flags; the audio gate moved UNDER the per-video source decision (explicit 'audio' keeps the readable fail-fast error); STT provider check/model validation only for audio-routed videos, vision also when OCR is used - review_pipeline: per-video source resolution, OCR branch for steps 1-2 (audio extraction skipped, failure panel mirrors STT path, checkpoint semantics preserved); audio-quality validation and the STT timeline-coverage guard now run for the audio source only - proof: pytest tests/test_transcript_sources.py tests/test_frame_ocr.py green; full suite 1513 passed, 28 skipped; ruff clean Authored-By: kimi <agents@vetcoders.io> (cherry picked from commit f1c5532d44c9bb92f31a55514b12ba6f960b6601)
…t-sources] - tests/test_transcript_sources.py: normalize/resolve unit coverage, OCR routing with mocked VLM (no 'has no audio track', pipeline reaches semantic on OCR segments), explicit audio keeps the readable error, --prompt reaches OCR + semantic configs, --frame-interval plumbing, flag conflict/invalid rejection, --help lists the three flags - tests/test_frame_ocr.py: interval grid extraction, consecutive dedupe, request shape (Responses payload with input_image), --prompt override in the OCR prompt, cache hit avoids transport, segment shape with frame-grid timestamps clamped to duration, empty-OCR frames skipped - tests/test_review_empty_state.py: legacy no-audio gate test now pins the explicit --transcript-source audio path (auto routes to OCR instead) - docs: USAGE.md review options table + transcript-source section + updated no-audio troubleshooting; README.md key options; CHANGELOG.md Unreleased - proof: 23 new tests green; full suite 1513 passed, 28 skipped (baseline 1490); ruff check + format clean Authored-By: kimi <agents@vetcoders.io> (cherry picked from commit 5be1649438c30d0442c37f82d757b7f6b9dacd89)
…set-aware keyword loading Why: Founder asked for --preset (programming, casual, medical, veterinary, custom) so the same engine serves clinical/veterinary recordings without new pipeline code. What: - new screenscribe/presets package: Preset dataclass, load_preset, load_custom_preset, PresetError; YAML definitions for programming (historical default, no own dictionary), casual, medical, veterinary (finding/observation/risk/followup/other + PL/EN dictionaries, >=15 phrases) - KeywordsConfig gains extra + active_categories; load priority is now --keywords-file > global file > preset dictionary > built-in default - default load without a preset stays bit-for-bit identical (programming preset keeps default_keywords.yaml as the single dictionary source) Proof: tests/test_presets.py registry + priority tests; full suite green. Authored-By: kimi <agents@vetcoders.io> (cherry picked from commit ad841af6798ef3bb8a2776a5b08fbc8bc507de2d)
…through review pipeline
Why: preset categories must reach detection, the JSON report, and the HTML
report instead of the hard-coded six; the preset prompt fragment rides the
existing --prompt channel.
What:
- review --preset flag (programming/casual/medical/veterinary/custom) in one
compact block in cli.py: resolves the preset, enforces custom requires
--keywords-file with a readable error, merges the preset prompt fragment
into analysis_prompt_override, passes the preset to run_review
- semantic_prefilter/_parse_prefilter_response/pois_to_detections accept an
active category vocabulary; the prefilter prompt category enum is swapped
for non-default presets; unknown categories still degrade to other
- run_review threads preset_categories into detection and records
preset {name, categories} in the enhanced JSON report for non-default
presets only (default report shape unchanged); dry-run counts list preset
categories when a preset vocabulary is active
Proof: full suite 1509 passed; HTML renderer falls back to raw upper-cased
category names so preset badges render without UI changes.
Authored-By: kimi <agents@vetcoders.io>
(cherry picked from commit 75ab8f736d36b3c1539850af5d26b9865034a0c0)
Why: acceptance requires verifiers for the --preset surface and honest docs for the new flag. What: - tests/test_presets.py (19 tests): preset registry, medical/veterinary dictionaries (>=15 phrases, PL+EN), custom from --keywords-file (categories from file keys, readable errors), keyword priority with presets, bit-for-bit default guarantee, category flow into POIs/detections/JSON/HTML, CLI --help and error-path contract - USAGE.md: --preset row in the review options table, a Presets subsection under Detection, a preset example - README.md: --preset in the review key options - CHANGELOG.md: Unreleased entry for review --preset Proof: tests/test_presets.py 19 passed; targeted gate (test_presets + test_cli_keywords_group + test_keywords_bootstrap) green; ruff check and format clean. Authored-By: kimi <agents@vetcoders.io> (cherry picked from commit c0bc92bab0ab93759ec0de81f897963bfec0b5aa)
…urces Why: the w1-03-presets cut was built before w1-02-transcript-sources landed; both touched the same review options block and the run_review signature, so the second cherry-pick (75ab8f7) conflicted in cli.py (1 hunk) and review_pipeline.py (2 hunks). What: resolved every conflict as a union so both features work in one 'review' invocation: - cli.py: run_review now receives transcript_source + frame_interval AND preset=active_preset. - review_pipeline.py: run_review signature takes transcript_source, frame_interval and preset; docstring documents both contracts. - CHANGELOG.md/USAGE.md: union, HEAD entry first, preset entry after. Added a combined contract test: --preset veterinary --no-audio passes CLI validation with mocked OCR/LLM and delivers both transcript_source="ocr" (frame_interval=5.0) and the veterinary preset to run_review, with the preset recorded in the JSON report. Proof: pytest tests/ -m 'not integration': 1538 passed, 0 failed; ruff check + ruff format --check clean; review --help shows --transcript-source, --no-audio, --frame-interval and --preset. Authored-By: kimi <agents@vetcoders.io>
- Why: continue a review conversation with Responses API chaining and tools. - What: screenscribe/agent — provider routing, SSE contract, tools, JSON seed. - Proof: donor medical loop rewritten (~400 lines) to the same contracts; tests in the next commit. Authored-By: grok <agents@vetcoders.io> (cherry picked from commit 7856f72d6932b095d8c93fcd56473bbc4b05eb47)
- Why: acceptance requires TestClient + fixture verifiers, not only live curl. - What: tests for SSE contract, chain/seed, egress, tools on the 08.22.27 fixture. - Proof: uv run pytest tests/test_agent_chat.py tests/test_agent_tools.py tests/test_review_server_agent.py → 28 passed. Authored-By: grok <agents@vetcoders.io> (cherry picked from commit 064c7ca2426e6b991101388e5155dee3c4cfa941)
…esponse_id - Why: UI (w1-05) binds against POST /api/agent/chat(/stream); next review needs the last VLM response_id. - What: review_server routes, analysis_passes.unified_analysis.response_id, SCREENSCRIBE_AGENT_EGRESS. - Proof: full suite 1520 passed, 28 skipped; live curl on 08.22.27 returned findings + done. Authored-By: grok <agents@vetcoders.io> (cherry picked from commit beb13b3655f0655d9a5450119d7cd2982f650af0)
…onal anthropic extra - Why: screen recordings are secrets; operators need the deny/allow knob documented. - What: USAGE + CHANGELOG agent section, optional anthropic extra, lockfile. - Proof: ruff check/format and bandit -q -r screenscribe -c pyproject.toml exit 0. Authored-By: grok <agents@vetcoders.io> (cherry picked from commit 7d98b68e12d2e0b59b6491092c765668bf7ea38f)
- Ship agent_panel.js/css as a separate report surface so W1-01 can keep review_app.js. - Load the panel only on the review shell (script + style loaders) against the w1-04 SSE contract. - Prove toggle/persist, SSE tools, offline file mode, and geometry in node vm tests. Authored-By: cursor <agents@vetcoders.io> (cherry picked from commit 5598bd15edc7e35a80008707809adcbec045da21)
- Keep panel copy in renderer.py + i18n.js with en/pl parity (offline message names screenscribe serve). - Expose window.__screenscribeAgentHost so tool_call seek/show_frame jump the player and highlight a finding. - Extend i18n/token/SRI guards to the new JS/CSS surface. Authored-By: cursor <agents@vetcoders.io> (cherry picked from commit b408fd63be8b536f1376c968fb3eaa6c0ef3d365)
- Require ScreenScribeAgentPanel in the review shell script order. - Record the floating chat in the unreleased changelog. - Regenerate site/demo/report.html from the product renderer so the sample ships the 『s』 chip. Authored-By: cursor <agents@vetcoders.io> (cherry picked from commit 677e361e88713dda36002f4b3861e1de40f13553)
… player Why: Enter in the textarea inserted a newline, restored positions floated over the video, and error turns left an empty assistant bubble. What: Enter submits (Shift+Enter keeps a newline, IME composition does not send); overlapping restored coords are discarded and the panel is recomputed beside the player or docked as a side sheet; error text paints the assistant row. Proof: tests/test_agent_panel.py 12 passed; full suite 1580 passed / 28 skipped; ruff clean; Playwright Chromium screenshots at 1280x800, 1440x900, 1920x1080 with no player/toolbar overlap. Authored-By: cursor <agents@vetcoders.io>
… the recording - Why: default SCREENSCRIBE_AGENT_EGRESS=deny marked api.x.ai as external and skipped the same host that already received STT audio, transcript, and VLM frames. Chat then died with "All agent providers were skipped". - What: infer trust=processor for any provider host matching stt_endpoint, llm_endpoint, or vision_endpoint, and always keep processor under deny. Explicit SCREENSCRIBE_AGENT_PRIMARY_TRUST still wins (external + deny remains the opt-out). Fallback on a foreign host (Anthropic) stays skipped. Docs/CHANGELOG follow. - Proof: tests/test_agent_chat.py + tests/test_review_server_agent.py cover default xAI kept, foreign fallback skipped, explicit external skipped with a readable message, and /api/agent/chat/stream on default config streams token events. Full suite 1572 passed, 28 skipped, 23 deselected; ruff check/format and bandit clean. Walk-around: 08.22.27 report in-process, egress=deny, primary trust=processor, urllib POST Host 127.0.0.1, events token=1 done=1, first 300 chars of a real xAI answer. Authored-By: grok <agents@vetcoders.io> (cherry picked from commit 04a20c0)
…verity, edit_finding, add_finding, propose_review)
- Why: Founder asked to talk to the review agent about findings and have it apply corrections. Chat was read-only (list_findings / get_transcript / seek / show_frame / get_report_summary).
- What: Write tools validate against the loaded report and return a review_patch (or a review_plan from propose_review). They never write report.json — the browser stays the single writer. merge_findings / unmerge_finding return {"unsupported": true} because merge is a client-only fold. System prompt: small edits go straight to a patch; broad requests call propose_review first; never claim an edit is saved. review_finding_state hydrates additive summary_override / category_override.
- Proof: tests/test_agent_review_patch.py covers validation, schemas, SSE tool_result round-trip with mocked provider, and empty API key + stored xAI account bearer. Full suite 1590 passed, 28 skipped, 23 deselected; ruff check/format and bandit clean.
Authored-By: grok <agents@vetcoders.io>
(cherry picked from commit 5d7df95)
…state hydrate test - Why: W2-02 codes against the patch contract; operators need the write-tool surface documented; additive summary_override / category_override must not break old reports. - What: USAGE + CHANGELOG note that write tools return a patch (browser saves). tests/test_review_server.py hydrates both new override fields and an older finding without them. - Proof: test_review_state_exposes_summary_and_category_overrides; full suite 1590 passed, 28 skipped. Authored-By: grok <agents@vetcoders.io> (cherry picked from commit aee35aa)
…from the agent - Why: Founder wants the 『s』 panel to apply the agent's finding edits, but the browser stays the single writer — same Zapisz recenzję path as a human click. - What: review_app.js exposes applyReviewPatch + saveReview on __screenscribeAgentHost (reuse verdict/severity/notes/manual-mark setters; merge ops stay unsupported). agent_panel.js applies review_patch, renders review_plan checkboxes with Apply/Cancel, blocks file://, shows Retry on 409, and the confirmation line points at Reset review instead of a second undo stack. i18n PL+EN, CHANGELOG, demo regen. - Proof: tests/test_agent_panel.py + test_f0_js_runtime_smoke.py cover ops, plan card, offline, and 409 Retry; full suite 1609 passed, 28 skipped, 23 deselected; ruff check/format clean. Walk-around: 08.22.27 report, live xAI «zmień finding 3 na high i dopisz notatkę» → severity medium→high, notes saved, reload persisted. Authored-By: cursor <agents@vetcoders.io>
Why: Founder measurement (2026-09-14, api.libraxis.com/v1/responses, 7 paired prompts) shows reasoning.effort="none" is accepted (HTTP 200, response.completed, no reasoning events) while "off" ends as response.failed. Today config validation rejects "none" and silently falls back to "medium" — exactly the reasoning the user asked to disable. What: add "none" to LLM_REASONING_EFFORTS (default stays "medium"; "off"/other invalid values still warn and fall back). Every reader of get_llm_reasoning_effort() passes the value verbatim, so it reaches the wire as reasoning.effort="none" through both build paths (api_utils build_llm_request_body, unified wire _build_unified_payload). Docs (USAGE.md, README.md, config setup template comment) list the full set; CHANGELOG entry appended. Proof: pytest tests/ -q -m 'not integration' -> 1613 passed, 28 skipped (baseline 1609 + 4 new: env none loads without warning, env off warns and falls back, effort none on both wire paths). ruff check + ruff format --check + bandit clean. Authored-By: kimi <agents@vetcoders.io> (cherry picked from commit 91fe8e04f4dad5e4c173935ed37048268f10b85b)
There was a problem hiding this comment.
An organization admin can view or raise the monthly spending cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, reopen this pull request to trigger a review.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🟢 Approval recommended
Only a minor changelog consistency nit remains; no blocking issues were identified.
Pull request overview
Adds none as a valid LLM reasoning-effort setting while preserving the medium default and fallback behavior.
Changes:
- Updates configuration validation and templates.
- Documents the new option.
- Adds environment and Responses payload coverage.
File summaries
| File | Summary |
|---|---|
USAGE.md |
Updates configuration documentation. |
tests/test_unified_cross_provider_chaining.py |
Tests unified payload propagation. |
tests/test_config_env.py |
Tests valid and invalid environment values. |
tests/test_api_utils.py |
Verifies none reaches Responses payloads. |
screenscribe/config.py |
Accepts none and updates setup output. |
README.md |
Updates environment documentation. |
CHANGELOG.md |
Records the new option; an older allowed-values reference needs alignment. |
Review details
Suppressed comments (1)
CHANGELOG.md:9
- This entry adds
noneto the setting, but the earlier unreleased changelog entry atCHANGELOG.md:190still describes the allowed values as onlyminimal|low|medium|high. Please update that reference as well so the changelog does not contradict the newly documented configuration contract.
- **Added: `SCREENSCRIBE_LLM_REASONING_EFFORT=none`.** `none` is now a legal
reasoning-effort value alongside `minimal|low|medium|high`; it turns
reasoning off on providers that support it (OpenAI Responses, LibraxisAI)
and is sent to the wire verbatim as `reasoning.effort = "none"`. `medium`
stays the default, and truly invalid values (e.g. `off`) still warn and
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…drop minimal, add xhigh/max Why: live probes (2026-09-16) showed no provider supports "minimal" — OpenAI gpt-5.6-terra rejects it with 400 unsupported_value, xAI grok-4.6 silently aliases it to "low". It only ever lived two days on the stack base (c3b6174), never tagged, never on main, so there is no one to deprecate for: remove it outright. Meanwhile main has shipped a hard-coded reasoning.effort="none" since 68f8f85, and xAI rejects "none" with a 400 — so the unset default must be resolved per provider preset instead of one global "medium". What: - LLM_REASONING_EFFORTS is now ("none", "low", "medium", "high", "xhigh", "max"); "minimal" is invalid like any other unknown value (warn + fall back), no deprecation shim. - New PROVIDER_DEFAULT_REASONING_EFFORTS: xai -> "low" (xAI 400s on "none"), libraxis/openai/custom -> "none" (restores main's shipped behavior for those providers). get_llm_reasoning_effort() and the _normalize_reasoning_effort warning/fallback resolve the default via recognized_provider() instead of blindly "medium"; DEFAULT_LLM_REASONING_EFFORT stays as the last-resort constant. - The llm_reasoning_effort field defaults to "" (unset) so an explicit value always wins and an unset one resolves per provider; save_default_config() (the config setup path) writes the resolved provider default into config.env explicitly, so a fresh setup never starts with a value its provider rejects. - Docs (USAGE.md, README.md, setup template comment) and CHANGELOG.md updated; tests updated and extended (per-preset defaults, invalid-value provider fallback, explicit "medium" unchanged, xhigh/max on the wire). Proof: uv run pytest tests/ -q -m 'not integration' -> 1628 passed, 28 skipped (baseline on branch: 1613 passed); ruff check, ruff format --check and bandit -r screenscribe all clean. Authored-By: kimi <agents@vetcoders.io> (cherry picked from commit f08385acaac55f63b256ef540bdf9add42a900ca)
Integrates the existing OAuth, annotation, transcript-source, preset and agent stack into
main, with the review fixes needed to use narrated walkthroughs as coding-agent handoffs.The exported TODO/ZIP preserves the complete timestamped source, later scope limits, reviewer decisions and notes alongside model proposals. Human edits and annotations survive reload; manual captures retain their images across server restart and portable exports. xAI account setup works without a pasted key, auxiliary credentials follow their actual destination, and repeated agent/tool turns keep trusted instructions through stateless history.
Unexpected provider/tool exceptions stay in server logs rather than crossing the SSE, model-tool or non-streaming HTTP boundary; explicit configuration and input-validation messages remain readable.
The stack from #32–36 is retained: #32/#33/#34/#36 are ancestors, while the remaining #35 commits have matching cherry-pick patches. #29/#30/#31 were integrated separately with their own required checks. The earlier feature PRs can be closed as superseded after this integration lands.
The development lockfile also updates PyJWT, urllib3 and virtualenv to patched releases. Semgrep 1.179.0 is aligned between the development floor and repository hook; the local ruleset and fail-closed gates are retained.
Validation:
make verifyon Sztudio: lint, format, mypy, security/secrets, coverage floor, package build and isolated-wheel rendering.