Commit Graph
7 Commits
Author SHA1 Message Date
aeff60cbe8 [57f83a44] Verify and fix all failing CI quality gates from run 28194267886 (#271) (#272) (#273)
* [57f83a44] fix(lint): remove unused imports from autonomous-maintenance code [CI run 28194267886]

The Feat/autonomous-maintenance (#264) merge introduced 4 ruff lint errors
that broke the quality gate:

  F401 roboco/api/routes/project.py:7         unused `cast` import
  F401 roboco/services/self_heal_engine.py:28 unused `cast` import
  F401 roboco/services/self_heal_engine.py:46 unused `UUID` in TYPE_CHECKING
  TC003 roboco/services/telemetry/source.py:18 `Sequence` not in TYPE_CHECKING

Root cause: automated maintenance PR added self_heal_engine.py and
ci_watch_engine.py with imports that became orphaned when the implementation
was refactored. `cast` was imported in both project.py and self_heal_engine.py
but never called. `UUID` was placed in self_heal_engine.py's TYPE_CHECKING
block but not referenced in any annotation. `Sequence` in telemetry/source.py
was imported at module level when it is only used in function-signature
annotations and therefore belongs in TYPE_CHECKING (the file has
`from __future__ import annotations` so this is runtime-safe).

The mypy type-narrowing issue in test_pr_gate_records_verdict.py (the original
AC context: in-body None assignment making subsequent assertions unreachable,
resolved via annotation-typed class attributes) was already fixed in a prior
commit before this task was opened.

Fix: remove the three unused imports; move Sequence into TYPE_CHECKING.
No suppressions, no xfail markers, no coverage threshold changes.
`ROBOCO_ENCRYPTION_KEY='...' make quality` exits 0: ruff format, ruff check,
markdown prose, mypy (0 errors, 819 files), pytest (10197 passed, 95.51%
coverage), xenon, radon mi, vulture, bandit, pip-audit, deptry, alembic
--sql, import-linter, and all foundation drift checks.

* [57f83a44] docs(changelog): document ruff lint fixes from autonomous-maintenance PR

Added comprehensive entry to CHANGELOG documenting the 4 ruff lint errors
(F401 unused imports, TC003 import placement) that were introduced by
Feat/autonomous-maintenance (#264) and subsequently fixed. Documents root
cause (orphaned imports from refactoring) and the TC003 best practice
(type-annotation-only imports belong in TYPE_CHECKING block with
`from __future__ import annotations` for runtime safety).

All quality gates pass: 10197 tests at 95.51% coverage, zero suppressions.

---------

Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev>
Co-authored-by: Backend Documenter <be-doc@agents.roboco.dev>
2026-06-26 02:36:40 +02:00
153723406e Feat/autonomous maintenance (#264)
* feat(ci-watch): config flags

Default-off CI-watch config (mirrors self_heal_*): ci_watch_enabled,
ci_watch_default_workflow (ci.yml), ci_watch_interval_seconds (1800),
ci_watch_max_open_tasks (3), ci_watch_max_per_cycle (1). Registers
ci_watch_enabled in the panel FEATURE_FLAGS. 4 tests.

* feat(ci-watch): per-project ci_watch_enabled/workflow (migration 048)

Adds projects.ci_watch_enabled (bool NOT NULL default false) +
projects.ci_watch_workflow (varchar null) — the per-project opt-in for
multi-repo CI-watch. ProjectTable + Pydantic Project fields + migration 048
(off 047_ws_single_active). Real upgrade->downgrade->upgrade chain verified
against a throwaway Postgres; 2 ORM round-trip tests.

* feat(runtime): prune dangling agent images in the background sweeper

Every agent-image rebuild orphans the prior build's layers as an untagged
<none> image; across deploys these pile up (the operator hit ~80). The sweeper
now runs 'docker image prune -f --filter dangling=true' (dangling only — a
tagged image or one backing a running container is never dangling), throttled
to settings.image_prune_interval_seconds (default 6h) and gated by
image_prune_enabled (default on). Best-effort: any failure is logged, never
raised into the sweeper. Mirrors the transcript-retention prune. 4 tests.

* feat(ci-watch): source tag + open-task dedupe query

CI_WATCH_SOURCE='ci_watch' + TaskService.list_open_ci_watch_tasks(git_url=None):
non-terminal ci_watch tasks (the dedupe + open-cap basis), optionally scoped to
one repo by git_url — a monorepo registers several cell-projects on one git_url,
so dedupe keys on the repo, not the slug. 2 real-PG tests.

* feat(ci-watch): multi-project CI telemetry fan-out

MultiProjectCITelemetrySource.fetch(projects) reuses the hardened per-project
get_latest_ci_conclusion for each opted-in project (passing its ci_watch_workflow
or the configured default). Per-project isolation: a GitHub error or absent
signal yields NO sample (unknown, never read as green) and never aborts the
sweep; only a real conclusion yields a sample (fail→breach, pass→non-breach).
self-heal source untouched. 3 tests + self-heal regression green.

* feat(ci-watch): engine — fan-out, originate, dedupe, cap

CiWatchEngine.run_cycle(projects) mirrors SelfHealEngine: assess via
MultiProjectCITelemetrySource, open one PENDING ci_watch fix task per red repo
(team=main_pm, assigned_to=main-pm, confirmed_by_human=True so it dispatches
without an Approve-&-Start — the fe029fe3 lesson), never starts/approves/merges.
Dedupe per git_url (monorepo → one fix task per repo) + per-cycle/rolling caps.
Default-off; disabled → no-op. 5 real-PG tests (red→one task, dedupe, cap,
green/none→nothing, disabled).

* feat(ci-watch): orchestrator loop tick + watch-set loader

_ci_watch_loop (registered in start(), cancelled in stop(), separate from the
untouched self-heal loop): dormant unless ci_watch_enabled; each interval loads
the watch set (ci_watch_enabled projects, collapsed one-per-repo via the
existing _projects_one_per_repo) and runs CiWatchEngine.run_cycle, committing
opened tasks. _run_ci_watch_cycle extracted for testing; loud warning when
enabled-but-empty. confirmed_by_human=True on the originated task means it
dispatches without an Approve-&-Start (no stranding, the fe029fe3 lesson).
5 tests (disabled no-op, watch-set filter+one-per-repo, empty warn, engine run).

* docs(ci-watch): CHANGELOG + CLAUDE.md for multi-repo CI-watch

Document CI-watch (Added) in the CHANGELOG and the Self-Healing & Feature Flags
section of CLAUDE.md — it generalizes self-heal to opted-in projects, reuses the
hardened per-project CI lookup, never auto-merges, default-off. Adds the
ci_watch_enabled flag to the feature-flags enumeration.

* feat(dep-update): config flags

Default-off dep-update config (mirrors self_heal_*/ci_watch_*): dep_update_enabled,
dep_update_interval_seconds (604800 = weekly), dep_update_max_open_tasks (3),
dep_update_max_per_cycle (1). Registers dep_update_enabled in FEATURE_FLAGS. 4 tests.

* feat(dep-update): per-project dep_update_command/paths (migration 049)

Adds projects.dep_update_command (varchar null) + dep_update_paths (varchar[]
null) — the per-project opt-in for the dependency-update bot. ProjectTable +
Pydantic Project fields + migration 049 (off 048_ci_watch_project_cols). Real
upgrade->downgrade->upgrade chain verified on a throwaway Postgres; 2 ORM tests.

* feat(dep-update): source tag + open-task dedupe query

DEP_UPDATE_SOURCE='dep_update' + TaskService.list_open_dep_update_tasks(git_url=None):
non-terminal dep_update tasks (dedupe + open-cap basis), optionally scoped to one
repo by git_url (monorepo → one open dependency-update task per repo). 2 real-PG
tests.

* feat(dep-update): read-only lockfile-diff probe

WorkspaceService.dry_upgrade_changes_lockfile(project): clones the project's
read clone into a throwaway dir (--no-hardlinks, so the read clone is never
mutated), runs project.dep_update_command (no shell, shlex.split), and reports
whether any lockfile path (dep_update_paths or inferred uv.lock/pnpm-lock.yaml)
is dirty. Fail-safe: null/failing command → False (don't originate on a broken
probe), logged; throwaway always removed; never commits/pushes. 5 real-git tests.

* feat(dep-update): engine — detect, originate, dedupe, cap

DepUpdateEngine.run_cycle(projects) mirrors SelfHealEngine/CiWatchEngine: for
each opted-in project (dep_update_command set) with updates available (the
read-only probe), open one PENDING dep_update task (team=main_pm, assigned-to
main-pm, confirmed_by_human=True), never starts/approves/merges. Cheap checks
(command, per-git_url dedupe) before the expensive probe; per-cycle + rolling
caps. Default-off; disabled → no-op. 6 real-PG tests.

* feat(dep-update): weekly orchestrator loop tick

_dep_update_loop (registered in start(), cancelled in stop(), separate from the
self-heal + CI-watch loops): dormant unless dep_update_enabled; each interval
(default weekly) loads projects with a dep_update_command (one-per-repo) and runs
DepUpdateEngine.run_cycle, committing opened tasks. _run_dep_update_cycle
extracted for testing; loud warning when enabled-but-no-commands. Refactored
stop() to cancel background tasks via a shared _cancel_background_task loop
(keeps it under xenon B as the loop count grows). 4 loop tests.

Task 7 (anti-stranding dispatch guard) is satisfied by construction: no
dispatcher skip targets source='dep_update', and the engine sets
confirmed_by_human=True (the fe029fe3 lesson), asserted in the engine tests —
so the originated task dispatches via the assigned-PM path, never stranded.

* docs(dep-update): CHANGELOG + CLAUDE.md for the dependency-update bot

Document the dep-update bot (Added) in the CHANGELOG and the Self-Healing &
Feature Flags section of CLAUDE.md — read-only lockfile-diff probe, never
auto-merges, per-project opt-in via dep_update_command, default-off. Adds the
dep_update_enabled flag to the feature-flags enumeration.

* feat(ci-watch): route fix-task notification to the project's cell PM

On opening a fix task, CiWatchEngine notifies the red project's own cell PM
(resolved from project.assigned_cell via foundation AGENTS — e.g. BACKEND →
be-pm), not the CEO, once per project per cycle. Best-effort: a notification
failure never rolls back the origination. Adds _cell_pm_slug_for +
_notify_cell_pm. 1 real-PG test (asserts to_agent='be-pm', not 'ceo').

* feat(ci-watch,dep-update): expose per-project opt-ins in the project API

Add ci_watch_enabled/ci_watch_workflow + dep_update_command/dep_update_paths to
ProjectUpdate, ProjectUpdateRequest, the PATCH route mapping, ProjectResponse,
and project_to_response — so the panel edit-project dialog can read + set the
per-project autonomy opt-ins (the columns were unreachable through the API
before). Also threads the previously-dropped quality_command through the update
route. 1 real-PG update round-trip test.

* feat(ci-watch,dep-update): panel project-edit fields for the per-project opt-ins

Adds an 'Autonomous Maintenance' section to the edit-project dialog: a CI-watch
enable switch + workflow input, and a dependency-update command + lockfile-paths
input (comma-separated → list). Threads the four fields through the Project /
ProjectUpdate TS types and the mock-mode create fixture. The global on/off
toggles already live in Settings → Feature Flags; these are the per-project
opt-ins. panel tsc --noEmit + eslint green.

* docs(0.12): CI-watch + dep-update bot + image-prune across user docs + RAG

New docs/optional/autonomous-maintenance.md (mirrors self-heal.md) covering both
engines; optional/index rows; panel settings + projects-and-products notes for
the Feature Flags toggles + the edit-project Autonomous Maintenance fields;
resilience note for the dangling-image prune; env-reference + RAG config-reference
tables for all ROBOCO_CI_WATCH_* / ROBOCO_DEP_UPDATE_* / ROBOCO_IMAGE_PRUNE_*
vars; mkdocs nav entry. reflow-check green; prompts unchanged (operator-facing,
not agent-facing).

* chore(release): 0.12.0

Cut [Unreleased] -> [0.12.0] (CI-watch + dep-update bot + image-prune housekeeping
+ the post-0.11.1 run-hardening fixes). Bumps all 8 canonical version refs to
0.12.0 (pyproject / uv.lock roboco pkg / panel package.json / __init__ /
config.app_version + the README / deployment / agent-image-tag examples).

* fix(pr-review): repo-scope external-PR dedupe (no duplicate review on a monorepo)

external_review_task_exists keyed on (project_id, pr, head_sha), but a monorepo
registers several cell-projects on one git_url and the poll already collapses to
one canonical project per repo — so once a review task was re-pointed to a
sibling project, the next poll (checking the canonical project) no longer saw it
and opened a second review of the same PR (observed: PR #131 reviewed once on
guard-core-saas-frontend, once on -backend). Dedupe now spans every project
sharing the PR's repo (git_url); re-review on a new head SHA still works; a
genuinely different repo with the same PR number is independent. 3 real-PG tests.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-06-25 21:11:36 +02:00
Renn F 17ec52d1b7 feat(conventions): generalize defaults, backfill old projects, adopt the standard in-repo
Harden the architectural-conventions standard so it works out-of-the-box on
any project and resolves for projects that predate it, and make RoboCo pass
its own gate.

General defaults (apply to every project, not just one with a tuned file):
- The auto-scan excludes test and documentation trees (tests/, docs/) — those
  legitimately define fixtures and aren't enforced code.
- Helper placement seeds at warn, not block: `helper` matches any top-level
  function, too blunt a signal to hard-block a route file's small private glue.
  Misplaced model/route/component stay block; the body-level thin_routes check
  remains the real fat-handler guard.
- thin_routes no longer counts transaction-lifecycle calls (commit/flush/
  refresh) as data access — an explicit `db.commit()` after delegating to a
  service is a valid pattern.
- no_lint_suppressions exempts a small allowlist of structurally-unavoidable
  framework codes (ruff TC001-TC003, pydantic prop-decorator); bare or other
  suppressions still flag.
- CLAUDE.md rule-lifting skips bare common-word tokens that would match
  everywhere (e.g. "commit"), keeping only specific identifiers.
- The ambient prompt block lists only constrained modules and truncates at a
  line boundary with a "+N more" pointer instead of cutting mid-line.

Backfill: the standard previously read the committed file + repo scan from
project.workspace_path, a field only a manual API call set — so an older
project (or one whose workspace was cleared) showed an empty "missing" map no
matter what was pushed. The service now ensures a dedicated, default-branch
read clone on demand (WorkspaceService.ensure_read_clone) and resolves from
it, persisting the resolved path + real HEAD. The panel tab, the spawn-time
ambient block, and the per-task constraints all resolve the committed standard
with no manual setup.

Adopt in-repo: relocate the inline request/response models from the system and
*_live route modules into roboco/api/schemas/ so the codebase passes its own
placement gate, and ship a canonical .roboco/conventions.yml. no_models_in_routes
and modular_cohesion are now clean and enforced at block.

Docs updated across the user guide, the agent-facing RAG standard, the
developer and pr_reviewer role prompts, CLAUDE.md, and the changelog. New unit
tests cover the scan exclusions, helper-warn, the suppression allowlist, the
commit exemption, and the resolve/backfill path; the conventions + project
integration suites pass against Postgres.
2026-06-22 18:15:19 +02:00
16789c1ca7 Feature/architectural conventions standard (#243)
* feat(conventions): standard schema models + effective-map merge

* feat(conventions): tree-sitter Python classifier + placement checks

* feat(conventions): TS classifier, hygiene/custom checks, runner + CLI

* feat(conventions): ROBOCO_CONVENTIONS_ENABLED flag + cache table + migration

* feat(conventions): repo auto-scan + scaffold draft renderer

* feat(conventions): ConventionsService (cache/baseline/ambient/scaffold/restore)

* feat(conventions): auto-scaffold on project registration (flag-gated)

* feat(conventions): TaskDescription.constraints + auto-baseline attach

* feat(conventions): ambient architecture-map injection at spawn

* test(conventions): subprocess CLI smoke for the agent-image entrypoint

* feat(conventions): block i_am_done on block-level convention violations

* feat(conventions): block pr_pass on unresolved convention violations

* feat(conventions): surface convention findings into QA evidence

* docs(prompts): convention awareness for PO/Intake/Dev/QA/PR-reviewer

* feat(conventions): panel Conventions tab + flag toggle + parity

* test(conventions): end-to-end block, fix, and waiver through the gate

* refactor(conventions): extract pr_pass guards to keep pr_gate under the gate

* style(conventions): format the baseline-constraints attach in task.create

* test(conventions): type-annotate test helpers for the full mypy gate

* build(conventions): ignore types-PyYAML in deptry (mypy-only type stub)

* docs(conventions): document the standard in CLAUDE.md + PM prompt awareness

* fix(conventions): baseline constraints are non-suppressible (dedup-append)

* feat(conventions): scaffold on first workspace clone (threaded workspace)

* feat(conventions): multi-project ambient map for PO/Intake (per-product)

* feat(conventions): persist findings + violations-feed route (migration 044)

* feat(conventions): panel violations feed in the Conventions tab

* test(conventions): intake-spawn mock accepts the ambient layer kwarg

* fix(docker): ollama-init best-effort pull, gate startup on cached models present

A degraded/slow ollama registry made the model manifest re-check fail under
set -e, so ollama-init exited 1 and blocked the orchestrator's
service_completed_successfully gate — taking the whole stack down even though
both models were already cached. Pulls are now best-effort; success is gated on
the models being present, so a flaky registry can't down a cached deployment.

* refactor(content): drop dead TaskDescription.with_baseline_constraints

The structured baseline-merge helper had zero production callers. Project-task baseline constraints are attached by the wired string backstop (TaskService._attach_baseline_constraints), and a real task is free-form prose that cannot form a valid TaskDescription (requires a non-trivial objective + non-empty the_work), so the helper was unreachable from any live path — a leftover from the structured-merge -> string-append design pivot. Removing it leaves a single enforcement path. The constraints field itself stays: it is a member of the well-formed-spec schema (Objective / What This Builds / The Work / Notes / Constraints / Acceptance Criteria), rendered by render_markdown and unit-tested.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-06-22 12:37:46 +02:00
Renn F 254cc93fd5 It gets to documenter but QA fails false positive then it gets blocked because it needs to be awaiting_pm_review to be able to merge. It's almost there. 2026-05-01 05:41:10 +02:00
Renn F 955455d704 Git integration and Project workspace fixes 2026-01-08 16:45:25 +01:00
Renn F 22d1401020 First git integration implementation + KB fixes and upgrades 2025-12-30 18:55:32 +01:00