From 3338f88e1bb28f5aed1c8b1b991d5a1c7ac9c980 Mon Sep 17 00:00:00 2001 From: Renn F Date: Tue, 14 Jul 2026 14:49:52 +0200 Subject: [PATCH] chore(release): 0.24.0 --- CHANGELOG.md | 13 +++++++++++++ panel/package.json | 2 +- pyproject.toml | 2 +- roboco/__init__.py | 2 +- roboco/config.py | 2 +- uv.lock | 2 +- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048ee5fb..e7ee6ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +## [0.24.0] - 2026-07-14 + ### Added - **Scheduled auditor sweeps.** `ROBOCO_AUDIT_INTERVAL_SECONDS` (default 21600s / 6h, `audit_interval_seconds` in `roboco/config.py`) drives a periodic auditor spawn. `_dispatch_audit_work` now spawns the auditor on a scheduled sweep when the interval has elapsed, the auditor is not already active, and recent delivery activity exists (active delivery states or a task completed within the window). Reactive alert spawns also stamp `_last_audit_spawn_at` so the interval gate is shared. A one-tick notification sentinel and the existing active-agent breaker prevent auditor spawn storms; `0` disables scheduled sweeps. The auditor identity prompt and `_build_audit_prompt(scheduled=True)` support sweep-based reviews. - **E2E smoke test for auditor triggers.** `tests/e2e_smoke/test_auditor_triggers.py` exercises both auditor spawn paths end-to-end against the real orchestrator dispatcher: a scheduled sweep that sees recent delivery activity and a reactive `ALERT` created by `POST /api/tasks/{id}/fail-qa`. `spawn_agent` is stubbed so the test asserts the dispatch decision without running an auditor container. The e2e harness now mounts `/api/notifications` so `_dispatch_audit_work` can poll alert rows. - **Docs-divergence sync engine (default-off).** With `ROBOCO_DOCS_SYNC_ENABLED`, a successful release publish now hands the release to `DocsSyncEngine`, which originates exactly one PENDING Main-PM docs-update task against the `roboco-website` project per release tag. The task carries the release's drafted CHANGELOG section and a pointer to the divergence checklist (declared-vs-actual agent count, stale verb-surface tables) so the public docs at docs.roboco.tech reflect what actually shipped. Bounded by `ROBOCO_DOCS_SYNC_MAX_OPEN_TASKS` (default 3) and `ROBOCO_DOCS_SYNC_MAX_PER_CYCLE` (default 1), deduped per release version via the `docs_sync_release_version` marker, and never auto-merges — the docs update still ships through the normal dev → QA → PR-review → CEO-merge gates. If `roboco-website` is not registered as a project the engine logs a warning and no-ops. +- **Sandbox DB extensions/modules on the fly.** A project may declare `sandbox_extensions` (migration 072, jsonb null) — a per-service extension/module map, e.g. `{"postgres": ["vector", "postgis"], "redis": ["search"]}` — activated post-ready via `docker exec` (`CREATE EXTENSION IF NOT EXISTS` / `MODULE LOAD`) and verified (`pg_extension` / `MODULE LIST`), never via bind-mounts or initdb. `request_sandbox(extensions=...)` unions a per-call override with the project's standing set, bounded to the opted-in service set plus the fixed allowlists `SANDBOX_PG_EXTENSIONS` (vector/postgis/pg_trgm/citext/uuid-ossp) and `SANDBOX_REDIS_MODULES` (search/json/bloom); `plpython3u` and other superuser-RCE vectors are excluded by construction. `image_for(features)` selects the light upstream image for bare requests and the kitchen-sink image (`pgvector/pgvector:pg16` + postgis apt for postgres, `redis/redis-stack-server` for redis) only when features are requested, so existing opters stay byte-for-byte bare. Cache-by-features: a cached entry satisfies a new call iff services are a subset AND every requested feature per service is already cached — a feature superset re-provisions (rotates creds); `available_extensions` rides the evidence payload so an agent never guesses what was activated. The project edit dialog exposes a per-service Switch picker grouped under each enabled service. See `docs/rag/architecture/sandbox-db.md` and `docs/internal/specs/2026-07-13-sandbox-extensions-on-the-fly.md`. +- **Revision findings ledger.** `fail_review`, `pr_fail`, `request_changes`, and `ceo_reject` now take structured `findings: list[dict]` validated into `Finding` (`roboco/foundation/policy/content/models.py`) — `file` (repo-relative, ≤300 chars, no `..`/absolute), `line` ≥1, `severity` (blocker|major|minor|nit), `criterion` (≤500, must match an AC id or its exact text), `expected`/`actual` (≤300), `fix` (≤500), `evidence` (≤2000) — persisted as one append-only row per finding into `task_review_findings` (migration 071; `origin` qa|pr_gate|pm|ceo, `round` = `revision_count+1` read pre-transition, `status` open→addressed→verified|waived) via `ReviewFindingsRepository`, then mirrored into `qa_notes`/`pr_reviewer_notes`/`pm_notes` (new `tasks.pm_notes` column + `PmReviewContent`) as a deterministic per-finding rendering `[F-id8] file:line (severity) — expected → actual → fix`. Count guardrails at the verb chokepoint (`roboco/services/gateway/choreographer/findings.py`): soft nudge above `FINDINGS_NUDGE_COUNT` (5), hard reject above `FINDINGS_HARD_CAP` (10) in one call; `criterion` must resolve against the task's acceptance criteria or the envelope refuses. Legacy `issues=[...]` stays as a deprecated shim for one release (each string → a file-less `severity=major` finding, deprecation-logged) and merges with `findings` rather than one silently dropping the other; `ceo_reject` now validates its reason (no more empty-trivial 500) and stamps one `origin=ceo` `blocker` finding. Resolution: `i_am_done`, `submit_up`, and `submit_root` take `resolved_findings` (`{finding_id, commit?, note?}`) gated by `Requirement.FINDINGS_ADDRESSED` — every OPEN finding must be named by an unambiguous 8-char prefix match against the rendered `[F-id8]` or the envelope rejects, listing the still-open ids; `pass_review`/`pr_pass`/`complete` bulk-verify their own origin's `addressed` findings same-transaction, `ceo_approve` stamps `ceo`-origin best-effort, and the ledger mutation is owner-gated so a stale non-owner PM can never mutate it. Delivery: `evidence()`/`build_task_handoff` carry `revision_findings` (open only, capped) so a bounced dev gets what the role docs promise; `claim_review`/`claim_gate_review` carry the full `prior_findings` ledger so round-2+ reviewers check prior findings instead of re-deriving them; the `REVISION_REQUIRED` spawn prompt and PM triage "bounced" block render open findings inline; A2A fail bodies share it; `GET /api/tasks/{id}/findings` (capped 500, SQL-aggregated per-origin/status `summary` + `total`/`truncated`) backs the panel's task-detail Findings tab and a `bounced xN` header chip (`revision_count`); metrics attribute `pm_rejects`/`ceo_rejects` + open/total findings counts per task; vault task notes render a capped `## Findings` section (fail-open fetch, never blocks the note write). New audit events `task.request_changes`/`task.ceo_reject` join `task.qa_fail`/`task.pr_fail` in `_audit_events_for` so rework metrics attribute every bounce kind. See `docs/map/review-findings.md` and `docs/rag/architecture/review-findings.md`. +- **Auditor `waive_finding` verb + findings queue panel.** The auditor-only `waive_finding` flow verb (`IntentSpec` in `roboco/foundation/policy/lifecycle.py`, auto-derived into the auditor manifest; `flow_auditor` route + `flow_server` MCP tool) wires the long-unwired `ReviewFindingsRepository.mark_waived` to a severity-scoped closure: only `minor`/`nit` open findings are waivable, `blocker`/`major` must be fixed and are hard-refused, a `note` explaining the waive is required, and no task status change occurs — the ledger row moves `open → waived` and a `task.finding_waived` audit event (info, note capped 300) records the decision. Already-waived/addressed/verified rows are immutable (refused), and a missing finding 404s with a remediate pointing at `GET /findings` or `triage()`. Registered in `VERBS_WITHOUT_TRACING` (the note + audit event is the durable rationale — no `journal:decision` needed, mirroring `declare_coverage`). The auditor dashboard gains a `findings` field (`ReviewFindingsRepository.list_open_findings`, cross-task, blocking-severity-first then newest, capped 20) surfaced in a new read-only `FindingsQueuePanel` (`panel/src/components/auditor/findings-queue-panel.tsx`) restoring the 4-card auditor layout — each row links to its task, severity-colored, with an open-count destructive badge. `GET /api/tasks/{id}/findings` remains the full per-task ledger view (the dashboard is a glance, not the ledger). See `roboco/services/gateway/choreographer/board.py`. +- **Obsidian vault V2 — drift janitor, archival, weekly report, KB ingest (default-off, gated on `ROBOCO_OBSIDIAN_VAULT_ENABLED`).** The V2 spec ships four pieces on top of the V1 materializer: a **drift janitor** (`roboco/services/vault_janitor.py`, hourly-ticked `_vault_janitor_loop`) running a daily changed-task re-projection + random-sample drift check + archival pass — each capped at 200/cycle and per-item isolated so one bad row never wedges the sweep — restart-proof via a `RoboCo/_meta/.janitor_state.json` state file so real work and the weekly org-report (`vault_report_enabled`, default true — `VaultWriter.write_org_report` from `MetricsService`/`UsageService` into `Reports/.md`, once per ISO week with a best-effort CEO notification) each fire exactly once per elapsed period regardless of loop/restart cadence; **archival** (`vault_archive_days`, default 30, `0`=off) moving old terminal tasks' notes into `RoboCo/Archive//Tasks//` through one `write_task` code path shared with rebuild, alias links making the move free and the shipped Dataview/graph assets `Archive/`-aware; **KB ingest** (`vault_kb_enabled`, default false — NAS compose arms it `true`, registry `false`) embedding the CEO's `RoboCo/Notes/` (config `vault_kb_dirs`, csv, load-time-validated against traversal/overlap with reserved projection dirs) into a new `IndexType.VAULT_NOTES` corpus via `_vault_kb_loop` (`roboco/services/vault_kb_engine.py`, default 900s), with every note re-checked for symlink/path-escape at read time and screened through the injection guard as a hard GATE — a flagged note is quarantined (skipped, logged, callout-marked, never embedded) rather than the intake watcher's screen-and-still-process posture — reaching `roboco_kb_search`, `MentorService`'s default domain, and `EvidenceRepo.similar_memory` (claim-time briefings, relevance-floored, kind `vault_note`); plus **Bases views** (`Task Board.base`, `Reports.base`) and the Mac sync runbook shipped as vault assets. No migration (the `chunks` table auto-creates; migration 030's `CHUNK_TABLES` tuple appended per the chunks_playbooks precedent). See `docs/internal/specs/2026-07-11-obsidian-vault-v2.md`. +- **Intake technical depth now carries down the delegation chain.** Intake's file:line targets, code examples, and rationale were getting lost as a task traveled umbrella → root-subtask → cell → dev — the detail survives in `Task.description`, but PMs were re-authoring it away and the intake prompt never demanded depth. `EvidenceRepo.ancestor_context_for_task` now walks the parent chain (cycle-guarded, depth-capped 16, desc-clipped 1500) and surfaces it as `parent_context` in the evidence payload; `EvidencePayload` gains `description` + `parent_context` (omit-when-empty, no null noise); `_description_body` (capped 4000) injects the description into the dev spawn prompt + SessionStart briefing. The `main_pm`/`cell_pm`/`developer`/`prompter` role prompts teach pass-the-torch-don't-dim-it, the prompter now demands `file:line`/code-examples in `the_work`/`notes` (reconciled with the no-code-level-ACs-on-roots rule), and `main_pm`'s brief-not-a-spec is scoped so facts forward verbatim while the solution stays non-binding. See `docs/internal/specs/2026-07-13-intake-technical-depth.md`. ### Fixed @@ -20,6 +27,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **Python quality gate type hygiene on the auditor-revival branch.** `roboco/services/task.py:get_all_descendants` now uses `cast("UUID", child.id)` instead of `# type: ignore[arg-type]` for the SQLAlchemy `Mapped[UUID]` value, and `roboco/services/notification_delivery.py` narrows the return types of `get_ack_status` and `get_delivery_summary` from bare `dict` to `dict[str, Any]`. These are typing-only changes; runtime behavior is unchanged and the local ruff / mypy quality gate stays green. - **Restored task.py auditor alerts and descendant-traversal cast after the docs-sync PR regression.** `roboco/services/task.py` again calls `_alert_auditor_of_rework` immediately after `await self.session.flush()` in `fail_qa`, `pr_fail`, and `request_changes`, matching the pre-regression reactive auditor ALERT path; the `_supersede_replacement_landed` descendant loop reverts to the original unquoted `cast(UUID, child.id)` form with a scoped `# noqa: TC006`. `DOCS_SYNC_SOURCE` and `list_open_docs_sync_tasks` were not touched. - **Docs-sync version-scoped query uses the generic JSON accessor.** `list_open_docs_sync_tasks(version=...)` in `roboco/services/task.py` now compares `TaskTable.orchestration_markers[markers.DOCS_SYNC_RELEASE_VERSION].as_string()` instead of the JSONB-specific `.astext`, because `orchestration_markers` is declared as generic `JSON`. This fixes the `AttributeError` raised by Postgres-backed integration tests and keeps the docs-sync dedupe/cap predicate in SQL. +- **PR-review and QA now judge coherence and intent, not just the AC checklist.** The `qa.md` and `pr_reviewer.md` role prompts gain a Coherence & intent rule — intent read from `description` + `parent_context`, coherence with project patterns and standards — and `Finding.criterion` is now optional so a criterion-less major finding can flag intent drift. `parent_context` + `description` are wired into the gate/QA/inbound-PR evidence builders (fail-open, logged), so a round-2+ reviewer sees upstream intake analysis instead of re-deriving it blind. +- **Settings preferences persist as real client prefs instead of 422-ing as theater.** The Settings page PUT four keys (`notifications_enabled`, `sound_enabled`, `auto_refresh`, `refresh_interval`) the backend's `services/settings.py` `_VALIDATORS` allowlist never accepted — Save died on the first 422 and nothing consumed the prefs anywhere (no auto-refresh timer, no notification toast, no sound existed). The four prefs move into the persisted UI store (`useUIStore`, client-only, same idiom as theme/sidebar) and the cards apply instantly; the dead server plumbing and global Save button are gone, the backend allowlist stays strict and untouched. New `AutoRefreshDriver` ticks the page-refresh registry every N seconds when Auto Refresh is on (default-off, skips while nothing is registered or a refresh is in flight); new `NotificationAlerts` toasts each newly-arrived WS notification with an optional ~120ms Web-Audio chime (initial backlog never toasts, one chime per batch, autoplay blocks never throw). Lesson: a panel control persisting server-side must name a key in `_VALIDATORS`; client-only prefs belong in `useUIStore`. +- **Pre-set `branch_name` is trust-but-verified against origin before push-by-name.** A `branch_name` set on a task was treated as proof the ref existed on origin, so `_finalize_claim` skipped `_ensure_branch_for_task` and `create_branch`/push never ran — a manual field write or a prior failed `create_branch` whose rollback didn't restore `branch_name` left the field set while the branch was never pushed; descendants then `ls-remote`'d the name, found it empty, and cut from `master` via `create_branch`'s silent fallback, breaking the cell→root branch hierarchy (MegaTask `f7d0a61a` root-branch 404). `_ensure_branch_for_task` now probes origin and, when the ref is confirmed missing, runs the full create to push it (an inconclusive probe fails soft so a transient glitch can't fail a normal resume claim); `_finalize_claim` always runs the chokepoint and snapshots+restores `branch_name` on rollback so a failed first attempt can't leave the field half-set. Gated on `project_id` so branchless coordination/umbrella tasks are untouched; new `GitService.branch_exists_on_remote` returns True/False/None (fail-soft). +- **Task-detail tab state lives in the URL; nav, kanban overflow, and sidebar divider fixed.** The active tab now persists in `?tab=` (survives reload, back/forward, prev/next task jumps); the prev/next arrows moved into the header row next to Actions instead of their own row above the title; the Constraints section starts collapsed (project boilerplate). Kanban swaps Radix ScrollArea for native overflow scroll (the `display:table` viewport let cards grow past the column and clip), columns share width (`flex-1`, 18rem floor, 24rem cap), and dark column colors normalize to `/40` tints. The sidebar footer drops the doubled `Separator` (the wrapper's `border-t` already carries it). A self-providing `Tooltip` root (300ms) plus hover hints lands across sidebar, header, task detail, kanban, and every icon-only button that had none. +- **Systematic tooltip and aria-label pass across the entire panel.** Per a new `tooltip-aria-label-spec.md`, accessible icon-only controls now carry an `aria-label` plus a matching `Radix Tooltip` — 8 controls retrofitted (bell, back-arrow, menu, toggle, drag-handle, move-forward, settings, review-link) plus the `assignee-avatar` full-name tooltip. Regression tests confirm the bell's `aria-label`/`title`/`Tooltip`; a follow-up wrapped the refresh button in a `Tooltip` (dropping the redundant native `title=`), fixed missing `TooltipProvider`/`Link`/`ArrowLeft` imports that were blocking Panel lint + the QA image build, deduped a `command-center` tooltip import, closed remaining a11y gaps (task-table row-expand + pagination `aria-label`s, work-session truncated `task-id`/`branch` `title`s, secretary Start loading label), and reflowed the doc prose for the hard-wrap gate. +- **Compose vault mount divergence (`docker-compose.yaml`).** `docker-compose.yaml` had drifted from `docker-compose.yml` and was missing the orchestrator's `${ROBOCO_DATA_DIR:-./data}/vault:/app/vault` bind mount plus the `ROBOCO_VAULT_PATH` env (and every other vault env), so `vault_path` fell back to its config default `/data/vault` (`roboco/config.py`, not bind-mounted) and notes were silently written into the container overlay instead of the host. Making `.yaml` byte-for-byte identical to `.yml` (commit `cf668bd9`) restored the `/app/vault` mount and the full vault env block, so the host volume receives projections again. ## [0.23.0] - 2026-07-11 diff --git a/panel/package.json b/panel/package.json index 0ffdeb4a..a15f4ee1 100644 --- a/panel/package.json +++ b/panel/package.json @@ -1,6 +1,6 @@ { "name": "roboco-panel", - "version": "0.23.0", + "version": "0.24.0", "private": true, "packageManager": "pnpm@11.10.0", "scripts": { diff --git a/pyproject.toml b/pyproject.toml index 95caabda..02373d04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "roboco" -version = "0.23.0" +version = "0.24.0" description = "AI Agents Company - A virtual organization of AI agents functioning as a software development workforce" authors = [ {name = "Renzo Franceschini", email = "rennf93@users.noreply.github.com"} diff --git a/roboco/__init__.py b/roboco/__init__.py index 07f942cd..ec1e5032 100644 --- a/roboco/__init__.py +++ b/roboco/__init__.py @@ -5,7 +5,7 @@ A virtual organization of 25 AI agents + 1 human CEO, designed to operate as a complete software development workforce. """ -__version__ = "0.23.0" +__version__ = "0.24.0" # Core exports from roboco.config import settings diff --git a/roboco/config.py b/roboco/config.py index 97cd0ad1..966b2f6d 100644 --- a/roboco/config.py +++ b/roboco/config.py @@ -36,7 +36,7 @@ class Settings(BaseSettings): # ========================================================================== # Application # ========================================================================== - app_version: str = "0.23.0" + app_version: str = "0.24.0" debug: bool = False environment: str = Field( default="development", pattern="^(development|staging|production)$" diff --git a/uv.lock b/uv.lock index afe70f6e..03197476 100644 --- a/uv.lock +++ b/uv.lock @@ -2537,7 +2537,7 @@ wheels = [ [[package]] name = "roboco" -version = "0.23.0" +version = "0.24.0" source = { editable = "." } dependencies = [ { name = "alembic" },