diff --git a/AGENTS.md b/AGENTS.md index 52b64f639..32290dc69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ Supported tools that auto-discover this file: Cursor, Codex, Aider, Jules, Devin - Unofficial Python CLI for [Google Flow](https://labs.google/fx/tools/flow) — drives Veo (image-to-video, text-to-video) and Imagen (text-to-image) generations from the terminal by reverse-engineering Flow's private REST API at `aisandbox-pa.googleapis.com`. - Python 3.11+ · `uv`-managed · `hatchling` builds · Playwright Chromium transport · `pyright` strict · `ruff` · `pytest`. - Single-package modular monolith. Top-level modules under `src/gflow_cli/`: `api/`, `auth/`, `data/`, `mcp/`, `services/`, `tools/`, `ui/`, `worker/`, `browser_manager.py`, `cli.py`, `_cli_helpers.py`, `cli_character.py`, `cli_data.py`, `cli_image.py`, `cli_instructions.py`, `cli_models.py`, `cli_movie.py`, `cli_run.py`, `cli_scene.py`, `cli_tools.py`, `cli_video.py`, `chain.py`, `chain_manifest.py`, `composition.py`, `config.py`, `errors.py`, `exceptions.py`, `image_batch.py`, `manifest.py`, `movie_manifest.py`, `observability.py`, `paths.py`, `profile_store.py`. -- Command surface: `gflow auth`, `gflow image` (t2i/i2i/batch/upload/upscale), `gflow video` (t2v/i2v/r2v/batch/chain), `gflow character` (create/list/show/rm/voices — reusable project-scoped Flow Character entities), `gflow scene` (create/show — Add Clip / Scenes, with `create --output` for credit-free server-side extended video), `gflow instructions` (persistent Agent-Mode brief cards — add/list/enable/disable/rm/apply/toggle-mode, credits-free, `--project` required), `gflow movie` (run/template — multi-scene manifest pipeline), `gflow tools` (list/show/run — prompt-rewriting tools, also `--tool` on generation commands), `gflow data` (catalog queries), `gflow models`, `gflow run`, `gflow mcp` (run/setup — stdio MCP server), and `gflow serve` (HTTP/SSE). +- Command surface: `gflow auth`, `gflow image` (t2i/i2i/batch/upload/upscale), `gflow video` (t2v/i2v/r2v/chain — `batch` is a Phase B stub), `gflow character` (create/list/show/rm/voices — reusable project-scoped Flow Character entities), `gflow scene` (create/show — Add Clip / Scenes, with `create --output` for credit-free server-side extended video), `gflow instructions` (persistent Agent-Mode brief cards — add/list/enable/disable/rm/apply/toggle-mode, credits-free, `--project` required), `gflow movie` (run/template — multi-scene manifest pipeline), `gflow tools` (list/show/run — prompt-rewriting tools, also `--tool` on generation commands), `gflow data` (catalog queries), `gflow models`, `gflow run`, `gflow mcp` (run/setup — stdio MCP server), and `gflow serve` (HTTP/SSE). - Requires a Google AI Ultra or Pro subscription with Flow access. All generations bill against the user's own Google account. ## Headed-browser dependency (architectural reality) @@ -28,7 +28,7 @@ If you can help unblock a pure HTTP transport (especially for video generation, - `uv sync` then `uv run playwright install chromium`. No global Python install needed. - Copy `.env.template` to `.env.local`; never commit `.env.local`. It documents every env var. - Output goes to `./tmp/` for scripts/tests or `$GFLOW_CLI_OUTPUT_DIR` for CLI outputs (defaults to `./out/`). -- One-time auth: `gflow auth login --browser chrome`. The `--browser chrome` flag is mandatory; the CLI fails fast on other strategies. +- One-time auth: `gflow auth login --browser chrome` (recommended — a real-Chrome profile is what generation runs need; the default `--browser auto` can also pick the internal strategy, and generation later fails fast on profiles created with a non-chrome strategy). - Use `/gflow:status` to see the current task before starting work; `/gflow:known-issues` before touching auth or reCAPTCHA code paths. ## Testing instructions — The Impeccable Routine diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e4cbe2e8..806b0da44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.38.0] — 2026-07-17 + +### Added + +- **Robust agentic↔classic UI mode control** (#332): `--ui-mode` is now driven by a state-aware mode controller that reads Flow's Agent toggle (`aria-pressed`, locale-invariant) instead of icon heuristics — the "Tools" icon present in both modes could previously be misread as a forced-agentic cohort, causing spurious "not recoverable" aborts. `--ui-mode classic` now reliably reaches the classic editor: the controller closes the chat sidebar, toggles off only when actually in agent mode, and verifies the result. Live-verified with a full classic→agent→classic round-trip on real Flow. +- **i2i reference dedup via picker filename search** (#314): repeated local `--ref` images in `gflow image i2i` are attached by selecting the already-uploaded library tile (exact-filename picker search, project-scoped) instead of re-uploading — ending duplicate library pile-ups (the reported 8× `son.jpg`). Falls back to upload when the filename isn't found, and scrolls the virtualised picker grid so off-screen matches are still selected rather than silently re-uploaded (#335). R2V video refs keep upload-only behavior. +- **PR-Triage Autopilot (#238, #333)**: deterministic Stage 0 pre-filter and ephemeral Docker container sandbox executing the `/gflow:pr-council-review` skill against qualified external PRs hourly on the host VPS. Includes Telegram notifications and a persistent audit ledger. Implementation + fixture evals ship in this release; host-side deployment is staged separately. + +### Changed + +- **Cognitive-complexity refactor of 5 pure-logic functions** (#331): extracted cohesive private helpers to bring them under the Sonar S3776 threshold with zero behavior change (verified by types, scoped tests, and an adversarial behavior-diff review). The remaining S3776/S107 findings in essential-complexity live-automation code were accepted in SonarCloud with justification. + ## [0.37.0] — 2026-07-17 ### Fixed @@ -319,6 +331,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 mid-run edit to the home `.env` can no longer produce a task whose client config disagrees with the parameters the task derived from `get_settings()`. + ## [0.24.0] — 2026-07-01 ### Added @@ -2007,7 +2020,8 @@ shell-script template that branches on these codes. First skeleton. Not functional end-to-end yet. -[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.37.0...HEAD +[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.38.0...HEAD +[0.38.0]: https://github.com/ffroliva/gflow-cli/compare/v0.37.0...v0.38.0 [0.37.0]: https://github.com/ffroliva/gflow-cli/compare/v0.36.0...v0.37.0 [0.36.0]: https://github.com/ffroliva/gflow-cli/compare/v0.35.0...v0.36.0 [0.35.0]: https://github.com/ffroliva/gflow-cli/compare/v0.34.0...v0.35.0 diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 1f6eef1b1..08b0040cc 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -751,6 +751,21 @@ key — surfaces as a `RuntimeError` that `auth/cookies.py` normalizes to ## Resolved +### False "forced agentic — not recoverable" aborts from an icon-heuristic cohort probe + +- **Status:** Resolved · **Severity:** Was-Medium (spurious exit-25 aborts on profiles that could in fact reach classic mode) · **Was-affecting:** `--ui-mode classic` and classic-only operations through v0.37.0 · **Fixed in:** 0.38.0 · **Tracked:** [#299](https://github.com/ffroliva/gflow-cli/issues/299), [#332](https://github.com/ffroliva/gflow-cli/issues/332) + +The forced-agentic detection keyed on UI icons including `apps_spark_2` — which is +Flow's **Tools** button, present in BOTH cohorts — so a classic-capable profile could +be misclassified as an unrecoverable agentic cohort and abort with exit 25. Recovery +was a blind single click on the Agent pill with no state verification. v0.38.0 +replaces this with a state-aware mode controller (`mode_control.py`) that reads the +Agent toggle's `aria-pressed` attribute (false = classic media, true = agent — +locale-invariant), closes the expanded chat sidebar first, toggles off only when +actually in agent mode, and re-verifies. Live-verified with a full +classic→agent→classic round-trip and a real agentic→classic recovery in the v0.38.0 +release run ([evidence](docs/LIVE_VERIFICATION_v0.38.0.md)). + ### Agentic image generation could silently attribute a pre-existing project asset as the "generated" image - **Status:** Resolved · **Severity:** Was-High (wrong file downloaded and reported as success, silently) · **Was-affecting:** the agentic driver through v0.30.0 (and, for the pre-download guard below, every transport) · **Fixed in:** unreleased (0.31.0) · **Tracked:** [#281](https://github.com/ffroliva/gflow-cli/issues/281); related picker fix [#282](https://github.com/ffroliva/gflow-cli/issues/282) diff --git a/PLAN.md b/PLAN.md index 8bb109bcc..1b04982a0 100644 --- a/PLAN.md +++ b/PLAN.md @@ -449,6 +449,18 @@ Phase A (T2V library transport) shipped with v0.7.0 via PR #23. PR #36 (2026-05- --- +### PR-Triage Autopilot — COMPLETED + +Autonomous hourly code review helper running in a Docker sandbox. Design spec in [2026-07-04-pr-triage-autopilot-design.md](file:///C:/development/github/gflow-cli-pr-triage/docs/superpowers/specs/2026-07-04-pr-triage-autopilot-design.md). + +- [x] Task 1: Stage 0 deterministic pre-filter (`pr_triage_gate.py`) +- [x] Task 2: §9 Autonomous mode in `pr-council-review` skill +- [x] Task 3: Ephemeral Docker sandbox image (`Dockerfile.triage`) +- [x] Task 4: Main host orchestration script (`pr_triage_autopilot.py`) +- [x] Task 5: Operations runbook (`PR_TRIAGE_AUTOPILOT-OPS.md`) + +--- + ### Technical Debt & Refactoring — BACKLOG Identified during v0.6.0a1 Council Review. To be addressed before or during Phase 6. diff --git a/deploy/PR-TRIAGE-AUTOPILOT-OPS.md b/deploy/PR-TRIAGE-AUTOPILOT-OPS.md new file mode 100644 index 000000000..470cd721e --- /dev/null +++ b/deploy/PR-TRIAGE-AUTOPILOT-OPS.md @@ -0,0 +1,94 @@ +# PR-Triage Autopilot Operations Runbook + +This runbook guides the deployment, configuration, monitoring, and administration of the hourly PR-Triage Autopilot on the VPS host. + +--- + +## 1. Environment & Credentials + +The autopilot orchestrator (`scripts/autopilot/pr_triage_autopilot.py`) runs as an hourly cron job and requires the following environment variables: + +| Variable | Scope | Purpose | +|---|---|---| +| `ANTHROPIC_API_KEY` | Autopilot orchestrator & sandbox | Mints the Claude agent tokens to execute the council review. | +| `GH_COMMENT_TOKEN` | Autopilot orchestrator (host) | The comment-only GitHub PAT used to post verdicts and reviews to `ffroliva/gflow-cli` PRs. | +| `TELEGRAM_BOT_TOKEN` | Autopilot orchestrator (host) | Bot token to dispatch alert messages. | +| `TELEGRAM_USER_ID` | Autopilot orchestrator (host) | The chat ID to receive triage alert messages. | +| `PR_TRIAGE_ENGINE` | Autopilot orchestrator (host), optional | Review engine selector. Default `council-claude`; any other value exits at startup (`council-multi-cli` is reserved backlog). | +| `HERMES_OPS_DIR` | Autopilot orchestrator (host), optional | Location of the hermes-ops checkout hosting the Resend email notifier. Default `/opt/hermes-ops`. | +| `RESEND_API_KEY` | Email notifier (hermes-ops) | Resend API key consumed by `$HERMES_OPS_DIR/scripts/notify/email_notify.py`. | +| `HERMES_NOTIFY_EMAIL_TO` | Email notifier (hermes-ops) | Recipient address for high-signal triage emails. | +| `HERMES_NOTIFY_EMAIL_FROM` | Email notifier (hermes-ops) | Verified sender address for the Resend account. | + +**Email channel note:** on the VPS the three email vars live in `/opt/hermes/.env`, rendered from hermes-ops' SOPS store. When they are unset the email channel silently disables itself — the notifier logs "email disabled" and the autopilot run is unaffected (Telegram, ledger, and GitHub comments remain authoritative). + +--- + +## 2. Directory Layout & Symlinks + +Deploy the following layout on the VPS: +- `/opt/gflow-cli`: Dedicated git repository checkout representing the active branch. +- `/opt/experience-vault`: Host experience vault directory structure. +- `/opt/experience-vault/projects/C--development-github-gflow-cli/memory`: The project-specific memory namespace to mount. + +--- + +## 3. Ephemeral Sandbox Firewall Hardening + +The docker sandbox runs under `scripts/autopilot/run_sandboxed_review.sh`. If run with root/sudo privileges on the VPS host, it automatically invokes `iptables` rules restricting the container bridge network egress interface to: +- UDP/TCP port 53 (DNS resolution) +- TCP port 443 to `api.anthropic.com` resolved IPs +- TCP port 443 to `github.com` resolved IPs +- All other internet egress is blocked (`DROP`). + +Ensure `iptables` is installed on the host VPS. If `iptables` permissions are withheld, the script falls back with a warning, but egress hardening will not be active. + +--- + +## 4. Cron Configuration & Verification + +### Setup Cron Tick +Configure a cron job checking every hour on the hour under the `hermes` system user: + +```cron +0 * * * * set -a; . /opt/hermes/.env 2>/dev/null; set +a; export ANTHROPIC_API_KEY="xxx" && export GH_COMMENT_TOKEN="xxx" && export TELEGRAM_BOT_TOKEN="xxx" && export TELEGRAM_USER_ID="xxx" && cd /opt/gflow-cli && uv run python scripts/autopilot/pr_triage_autopilot.py --repo-dir /opt/gflow-cli --memory-dir /opt/experience-vault/projects/C--development-github-gflow-cli/memory >> /var/log/hermes/pr_triage.log 2>&1 +``` + +Sourcing `/opt/hermes/.env` first (`set -a` exports everything it defines) is what delivers the `RESEND_API_KEY` / `HERMES_NOTIFY_EMAIL_*` vars to the process so the email channel works; without it the run still succeeds but emails are silently disabled. + +### Deploy mechanism (warning) + +Do **NOT** register the orchestrator directly via `hermes cron create --script pr_triage_autopilot.py`. Hermes' cron runs scripts with hermes-agent's own interpreter — which lacks this repo's dependencies (e.g. `structlog`) — and only accepts scripts under `HERMES_HOME/scripts/`. Supported mechanisms: + +- **(a) Plain crontab** — the line above, under the `hermes` system user. +- **(b) Thin shim in `HERMES_HOME/scripts/`** — a script that does `cd /opt/gflow-cli && exec uv run python scripts/autopilot/pr_triage_autopilot.py "$@"` (mirrors the existing EV ops-health shim pattern), registered with `hermes cron create "1h" --no-agent --script --deliver telegram`. + +### Check Logs & Status +- **Log Location**: `/var/log/hermes/pr_triage.log` +- **Triage Ledger**: `/opt/gflow-cli/pr_triage_ledger.jsonl` tracks verdicts and failure states. +- **Lock File**: `/tmp/pr_triage_autopilot.lock` prevents overlapping ticks. + +--- + +## 5. Operations & Kill-Switches + +### Incident: Infinite loop or excessive cost +1. **Pause Cron**: Comment out the cron entry in `crontab -e`. +2. **Kill Running Container**: + ```bash + docker ps | grep gflow-triage + docker kill + ``` +3. **Clean Up Lock**: + ```bash + rm -f /tmp/pr_triage_autopilot.lock + ``` + +### Incident: Permanently Failed PR +If a PR enters the `FAILED_PERMANENT` state in `pr_triage_ledger.jsonl` due to 3 consecutive failures: +1. Examine `/var/log/hermes/pr_triage.log` for the exact trace. +2. Fix the underlying environmental or syntax issue. +3. To trigger a re-review, delete or edit the `FAILED_PERMANENT` entries for that PR number/SHA in `pr_triage_ledger.jsonl`, then run the script manually: + ```bash + uv run python scripts/autopilot/pr_triage_autopilot.py --repo-dir /opt/gflow-cli --memory-dir /opt/experience-vault/projects/C--development-github-gflow-cli/memory + ``` diff --git a/docs/GITHUB.md b/docs/GITHUB.md index 2120669be..53055d2c5 100644 --- a/docs/GITHUB.md +++ b/docs/GITHUB.md @@ -24,9 +24,7 @@ when deciding what to do with an external or internal PR. ## Automated External PR Triage -External PR routing is handled by -`.github/workflows/external-pr-triage.yml`. It uses `pull_request_target`, but -only for repository metadata operations: +External PR routing starts with `.github/workflows/external-pr-triage.yml` (triggered via `pull_request_target`), which performs basic repository metadata operations: - label external PRs as `external-contribution` - label them as `needs-maintainer-review` @@ -34,12 +32,17 @@ only for repository metadata operations: - request `@ffroliva` as reviewer - post or update the external-contribution checklist comment -Bot PRs such as Dependabot updates are skipped by this human-contributor -triage. They still run normal CI and remain subject to branch protection. +Bot PRs such as Dependabot updates are skipped by this human-contributor triage. They still run normal CI and remain subject to branch protection. -This workflow must not checkout the PR branch, install dependencies, run tests, -or execute contributor code. Keep all code execution in the normal `pull_request` -CI workflow, where forked PRs do not receive repository secrets. +This workflow must not checkout the PR branch, install dependencies, run tests, or execute contributor code. Keep all code execution in the normal `pull_request` CI workflow, where forked PRs do not receive repository secrets. + +### PR-Triage Autopilot (VPS Sandbox) + +To automate deeper verification, an hourly `hermes-ops` cron job runs on the host VPS to triage eligible external PRs. +- It uses the deterministic **Stage 0 pre-filter** (`pr_triage_gate.py`) to confirm eligibility. +- It fetches the PR branch and runs the full `/gflow:pr-council-review` inside an **ephemeral, non-root, read-only Docker container sandbox** (with restricted network egress and no write credentials). +- The host orchestrator posts the review comment to the PR and alerts the maintainer via Telegram. +- For design details, see [2026-07-04-pr-triage-autopilot-design.md](superpowers/specs/2026-07-04-pr-triage-autopilot-design.md) and the implementation plan [PLAN.md](superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md). ## GitHub Copilot Code Review diff --git a/docs/INDEX.md b/docs/INDEX.md index 15ebb13d9..9a0a95efe 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -47,6 +47,8 @@ Welcome to the `gflow-cli` documentation. This index is the routing layer: it te | **[skills/README.md](../skills/README.md)** | Installable agent skill docs (gflow-cli, predict, pr-council-review, scenario) — cross-tool portable Markdown consumed by Claude Code, Cursor, Codex, Gemini CLI, Aider, etc. | Any agent wanting to use gflow-cli correctly | | **[scripts/dev/skillopt/README.md](../scripts/dev/skillopt/README.md)** | SkillOpt mock harness — rollout→score loop for measuring and improving skill doc accuracy across multiple LLM providers | Measuring a skill edit's impact; comparing Claude vs GPT-4o vs Gemini on gflow tasks | | **[scripts/diag/README.md](../scripts/diag/README.md)** | Diagnostic investigation scripts — run against a live authenticated profile to capture wire samples, measure Chrome memory, or mint reCAPTCHA tokens | Running a one-off investigation against a live Flow session; establishing baseline measurements for issue #155 | +| [docs/superpowers/specs/2026-07-04-pr-triage-autopilot-design.md](superpowers/specs/2026-07-04-pr-triage-autopilot-design.md) | PR-triage autopilot design spec: Stage 0/1 gate, Docker sandboxing, Telegram and audit ledger | Reviewing or auditing the automated PR-triage setup | +| [docs/superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md](superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md) | Implementation plan for the PR-Triage Autopilot | Tracking task-by-task execution | ## Agent commands @@ -123,7 +125,7 @@ Slash commands for Claude Code, stored in `.claude/commands/gflow/`. All prefixe **"A gflow command hangs / fails — where do I start?"** → [DEBUGGING § Quick reference](DEBUGGING.md#quick-reference) **"Flow's UI broke a selector — how do I diagnose it?"** → [DEBUGGING § Inspecting Flow's live UI](DEBUGGING.md#inspecting-flows-live-ui) **"What does each `ui_automation.*` log event mean?"** → [DEBUGGING § Listener & HTTP-layer debugging](DEBUGGING.md#listener--http-layer-debugging) -**What was actually live-verified for the latest release?** → latest: [LIVE_VERIFICATION_v0.37.0](LIVE_VERIFICATION_v0.37.0.md) (**viewport 1920×1080 + agentic count enforcement + FIPS-safe SAPISIDHASH proven live e2e** — `image t2i` [classic cohort, real 1376×768 JPEG] and `video t2v` veo-fast [`MEDIA_GENERATION_STATUS_SUCCESSFUL`, 11.5 MB `ftyp` mp4] both succeeded end-to-end at the new 1920×1080 viewport with every selector resolving [no drift]; SAPISIDHASH digest verified byte-identical [FIPS-safe `usedforsecurity=False`], auth unchanged). Prior: [v0.36.0](LIVE_VERIFICATION_v0.36.0.md) (**#316 diagnostic tooling live-verified against real Flow traffic** — `GFLOW_CLI_HAR_PATH` captured a real 54-entry HAR [real Flow domains, 34× 200s, and a genuine 401 the tool exists to diagnose]; `GFLOW_CLI_DEBUG_TRACEBACK`'s typed-error scoping confirmed live [console output identical with/without the flag on a `GFlowError`]; unhandled-path behavior covered by 6 new tests (30 total across both files) + SDD task review; #312 reference-entity-smuggling fix covered by its own dedicated e2e test). Prior: [v0.35.0](LIVE_VERIFICATION_v0.35.0.md) (**#305-follow-up multimodal reverse-engineering & storyboard tool proven live e2e** — `gflow tools run storyboard` successfully deconstructed a cyberpunk scene into a cohesive 4-panel sequence; `reverse-engineer` tool extracts frames from video streams/URLs using `claude-video`'s `watch.py` and produces structured prompt components using Gemini multimodal models; dynamic token budgeting and strict Pyright types verified via test suite). Prior: [v0.34.0](LIVE_VERIFICATION_v0.34.0.md) (**#299 `--ui-mode` gate proven live, ZERO credits** — drove `get_ui_driver` directly over 4 real editor loads, never generated: bidirectional switch works [classic→agentic via `_force_agent_mode`, agentic→classic via `_exit_agent_mode`, each DOM-reverified], `agentic`-when-already-agentic is idempotent, and a timed-out toggle → **exit-28 fail-fast before submission**; deprecation behavior-change [`prefer_classic`→exit 28] confirmed). Prior: [v0.33.0](LIVE_VERIFICATION_v0.33.0.md) (**#287-follow-up picker fix proven live on the original repro** — the v0.32.1-failing `i2v --project ` command now exits 0 with a real 3.0 MB `ftyp` mp4, `frame_ref_attached` + `video_saved`, zero uploads; **#241 jitter live-verified on both paths** — flag `--jitter 4-6` → `batch_jitter_sleep seconds=4.98`, and the new 0.5–1.5 s default → `seconds=1.47`, each firing exactly once between two t2i submissions; environment note: the denon82 cohort is now natively agentic and round-2 generations hit the pre-existing #281 `MediaAttributionError`, not a regression). Prior: [v0.32.1](LIVE_VERIFICATION_v0.32.1.md) (**#293 teardown fix live-verified credit-free** — error-path aborts leave ZERO chrome.exe holding the profile [3× leak reproduced pre-fix same day]; force-close fallback + ProfileLockedError translation unit-pinned; #283 wave logic fixes mock-verified to exact call counts). Prior: [v0.32.0](LIVE_VERIFICATION_v0.32.0.md) (**#287 UUID i2v frames proven live e2e** — an in-project asset selected by media UUID in the frame-slot dialog [frame_ref_attached, startImage bound on the wire, ZERO upload events] produced a real 720×1280 interpolation, resolving the #237 negative prior; foreign UUID → exit 9 pre-generation with screenshot; **#288 duration fail-fast proven live** — exit 23 pre-submit saved a 10-credit generation, and the debug screenshot confirmed the duration control is ABSENT from the cohort's settings popover [locale hypothesis refuted]; upload-400 exit-27 repro deferred-with-reason — the original rejected JPEG no longer exists). Prior: [v0.31.0](LIVE_VERIFICATION_v0.31.0.md) (**#281 wrong-media defenses live-verified in BOTH directions, credit-free** — correct attribution downloads with real wire metadata [seed, 768×1376]; the incident-class lazy-render ambiguity reproduced live and hard-stopped with `MediaAttributionError` exit 26 naming every candidate UUID, no file written; **#282 two-UUID-ref i2i proven e2e** — both freshly generated portrait UUIDs picker-selected into one scene with both canonical identities visually transferred). Prior: [v0.30.0](LIVE_VERIFICATION_v0.30.0.md) (**agentic-cohort image path + both fixes live-verified credit-free** — native 768×1376 stills, i2i-chain + canonical-ref identity hold; the character-create `DataIntegrityError` and the `prefer_classic` WARNING were observed live then fixed [recorder idempotent + INFO log]; MCP video-param feature deferred-with-reason on Veo credits, per the stills-only goal). Prior: [v0.29.0](LIVE_VERIFICATION_v0.29.0.md) (**persistent `gflow instructions` CRUD proven live e2e GREEN, credit-free** — 11-command sequence on a fresh project: add incl. `--ref ` attach, list/enable/disable, master toggle both ways, TOML `apply` destructive full-sync with `enabled=false` honored, `rm` with server card-id preserved across mutations; movie brief-sync code-path-verified via the same primitives, deferred-with-reason on credits; MCP `gflow_instructions_*` tools are thin adapters over the live-verified primitives). Prior: [v0.28.0](LIVE_VERIFICATION_v0.28.0.md) (**agent instructions `-i` now steer agentic generation — crayon e2e GREEN**: a style-neutral prompt + an enabled "crayon" card produced an unmistakable crayon drawing through the real transport; root causes were conversational phrasing + the `project_brief.enabled` master switch). Prior: [v0.27.1](LIVE_VERIFICATION_v0.27.1.md) (**v0.27.0 release follow-up fixes and documentation sync**). Prior: [v0.27.0](LIVE_VERIFICATION_v0.27.0.md) (**`[style]` block with named variants + prompt-aware resume — credit-free CLI verification**: real `movie run --dry-run` 3-way ledger proving resolved-style display, `skip (done)` on matching `style_hash`, and `re-run (style changed)` after a style edit; composition is local, wire path untouched, so no Veo credits spent — first credited styled run will confirm visually). Prior: [v0.26.0](LIVE_VERIFICATION_v0.26.0.md) (**image i2i by generated-image UUID proven live e2e GREEN** — the existing asset is *selected in place* by its media UUID, no duplicate upload, producing an i2i output that references it; plus generated-image `display_name` capture, credited @C1ph3r404). Prior: [v0.25.0](LIVE_VERIFICATION_v0.25.0.md) (**#237 remote-UUID i2v proven live e2e GREEN** — a real 8s 720×1280 interpolation from a generated image's UUID, after the picker-search attach was reworked to local upload; #240 home-`.env` matrix live; plus two silent-failure guards root-caused live: video-as-image download rejection and rejected-upload fail-loud). Prior: [v0.24.0](LIVE_VERIFICATION_v0.24.0.md) (--project parity across CLI + MCP — automated coverage; live gen auth-gated). Prior: [v0.23.0](LIVE_VERIFICATION_v0.23.0.md) (MCP generation wiring proven live end-to-end — tool→worker→real Flow REST→structured-error translation; final image-write blocked by an expired session, environmental. #222 macOS fix reporter-verified e2e on Apple Silicon). Prior: [v0.22.0](LIVE_VERIFICATION_v0.22.0.md) (Tools framework — automated/CI coverage complete; credit/key-gated live feature run is a pending owner gate, see the doc) · [v0.21.0](LIVE_VERIFICATION_v0.21.0.md) (MCP server over stdio + HTTP/SSE — credit-free protocol handshake; 4 tools / 2 prompts / 3 resources confirmed on stdout) · [v0.20.1](LIVE_VERIFICATION_v0.20.1.md) · [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md) +**What was actually live-verified for the latest release?** → latest: [LIVE_VERIFICATION_v0.38.0](LIVE_VERIFICATION_v0.38.0.md) (**#332 mode control + #314 i2i ref dedup proven live e2e** — `image t2i --ui-mode classic` recovered agentic→classic in a real run [`ui_mode.attempt_exit_agent` → full classic chain, 1376×768 JPEG]; two identical `image i2i` runs proved the dedup contract [run 1: picker miss → `image_uploaded`; run 2: `reference_deduped_by_filename`, ZERO uploads]; `video t2v` veo-fast [`MEDIA_GENERATION_STATUS_SUCCESSFUL`, 15.2 MB `ftyp` mp4]; autopilot #333 recorded deferred-with-reason [hermes-ops deployment staged separately]). Prior: [v0.37.0](LIVE_VERIFICATION_v0.37.0.md) (**viewport 1920×1080 + agentic count enforcement + FIPS-safe SAPISIDHASH proven live e2e** — `image t2i` [classic cohort, real 1376×768 JPEG] and `video t2v` veo-fast [`MEDIA_GENERATION_STATUS_SUCCESSFUL`, 11.5 MB `ftyp` mp4] both succeeded end-to-end at the new 1920×1080 viewport with every selector resolving [no drift]; SAPISIDHASH digest verified byte-identical [FIPS-safe `usedforsecurity=False`], auth unchanged). Prior: [v0.36.0](LIVE_VERIFICATION_v0.36.0.md) (**#316 diagnostic tooling live-verified against real Flow traffic** — `GFLOW_CLI_HAR_PATH` captured a real 54-entry HAR [real Flow domains, 34× 200s, and a genuine 401 the tool exists to diagnose]; `GFLOW_CLI_DEBUG_TRACEBACK`'s typed-error scoping confirmed live [console output identical with/without the flag on a `GFlowError`]; unhandled-path behavior covered by 6 new tests (30 total across both files) + SDD task review; #312 reference-entity-smuggling fix covered by its own dedicated e2e test). Prior: [v0.35.0](LIVE_VERIFICATION_v0.35.0.md) (**#305-follow-up multimodal reverse-engineering & storyboard tool proven live e2e** — `gflow tools run storyboard` successfully deconstructed a cyberpunk scene into a cohesive 4-panel sequence; `reverse-engineer` tool extracts frames from video streams/URLs using `claude-video`'s `watch.py` and produces structured prompt components using Gemini multimodal models; dynamic token budgeting and strict Pyright types verified via test suite). Prior: [v0.34.0](LIVE_VERIFICATION_v0.34.0.md) (**#299 `--ui-mode` gate proven live, ZERO credits** — drove `get_ui_driver` directly over 4 real editor loads, never generated: bidirectional switch works [classic→agentic via `_force_agent_mode`, agentic→classic via `_exit_agent_mode`, each DOM-reverified], `agentic`-when-already-agentic is idempotent, and a timed-out toggle → **exit-28 fail-fast before submission**; deprecation behavior-change [`prefer_classic`→exit 28] confirmed). Prior: [v0.33.0](LIVE_VERIFICATION_v0.33.0.md) (**#287-follow-up picker fix proven live on the original repro** — the v0.32.1-failing `i2v --project ` command now exits 0 with a real 3.0 MB `ftyp` mp4, `frame_ref_attached` + `video_saved`, zero uploads; **#241 jitter live-verified on both paths** — flag `--jitter 4-6` → `batch_jitter_sleep seconds=4.98`, and the new 0.5–1.5 s default → `seconds=1.47`, each firing exactly once between two t2i submissions; environment note: the denon82 cohort is now natively agentic and round-2 generations hit the pre-existing #281 `MediaAttributionError`, not a regression). Prior: [v0.32.1](LIVE_VERIFICATION_v0.32.1.md) (**#293 teardown fix live-verified credit-free** — error-path aborts leave ZERO chrome.exe holding the profile [3× leak reproduced pre-fix same day]; force-close fallback + ProfileLockedError translation unit-pinned; #283 wave logic fixes mock-verified to exact call counts). Prior: [v0.32.0](LIVE_VERIFICATION_v0.32.0.md) (**#287 UUID i2v frames proven live e2e** — an in-project asset selected by media UUID in the frame-slot dialog [frame_ref_attached, startImage bound on the wire, ZERO upload events] produced a real 720×1280 interpolation, resolving the #237 negative prior; foreign UUID → exit 9 pre-generation with screenshot; **#288 duration fail-fast proven live** — exit 23 pre-submit saved a 10-credit generation, and the debug screenshot confirmed the duration control is ABSENT from the cohort's settings popover [locale hypothesis refuted]; upload-400 exit-27 repro deferred-with-reason — the original rejected JPEG no longer exists). Prior: [v0.31.0](LIVE_VERIFICATION_v0.31.0.md) (**#281 wrong-media defenses live-verified in BOTH directions, credit-free** — correct attribution downloads with real wire metadata [seed, 768×1376]; the incident-class lazy-render ambiguity reproduced live and hard-stopped with `MediaAttributionError` exit 26 naming every candidate UUID, no file written; **#282 two-UUID-ref i2i proven e2e** — both freshly generated portrait UUIDs picker-selected into one scene with both canonical identities visually transferred). Prior: [v0.30.0](LIVE_VERIFICATION_v0.30.0.md) (**agentic-cohort image path + both fixes live-verified credit-free** — native 768×1376 stills, i2i-chain + canonical-ref identity hold; the character-create `DataIntegrityError` and the `prefer_classic` WARNING were observed live then fixed [recorder idempotent + INFO log]; MCP video-param feature deferred-with-reason on Veo credits, per the stills-only goal). Prior: [v0.29.0](LIVE_VERIFICATION_v0.29.0.md) (**persistent `gflow instructions` CRUD proven live e2e GREEN, credit-free** — 11-command sequence on a fresh project: add incl. `--ref ` attach, list/enable/disable, master toggle both ways, TOML `apply` destructive full-sync with `enabled=false` honored, `rm` with server card-id preserved across mutations; movie brief-sync code-path-verified via the same primitives, deferred-with-reason on credits; MCP `gflow_instructions_*` tools are thin adapters over the live-verified primitives). Prior: [v0.28.0](LIVE_VERIFICATION_v0.28.0.md) (**agent instructions `-i` now steer agentic generation — crayon e2e GREEN**: a style-neutral prompt + an enabled "crayon" card produced an unmistakable crayon drawing through the real transport; root causes were conversational phrasing + the `project_brief.enabled` master switch). Prior: [v0.27.1](LIVE_VERIFICATION_v0.27.1.md) (**v0.27.0 release follow-up fixes and documentation sync**). Prior: [v0.27.0](LIVE_VERIFICATION_v0.27.0.md) (**`[style]` block with named variants + prompt-aware resume — credit-free CLI verification**: real `movie run --dry-run` 3-way ledger proving resolved-style display, `skip (done)` on matching `style_hash`, and `re-run (style changed)` after a style edit; composition is local, wire path untouched, so no Veo credits spent — first credited styled run will confirm visually). Prior: [v0.26.0](LIVE_VERIFICATION_v0.26.0.md) (**image i2i by generated-image UUID proven live e2e GREEN** — the existing asset is *selected in place* by its media UUID, no duplicate upload, producing an i2i output that references it; plus generated-image `display_name` capture, credited @C1ph3r404). Prior: [v0.25.0](LIVE_VERIFICATION_v0.25.0.md) (**#237 remote-UUID i2v proven live e2e GREEN** — a real 8s 720×1280 interpolation from a generated image's UUID, after the picker-search attach was reworked to local upload; #240 home-`.env` matrix live; plus two silent-failure guards root-caused live: video-as-image download rejection and rejected-upload fail-loud). Prior: [v0.24.0](LIVE_VERIFICATION_v0.24.0.md) (--project parity across CLI + MCP — automated coverage; live gen auth-gated). Prior: [v0.23.0](LIVE_VERIFICATION_v0.23.0.md) (MCP generation wiring proven live end-to-end — tool→worker→real Flow REST→structured-error translation; final image-write blocked by an expired session, environmental. #222 macOS fix reporter-verified e2e on Apple Silicon). Prior: [v0.22.0](LIVE_VERIFICATION_v0.22.0.md) (Tools framework — automated/CI coverage complete; credit/key-gated live feature run is a pending owner gate, see the doc) · [v0.21.0](LIVE_VERIFICATION_v0.21.0.md) (MCP server over stdio + HTTP/SSE — credit-free protocol handshake; 4 tools / 2 prompts / 3 resources confirmed on stdout) · [v0.20.1](LIVE_VERIFICATION_v0.20.1.md) · [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md) **"Where is the reverse-engineered wire protocol for a feature?"** → the `*_RECON.md` design docs: [CHARACTER_RECON](CHARACTER_RECON.md) (Flow character entity protocol), [IMAGE_UPSCALE_RECON](IMAGE_UPSCALE_RECON.md) (`/v1/flow/upsampleImage` wire). Naming convention: one `_RECON.md` per reverse-engineered surface, kept as the durable spec after the feature ships. **"What was live-verified for the data layer (PR #58)?"** → [LIVE_VERIFICATION_data_layer](LIVE_VERIFICATION_data_layer.md) — 1 Imagen + 1 Veo credit on denon82, 6-layer ledger (file + magic + Pillow + DB rows + CLI round-trip + structlog) **"What was live-verified for the video-download feature (#29)?"** → [LIVE_VERIFICATION_video_download](LIVE_VERIFICATION_video_download.md) diff --git a/docs/LIVE_VERIFICATION_v0.38.0.md b/docs/LIVE_VERIFICATION_v0.38.0.md new file mode 100644 index 000000000..d8fdd306c --- /dev/null +++ b/docs/LIVE_VERIFICATION_v0.38.0.md @@ -0,0 +1,52 @@ +# Live Verification — v0.38.0 + +**Date:** 2026-07-17 · **Profile:** `ffroliva` (real Chrome, live Flow) · **Tree:** `develop` @ v0.38.0 prep + +This release's user-facing changes were exercised end-to-end against live Flow before the +tag. Evidence uses the 5-layer ledger (file count + magic bytes + shape + structlog +invariants + user-confirmable artifact). + +## Features covered + +| Feature (release) | How verified | +|---|---| +| **Robust agentic↔classic mode control** (#332) | The t2i run below requested `--ui-mode classic` on a profile that loaded in the agentic cohort; the log shows `ui_driver.ui_mode.attempt_exit_agent` followed by the full classic-editor chain — the exact path that previously false-aborted as "forced agentic, not recoverable". Also live-validated in #332 itself via `scripts/dev/spike_mode_roundtrip.py` (full classic→agent→classic round-trip asserting `aria-pressed`). | +| **i2i ref dedup via picker filename search** (#314, #334, #335) | Two identical `image i2i` runs against the same project with a uniquely-named local ref. Run 1: picker search missed (scroll probes logged) → `image_uploaded` (HTTP 200) → `reference_attached`. Run 2: `reference_deduped_by_filename` (`dedup-v038-check.jpg`), **zero** upload events, `reference_attached` via the existing tile, generation OK. This closes the "Live e2e re-verify pending" note on #335. | +| **Cognitive-complexity refactor** (#331) | Behavior-preserving by construction (pyright 0/0/0, 1202 scoped tests, adversarial behavior-diff review in the PR); all three live runs below traverse the refactored `_enter_setup` client path. | +| **PR-Triage Autopilot** (#238, #333) | **Not live-verifiable this cycle:** the autopilot executes on the hermes-ops VPS, whose deployment is staged separately (implementation + fixture evals only in this repo). Recorded here per the release gate rather than silently omitted. | + +## E2e evidence — `gflow image t2i --ui-mode classic` (classic cohort, #332 path) + +- **Command:** `image t2i "a red vintage bicycle leaning against a stone wall, morning light, photorealistic" --aspect 16:9 -n 1 --ui-mode classic` · **exit 0** · `status: ok`. +- **File count:** 1 JPEG produced. +- **Magic bytes:** `ff d8 ff e0` (valid JPEG). +- **Shape:** 1376×768 (16:9), 1.16 MB, model `NARWHAL`. +- **Structlog invariants:** `ui_mode.attempt_exit_agent → image_mode_entered → image_model_selected → aspect_ratio_set → count_setter_completed → prompt_submitted → batch_response_captured`. +- **Artifact:** `2ac7ae3f-…_1.jpg`. + +## E2e evidence — `gflow image i2i` ×2, same ref (dedup, #314) + +- **Commands:** `image i2i "" --ref dedup-v038-check.jpg --project 9d5b9a86-…` run twice · both **exit 0** · `status: ok`. +- **Run 1 (miss→upload):** `picker_scroll_probe`×4 → `picker_scroll_done` → `image_uploaded` (HTTP 200) → `reference_attached`; 1 JPEG generated. +- **Run 2 (hit→dedup):** `reference_deduped_by_filename {filename: dedup-v038-check.jpg}` → `reference_attached`; **0** `image_uploaded` events; 1 JPEG generated. +- **Artifacts:** `582da216-…_1.jpg` (run 1), `00e99878-…_1.jpg` (run 2). + +## E2e evidence — `gflow video t2v` (veo-fast, classic cohort) + +- **Command:** `video t2v "slow dolly shot along a misty forest road at dawn, cinematic" --aspect 16:9 --model veo-fast` · **exit 0** · `generation_status: MEDIA_GENERATION_STATUS_SUCCESSFUL`. +- **File count:** 1 MP4 produced. +- **Magic bytes:** `ftyp isom` container (valid MP4). +- **Shape:** 15.2 MB (landscape), model `veo_3_1_fast`. +- **Structlog invariants:** `video_mode_entered → editor_ready → model_selected → aspect_set → output_count_set → generate_captured → poll_terminal(SUCCESSFUL) → video_saved`. +- **Artifact:** `3d001ce4-….mp4`. + +## Pre-tag gates + +- `/gflow:check`: hygiene + doc links + ruff check/format + pyright 0/0/0 green; full-suite CI green on the exact release tree (develop @ `b97a8ea`). +- `/gflow:doc-review`: mechanical pass green after fixes (PROJECT_STATUS updated for v0.37.0+v0.38.0; shipped plan dirs consolidated). _Council verdict: YELLOW/YELLOW/GREEN across the 3 auditors. 6 findings: 0 Tier 1; 4 Tier 2 fixed in the release-prep commit (USAGE i2i dedup semantics, KNOWN_ISSUES resolved entry for the false forced-agentic abort + exit-25 remediation refresh, AGENTS.md `--browser` claim, AGENTS.md video `batch` stub label); 2 cosmetic noted, no action. Council reports at `tmp/council/0{1,2,3}-*.md` (local-only)._ + +## Result + +All live generation paths succeeded end-to-end on the v0.38.0 tree: the #332 mode +controller recovered agentic→classic in a real run, and the #314 dedup path selected the +existing library tile instead of re-uploading. Release gate **PASS**. diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index 8cc6c2111..67e1f2ad5 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -4,9 +4,21 @@ ## Current release +**v0.38.0 — alpha.** **Robust agentic↔classic mode control + i2i reference dedup (#332, #314).** `--ui-mode` is now driven by a state-aware mode controller reading Flow's Agent toggle `aria-pressed` state (locale-invariant), ending spurious "forced agentic — not recoverable" aborts caused by an icon heuristic that matched both modes; `--ui-mode classic` reliably reaches the classic editor. Repeated local `--ref` images in `gflow image i2i` are now attached by selecting the already-uploaded library tile via exact-filename picker search (with a virtualised-grid scroll fallback, #335) instead of re-uploading duplicates. Also ships the PR-triage autopilot implementation (#238/#333, host deployment staged separately) and a behavior-preserving cognitive-complexity refactor (#331). Verification: [LIVE_VERIFICATION_v0.38.0](LIVE_VERIFICATION_v0.38.0.md) (agentic→classic recovery observed in a real run; dedup contract proven with an upload-then-dedup run pair; veo-fast t2v successful). + +**Develop (unreleased, post-v0.38.0):** *(empty — develop is the staging branch for the next release).* + +
v0.37.0 — viewport 1920×1080 + agentic count enforcement + +**v0.37.0 — alpha.** **Viewport 1920×1080 + agentic count enforcement + FIPS-safe SAPISIDHASH (#313, #315, #329).** UI-automation and auth-login viewports harmonized to 1920×1080 to blend with the most common desktop resolution; the agentic cohort's requested image count (`-n`) is reliably enforced via the Agent settings panel; the protocol-mandated SHA-1 in SAPISIDHASH is marked `usedforsecurity=False` so it works under FIPS-mode Python. Also bumps `mcp` to 1.28.1 (CVE-2026-59950). Verification: [LIVE_VERIFICATION_v0.37.0](LIVE_VERIFICATION_v0.37.0.md). + +
+ +
v0.36.0 — diagnostic tooling + reference-entity-smuggling fix + **v0.36.0 — alpha.** **Diagnostic tooling (`GFLOW_CLI_HAR_PATH` + `GFLOW_CLI_DEBUG_TRACEBACK`) + reference-entity-smuggling fix (#312/#316).** Two opt-in, env-var-only debug knobs: `GFLOW_CLI_HAR_PATH` captures full Playwright network traffic to a HAR file (0600-hardened on POSIX); `GFLOW_CLI_DEBUG_TRACEBACK` prints the real exception + traceback for unhandled errors (console and `--json`) instead of the generic placeholder, while structured telemetry stays hashed unconditionally either way. Also fixes a poisoned character entity leaking `referenceEntities` into unrelated `image i2i` calls in the same project workspace. Also ships the `llm-council` skill (`/gflow:llm-council`), adding external CLI reviewers (`codex`/`gemini`, opt-in `agy`) alongside the internal council for high-stakes reviews. Verification: [LIVE_VERIFICATION_v0.36.0](LIVE_VERIFICATION_v0.36.0.md) (live HAR capture against real Flow traffic; typed-error scoping confirmed live; unhandled path covered by 6 new tests, 30 total across both files). -**Develop (unreleased, post-v0.36.0):** *(empty — develop is the staging branch for the next release).* +
v0.35.0 — multimodal reverse-engineering + storyboard tool @@ -209,6 +221,8 @@ reporter-verified e2e on macOS). | Agent instructions (`-i`/`--instruction`) steer agentic generation — conversational directive + brief master switch (PR #263) | ✅ done (v0.28.0) | | Persistent `gflow instructions` CRUD + `movie.toml` instructions brief-sync + `gflow_instructions_*` MCP tools + CI-enforced MCP↔CLI parity (#192) | ✅ done (v0.29.0) | | Diagnostic tooling (`GFLOW_CLI_HAR_PATH` + `GFLOW_CLI_DEBUG_TRACEBACK`) + reference-entity-smuggling fix (#312/#316) | ✅ done (v0.36.0) | +| Viewports 1920×1080 + agentic count enforcement + FIPS-safe SAPISIDHASH (#313/#315/#329) | ✅ done (v0.37.0) | +| Robust `aria-pressed` agentic↔classic mode control (#332) + i2i ref dedup via picker filename search (#314) | ✅ done (v0.38.0) | | `gflow video batch` (TSV manifest) on `ui_automation` | ⏳ Phase B | | Persistence layer (stay-mounted batch sessions across project boundaries) | ⏳ Phase B | | Provider abstraction for official Veo 3.1 API | ⏳ planned | diff --git a/docs/USAGE.md b/docs/USAGE.md index b9238d166..c7851fcdb 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -306,7 +306,7 @@ Options: **Path-or-UUID semantics.** Each `--ref` value is classified at the CLI boundary: - **Looks like a Flow asset UUID** (case-insensitive 8-4-4-4-12 hex, e.g. `ddb6ef97-262d-49f4-8269-4a28c0fae6a2`) → passed through verbatim. No upload, no extra round-trip. -- **Anything else** → treated as a local path. The CLI canonicalises it (resolving symlinks once at validation time, closing the symlink-laundering vector where `./hero.png -> ~/.ssh/id_rsa` could exfiltrate secrets), uploads it, then uses the resulting UUID. +- **Anything else** → treated as a local path. The CLI canonicalises it (resolving symlinks once at validation time, closing the symlink-laundering vector where `./hero.png -> ~/.ssh/id_rsa` could exfiltrate secrets), then attaches it — **deduplicated by filename since v0.38.0 (#314):** if the target project's library already holds an asset with the exact same filename, that existing tile is selected in the picker instead of re-uploading, so repeating a ref across runs no longer piles up duplicate library entries. On a picker miss the file is uploaded as before. Caveat: the match is by exact filename only — a *different* image that happens to share the name of one already in the project will be reused, not uploaded; rename the file if you need a fresh upload. (Video `r2v` refs keep upload-only behavior.) Mix and match in a single call. UUIDs and paths can co-exist on the same command line; order is preserved so `imageInputs[]` matches the order you typed. @@ -1251,7 +1251,7 @@ shell scripts can branch on the failure mode without parsing stderr. | `22` | `UpscaleUnavailableError` | 4K upscale is gated to Flow **Ultra** accounts (HTTP 403) | Use `--scale 2k`, or upgrade the Flow plan | | `23` | `UiSelectorDriftError` | A Flow editor control could not be located — Google changed the frontend (issue #183) | Update gflow-cli; file a bug with the probe name + debug screenshot from the error message | | `24` | `BrowserEngineUnavailableError` | `GFLOW_CLI_BROWSER_ENGINE=patchright` but the engine is not installed | `pip install 'gflow-cli[patchright]'`, or unset `GFLOW_CLI_BROWSER_ENGINE` | -| `25` | `FlowAgentUiError` | The profile is on Flow's Agentic UI cohort and the classic media panel is unrecoverable for this operation | Use a Classic-cohort profile; see KNOWN_ISSUES on the agentic cohort | +| `25` | `FlowAgentUiError` | The profile is on Flow's Agentic UI cohort and the classic media panel is unrecoverable for this operation | Rare since v0.38.0 (#332): the mode controller reliably recovers agentic→classic, so first retry with `--ui-mode classic`; if it persists, see KNOWN_ISSUES on the agentic cohort | | `26` | `MediaAttributionError` | Generated media could not be reliably attributed to this request (issue #281) | Re-run; a dedicated project with fewer pre-existing assets avoids the ambiguity | | `27` | `MediaUploadRejectedError` | Flow's upload endpoint refused the input file (`uploadImage` 4xx, issue #287) | Re-encode the image (`ffmpeg -q:v 2 -map_metadata -1`), or reference the asset by its media UUID | | `28` | `UiModeUnavailableError` | The Flow UI arm this command required (`GFLOW_CLI_UI_MODE`, or inferred — `-i` forces agentic) couldn't be reached after a switch attempt; aborted before submitting — no credits spent (issue #299) | Retry (the cohort flaps per load); try another `--profile`; or relax `GFLOW_CLI_UI_MODE` | diff --git a/docs/superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md b/docs/superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md new file mode 100644 index 000000000..e1e8da490 --- /dev/null +++ b/docs/superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md @@ -0,0 +1,164 @@ +# PR-Triage Autopilot Implementation Plan + +> **For agentic workers:** Run `/gflow:status --feature pr-triage-autopilot` to find the next +> unchecked task. Implement one task at a time. Run `/gflow:check` before every commit. + +**Goal:** Implement an hourly `hermes-ops` autopilot that autonomously runs `/gflow:pr-council-review` against eligible external PRs on `ffroliva/gflow-cli` using a sandboxed Docker container, auto-posts reviews, and alerts via Telegram. + +**Architecture:** +- `scripts/autopilot/pr_triage_gate.py` (Stage 0): Deterministic pre-filter verifying author, draft status, diff sizes, and scanning for obvious prompt injections. +- `scripts/autopilot/Dockerfile.triage`: Ephemeral container image packing the `claude` CLI, run with restricted read-only volume mounts and network egress. +- `skills/pr-council-review/SKILL.md`: Gaining §9 autonomous resolutions for interactive gates (credit limits, memory, etc.). +- `scripts/autopilot/pr_triage_autopilot.py`: The hourly host orchestrator handling polling, Stage 1/council execution, posting comments via the host token boundary, Telegram alerts, and writing to `pr_triage_ledger.jsonl`. + +**Predict verdict:** GO — confidence 9/10 (based on reference `dependabot-autopilot` pattern). + +**Risk register:** +| Severity | Risk | Mitigation | +|---|---|---| +| Critical | Prompt injection executing commands on host VPS | Run reviews in ephemeral Docker sandbox with read-only code mounts, no write-scoped GitHub credentials, and network restrictions. | +| High | Unbounded LLM cost via commit churn or failed posts | Implement per-PR Stage-1 volume cap (default 3/day), calendar daily cap (default 5/day), and fail-count limit (cap at 3 retries, mark `FAILED_PERMANENT`). | +| Medium | Re-reviewing unchanged PRs | Key the audit ledger by `(pr, head_sha)` instead of just `pr` to skip already-reviewed commits. | + +--- + +## File structure + +### New files +``` +scripts/autopilot/pr_triage_gate.py + Stage 0 deterministic pre-filter logic +scripts/autopilot/pr_triage_autopilot.py + Main host orchestrator script (triggers, sandbox executor, posting, ledger) +scripts/autopilot/Dockerfile.triage + Ephemeral sandbox container definition +scripts/autopilot/run_sandboxed_review.sh + Docker invocation wrapper +eval/pr_triage_fixtures.json + Recorded/synthetic GitHub PR shapes for pre-filter testing +eval/pr_triage_eval.py + 100%-match assertion script for Stage 0 +deploy/PR-TRIAGE-AUTOPILOT-OPS.md + As-built operations runbook for the VPS +``` + +### Modified files +``` +skills/pr-council-review/SKILL.md + Add §9 Autonomous mode resolutions and structured verdict summary output +PLAN.md + Add PR-Triage Autopilot Phase status +``` + +--- + +## Task 1 — Stage 0 Deterministic Pre-Filter (`pr_triage_gate.py`) + +**What:** Implement the deterministic logic to identify eligible PRs, filter out drafts, bots, owner, oversized diffs, and obvious prompt injections. + +**Files:** +- `scripts/autopilot/pr_triage_gate.py` — Pre-filter function and CLI wrapper +- `eval/pr_triage_fixtures.json` — Target test vectors +- `eval/pr_triage_eval.py` — Run gate logic over fixtures + +**Steps:** +- [x] Define the `should_review(pr: dict) -> ShouldReviewResult` logic: + - Exclude author `ffroliva`, bot authors, and draft PRs. + - Detect PRs incorrectly targeting `main` base branch (route to `NEEDS-HUMAN` to post an automated request asking contributor to retarget to `develop` rather than silently skipping). + - Exclude diff sizes $> 30$ files or $> 1500$ additions+deletions. + - Scan title, body, and comments for regex-based injection patterns (e.g., "ignore previous instructions"). +- [x] Record 10+ fixtures in `eval/pr_triage_fixtures.json` covering: + - Valid PRs (`PROCEED`) + - Drafts, owner, bots, and merged/closed PRs (`SKIP`) + - Oversized diffs (`DEFERRED_SIZE`) + - Injection matches (`NEEDS-HUMAN`) +- [x] Implement `eval/pr_triage_eval.py` asserting a 100% verdict match rate over the fixtures. + +**Tests created (red):** +- [x] Run `python eval/pr_triage_eval.py` and verify all fixtures pass. + +--- + +## Task 2 — §9 Autonomous Mode in `pr-council-review` Skill + +**What:** Add non-interactive resolutions to the review skill when run under `hermes` orchestration. + +**Files:** +- `skills/pr-council-review/SKILL.md` — Modify review instructions + +**Steps:** +- [x] Add `## 9. Autonomous Mode` section: + - Instruct the agent to bypass the credit-spend gate (log as open item). + - Bypassing memory actions (report suggestions but do not auto-write). + - Forbid custom/unstructured output formats. + - Specify printing a single structured line to stdout at the end: `VERDICT: [verdict] | MUST-FIX: [count] | URL: [pr_url]` for the host orchestrator to parse. + - Set a content constraint on the review text itself: do not echo potential injection text, disclose environment details, or respond to meta-queries. + +--- + +## Task 3 — Ephemeral Docker Sandbox (`Dockerfile.triage`) + +**What:** Package the review environment in a secure, ephemeral container. + +**Files:** +- `scripts/autopilot/Dockerfile.triage` — Docker image specification +- `scripts/autopilot/run_sandboxed_review.sh` — Execution wrapper shell script + +**Steps:** +- [x] Define `Dockerfile.triage`: + - Base on a lightweight image (e.g., node/python-slim). + - Install dependencies (including the `gh` CLI required by the review skill) and Claude CLI. + - Configure a non-root runtime user with a writable home directory `/home/nonroot` (e.g. via tmpfs mount) to allow Claude CLI internal state writes. +- [x] Write `run_sandboxed_review.sh`: + - Bind-mount the fetched clone of `/opt/gflow-cli` as **read-only**. + - Mount only the project-specific memory namespace (`C:\Users\ffrol\.claude\projects\C--development-github-gflow-cli\memory` or its VPS equivalent) read-only, explicitly avoiding broad mounting of `/opt/experience-vault` to prevent cross-project exfiltration. + - Enforce restricted network egress (block everything except `api.anthropic.com` and `github.com`), explicitly permitting DNS (UDP/TCP port 53) for host name resolution. + - Restrict write capabilities (do not mount write-scoped GitHub tokens inside the container; pass the read-only auth token to `gh` safely). + +--- + +## Task 4 — Main Orchestration Loop (`pr_triage_autopilot.py`) + +**What:** Implement the hourly runner orchestrating Stage 0, Stage 1 pre-eval, sandbox execution, comment posting, Telegram notifications, and the audit ledger. + +**Files:** +- `scripts/autopilot/pr_triage_autopilot.py` — Orchestrator entrypoint +- `tests/autopilot/test_pr_triage_autopilot.py` — Core tests + +**Steps:** +- [x] Implement hourly lock file acquisition (`flock` or lockfile) to prevent concurrent cron ticks. +- [x] Fetch the list of open PRs via `gh pr list --json`. +- [x] Run Stage 0 gate. If qualified, fetch `pull//head` on the host machine. +- [x] Run Stage 1 pre-evaluation inside the container (cheap `claude -p` call). If `PROCEED`/`TRIVIAL`, proceed to the full review. +- [x] Launch the sandboxed Docker container executing the `pr-council-review` skill. +- [x] Capture the container's stdout, parse the structured summary line, and post the review comment from the host using `gh pr comment` (via the host's comment-only bot PAT). +- [x] Log outcomes atomically to `pr_triage_ledger.jsonl`. +- [x] Implement retry limits: increment `fail_count` on crashes; limit to 3 retries before marking `FAILED_PERMANENT` and sending a Telegram alert. +- [x] Implement the calendar daily cap (default 5 reviews/day). +- [x] Notify Telegram (Flavio) of every outcome (MERGE/FLAG/SKIP/ERROR). +- [x] Implement unit tests in `test_pr_triage_autopilot.py` ensuring that Docker and `gh` CLI subprocess calls are mocked/stubbed so tests pass cleanly on systems without Docker or `gh` CLI installed. + +--- + +## Task 5 — Operations Runbook & Shim (`PR_TRIAGE_AUTOPILOT-OPS.md`) + +**What:** Document deployment and operational management on the VPS. + +**Files:** +- `deploy/PR-TRIAGE-AUTOPILOT-OPS.md` — Deployment runbook + +**Steps:** +- [x] Document the VPS installation, environment variables (`TELEGRAM_USER_ID`, `GH_COMMENT_TOKEN`), and symlink setups. +- [x] Detail log locations, status checking (`hermes cron list`), and the kill-switch procedure (pausing cron / pausing docker daemon). +- [x] Record the golden-task baselines setup. + +--- + +## Definition of done + +- [x] All task steps checked off +- [x] `/gflow:check` green in the worktree +- [x] Pure-function fixture test (`pr_triage_eval.py`) passes 100% +- [x] Dry-run mode verified against actual open PRs without making live comments +- [x] `CHANGELOG.md` updated +- [x] Plan written to `docs/superpowers/plans/2026-07-08-pr-triage-autopilot/PLAN.md` diff --git a/docs/superpowers/plans/2026-07-16-agentic-count-enforcement-rework/PLAN.md b/docs/superpowers/plans/2026-07-16-agentic-count-enforcement-rework/PLAN.md deleted file mode 100644 index 206f7e22e..000000000 --- a/docs/superpowers/plans/2026-07-16-agentic-count-enforcement-rework/PLAN.md +++ /dev/null @@ -1,690 +0,0 @@ -# Agentic Image Count Enforcement — Rework Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Fix confirmed reliability bugs in the issue #313 fix (PR #325, merged to `develop`) by reworking `AgenticFlowUiDriver._enforce_image_count_via_settings_panel` to reuse classic mode's already-robust, already-tested count-tab primitives instead of the fragile hand-rolled version that shipped — and add a live e2e regression test so this is verified end-to-end, not just against mocks. - -**Why this plan exists:** An xhigh-effort code review (10 finder angles + verification + gap sweep) and a ponytail (YAGNI/simplicity) review of PR #325 both independently found the same root cause: `src/gflow_cli/api/transports/ui_automation.py` already has `_set_count`/`_count_tabs_locator`/`_COUNT_TAB_TEXT_RE` — classic mode's count-tab setter — which already solves every reliability problem the review found in the new agentic-mode code: it waits for React re-renders, short-circuits when the count already matches, and does read-back verification with retries. The shipped fix reinvented a weaker version from scratch (no waits, no short-circuit, no verification) instead of reusing it. - -Live investigation (2026-07-16, this session) confirmed Flow's Agent-mode settings panel uses the **same `role="tab"`/`aria-selected` Radix pattern** as classic mode's count popover — `_count_tabs_locator`/`_COUNT_TAB_TEXT_RE` work unmodified against it. Confirmed via `page.evaluate` against a real Flow project: 8 `[role="tab"]` elements match the count-tab text pattern when the Agent panel is open (4 for the "Image generation default" section, 4 for "Video generation default"), with the image section's 4 tabs always appearing first in DOM order — the same ordering assumption the shipped fix already relied on, now confirmed to hold for the reused primitive too. - -**Real, confirmed structural differences from classic mode that prevent calling `_set_count` unmodified:** -1. Classic mode's popover applies a tab click **live** (no persist step). The Agent panel requires an explicit **Save** click to persist — confirmed live: `aria-selected` flips immediately on click, but reopening the panel without clicking Save reverts to the prior selection. -2. Classic mode's `_set_count` **raises `RuntimeError`** on failure to converge — there's no fallback for classic, so a hard failure is correct there. The agentic driver's natural-language directive is a genuine fallback, so this method must stay **best-effort and never raise** (matches the existing method's contract, which the shipped version violated by leaving the panel open on failure paths and thereby causing `send_prompt` to hard-fail downstream). -3. Classic mode opens its popover via the `crop_*` trigger (`_open_gen_settings_panel`, tries `GEN_SETTINGS_BUTTON_SELECTORS = MODE_SWITCH_TRIGGER_SELECTORS`). The Agent panel opens via the `tune` icon (`AGENT_TUNE_INDICATOR_SELECTOR`, canonical in `factory.py`) — a different trigger entirely. - -**Architecture:** Reuse `_count_tabs_locator`, `_COUNT_TAB_TEXT_RE` (module-level, `ui_automation.py`) and `UiAutomationTransport._is_settings_panel_open` (static method, same file) via late import (matches this module's existing circular-import discipline — `agentic.py` must never import `ui_automation`/`ui_automation_video`/`factory` at module load time). Add Agent-specific glue: open via `tune` (canonical selector from `factory.py`, not a local redefinition — this also fixes the shipped version's `test_selector_symmetry.py`-violating duplicate), a scoped (not page-wide-ambiguous) read-back check on the target tab itself, an explicit Save step, and — the fix for the most severe confirmed bug — **always close the panel via its `arrow_back` header button before returning on any failure/short-circuit path**, so `send_prompt`'s composer click is never blocked by a panel left open. - -**Tech Stack:** Python 3.11+, Playwright async API, pytest + pytest-asyncio, existing `AgenticFlowUiDriver` mock-page test conventions, live e2e via `tests/e2e/` (credit-free — image generation spends no Flow credits). - -## Global Constraints - -- The method must remain **best-effort and never raise** — any failure degrades to the pre-existing natural-language-only behavior, never a hard failure of the generation. -- The method must **always leave the settings panel closed / composer visible** by the time it returns, on every code path (success, short-circuit, or failure) — this is the direct fix for the most severe bug found in review. -- Reuse `AGENT_TUNE_INDICATOR_SELECTOR` from `factory.py` (canonical, `is`-identity-tested by `tests/api/transports/test_selector_symmetry.py`) — do not redefine an equal-looking local copy. -- Reuse `_count_tabs_locator`/`_COUNT_TAB_TEXT_RE` from `ui_automation.py` — do not redefine a local `_IMAGE_COUNT_TABLIST_SELECTOR`. -- All new/changed selectors are locale-invariant (Material Symbol ligatures + structural DOM position, never UI text, never hashed CSS-module class names as the sole anchor). -- Count is validated to 1–4 by `GenerateImageRequest.__post_init__` (`src/gflow_cli/api/image.py:443-444`) — the 4-tab range covers it fully, no clamping needed. -- Scope stays count-only — no aspect/model automation via this panel. - ---- - -### Task 1: Rework `_enforce_image_count_via_settings_panel` to reuse classic mode's count-tab primitives - -**Files:** -- Modify: `src/gflow_cli/api/transports/drivers/agentic.py` -- Modify: `tests/api/transports/drivers/test_agentic.py` - -**Interfaces:** -- Consumes: `_count_tabs_locator(page: Page) -> Locator` and `_COUNT_TAB_TEXT_RE` (module-level, `gflow_cli.api.transports.ui_automation`); `UiAutomationTransport._is_settings_panel_open(page: Page) -> bool` (static method, same module); `AGENT_TUNE_INDICATOR_SELECTOR: str` (module-level, `gflow_cli.api.transports.drivers.factory`). -- Produces: `AgenticFlowUiDriver._enforce_image_count_via_settings_panel(page: Page, count: int) -> None` — same signature as before, replaces the existing implementation entirely. - -- [ ] **Step 1: Read the current state of the file before editing** - -Read `src/gflow_cli/api/transports/drivers/agentic.py` in full (it's ~700 lines) so the replacement lands consistent with the module's existing style (structlog event names prefixed `agentic_driver.`, late-import discipline per the module docstring, `# noqa: PLC0415` on late imports). - -- [ ] **Step 2: Replace the module-level selector constants** - -Replace the existing block (currently lines ~62-120: `_TUNE_BUTTON_SELECTOR`, `_IMAGE_COUNT_TABLIST_SELECTOR`, `_COUNT_BUTTON_TEXT`, `_FIND_SAVE_BUTTON_JS`) with: - -```python -# Agent settings panel (issue #313, reworked 2026-07-16 after code review) — -# driven as a best-effort FALLBACK only, because prompt-encoding is the -# primary mechanism and this popover is the most drift-prone surface on -# Flow's UI (docs/AGENT_UI_RECON.md § "Settings via prompt, not the tune -# popover"). A sticky prior value in this panel can silently override the -# natural-language directive's requested count — that mismatch is issue -# #313's root cause. -# -# Reuses classic mode's count-tab primitives (_count_tabs_locator, -# _COUNT_TAB_TEXT_RE, UiAutomationTransport._is_settings_panel_open) via late -# import — live-verified 2026-07-16 that Flow's Agent settings panel uses the -# SAME role="tab"/aria-selected Radix pattern as classic mode's count -# popover: 8 matching [role="tab"] elements appear when the panel is open (4 -# for "Image generation default", 4 for "Video generation default"), with -# the image section's 4 tabs always rendering first in DOM order. The tune -# open-trigger, the explicit Save requirement (classic's popover applies -# live, this panel does not), and the never-raise contract (classic's -# _set_count raises on failure since it has no fallback; this method DOES -# have a fallback — the natural-language directive — so it must degrade -# instead) are the genuine differences that prevent calling _set_count -# directly. - -# The panel's "Save" button has no icon ligature, no data-* attribute, no -# type="submit", and its text ("Salvar"/"Save"/etc.) is locale-dependent — -# per memory flow-locale-leak-icon-ligatures this cannot be text-matched. -# Locate it structurally instead: walk up from the panel's arrow_back header -# icon to the nearest ancestor that ALSO contains a count tablist (i.e. the -# panel root), then take the last visible