feat(bug-fast-track): cheaper/shorter pipeline route for bug-fix tasks (ORCH-019)
A task carrying the Plane `Bug` label takes a shortened route that skips the `architecture` stage (one opus architect run + ADR + check_architecture_done), replacing heavy analysis with a lite package (bug-report + mandatory regression test plan). EVERY Quality Gate / sub-gate runs UNCHANGED — the route is a scheduler property, not a gate (root invariant NFR-1): STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict keys are byte-for-byte preserved. - src/bug_fast_track.py: new leaf (never-raise) — bug_fast_track_applies (local, network-free, checked first), is_bug_task (labels.has_label, Plane API source), skips_architecture (pure DB-backed routing predicate), snapshot. - src/db.py: additive idempotent tasks.track column (TEXT DEFAULT 'full') + set_task_track / get_task_track helpers (missing/NULL -> 'full', fail-safe). - src/stage_engine.py: routing-override on the analysis-exit edge (track='bug' -> development/developer, skipping architect); brd-review-clock stamp extended to analysis->development. get_next_stage/get_agent_for_stage stay pure. - src/webhooks/plane.py: classify task as bug in start_pipeline (applies-first short-circuit; never-raise -> full cycle on any error). - src/main.py: additive bug_fast_track block in GET /queue + POST /bug-fast-track/escalate (reset 'bug'->'full' to return to the full cycle). - src/config.py: bug_fast_track_enabled / _label / _repos flags (empty CSV -> self-hosting only). - src/notifications.py: optional 🐞 marker on the bug-track card (never-raise). - Prompts: analyst.md (lite bug package + escalation), reviewer.md (regression- test axis) — 52d canon preserved. - Docs: CLAUDE.md, README.md (env + API + section), docs/architecture/README.md, CHANGELOG.md, .env.example. - Tests: tests/test_bug_fast_track*.py + test_db_migrations.py + queue block (TC-01..TC-15). Full regression green (1551 passed). Kill-switch ORCH_BUG_FAST_TRACK_ENABLED=false -> 1:1 pre-ORCH-019 (zero regression; residual track column harmless). Refs: ORCH-019 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
47
src/main.py
47
src/main.py
@@ -212,6 +212,7 @@ async def queue():
|
||||
from . import fs_normalize
|
||||
from . import labels
|
||||
from . import cancel
|
||||
from . import bug_fast_track
|
||||
from .disk_watchdog import disk_watchdog
|
||||
from .build_cache_pruner import build_cache_pruner
|
||||
return {
|
||||
@@ -243,6 +244,10 @@ async def queue():
|
||||
# repo scope, cancelled/deferred counts, recent cancellations. Additive block;
|
||||
# never-raise.
|
||||
"stop": cancel.snapshot(),
|
||||
# ORCH-019 (FR-7 / AC-7): bug-fast-track observability (read-only) —
|
||||
# kill-switch, label, scope, bug-task counts + the structural savings metric
|
||||
# (architecture stages skipped). Additive block; never-raise.
|
||||
"bug_fast_track": bug_fast_track.snapshot(),
|
||||
# ORCH-063 (FR-6 / AC-7): disk-watchdog observability (read-only) —
|
||||
# enabled, threshold, interval, last measurement per host-path. Additive
|
||||
# block; never-raise (status() returns {"enabled": ...} minimum on error).
|
||||
@@ -343,3 +348,45 @@ async def coverage_set_baseline(repo: str = "", value: float | None = None):
|
||||
repo = repo.strip()
|
||||
ok = db.set_coverage_baseline(repo, value, sha="manual-override")
|
||||
return {"ok": ok, "repo": repo, "baseline": db.get_coverage_baseline(repo)}
|
||||
|
||||
|
||||
@app.post("/bug-fast-track/escalate")
|
||||
async def bug_fast_track_escalate(work_item: str = ""):
|
||||
"""ORCH-019 (FR-5 / AC-5, ADR-001 D5): escalate a bug-fast-track task to the
|
||||
full cycle (return it to the route WITH `architecture`).
|
||||
|
||||
Operator path for a bug that turned out to be complex / architectural / visual
|
||||
(needs an ADR or a mock): reset ``tasks.track`` 'bug' -> 'full'. Apply while the
|
||||
task is still in `analysis` (before its exit) — the next advance_stage then routes
|
||||
analysis -> architecture normally. By образцу ``POST /serial-gate/unfreeze`` /
|
||||
``POST /coverage/baseline``. never-raise.
|
||||
"""
|
||||
from . import db
|
||||
if not work_item or not work_item.strip():
|
||||
return {"ok": False, "error": "missing 'work_item'", "work_item": work_item}
|
||||
work_item = work_item.strip()
|
||||
task = db.get_task_by_work_item_id(work_item)
|
||||
if not task:
|
||||
return {"ok": False, "error": "unknown work_item", "work_item": work_item}
|
||||
prev_track = task.get("track") or "full"
|
||||
db.set_task_track(task["id"], "full")
|
||||
if prev_track == "bug":
|
||||
try:
|
||||
from .notifications import send_telegram
|
||||
send_telegram(
|
||||
f"🐞➡️ {work_item}: эскалация в ПОЛНЫЙ цикл "
|
||||
f"(багфикс-трек снят, стадия architecture восстановлена)."
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from .plane_sync import add_comment
|
||||
add_comment(
|
||||
work_item,
|
||||
"🐞➡️ Эскалация: задача возвращена в полный цикл "
|
||||
"(багфикс-трек снят, стадия architecture восстановлена).",
|
||||
author="analyst",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": True, "work_item": work_item, "track": "full", "was": prev_track}
|
||||
|
||||
Reference in New Issue
Block a user