kin: KIN-135-backend_dev

This commit is contained in:
Gros Frumos 2026-03-20 21:56:46 +02:00
parent 4c01e0e4ee
commit 24be66d16c
9 changed files with 436 additions and 6 deletions

View file

@ -23,6 +23,16 @@ TASK_CATEGORIES = [
"ARCH", "TEST", "PERF", "DOCS", "FIX", "OBS",
]
# Valid categories for task returns (KIN-135)
RETURN_CATEGORIES = [
"requirements_unclear",
"scope_too_large",
"technical_blocker",
"missing_context",
"recurring_quality_fail",
"conflicting_requirements",
]
def validate_completion_mode(value: str) -> str:
"""Validate completion mode from LLM output. Falls back to 'review' if invalid."""
@ -1301,3 +1311,97 @@ def get_chat_messages(
query += " ORDER BY created_at ASC, id ASC LIMIT ?"
params.append(limit)
return _rows_to_list(conn.execute(query, params).fetchall())
# ---------------------------------------------------------------------------
# Task returns — escalation tracking (KIN-135)
# ---------------------------------------------------------------------------
def record_task_return(
conn: sqlite3.Connection,
task_id: str,
reason_category: str,
reason_text: str | None = None,
returned_by: str = "system",
pipeline_id: int | None = None,
) -> dict:
"""Record a task return to PM and increment return_count.
reason_category must be one of RETURN_CATEGORIES; defaults to 'missing_context'
if an invalid value is supplied (fail-open).
Returns the inserted task_returns row as dict.
"""
if reason_category not in RETURN_CATEGORIES:
reason_category = "missing_context"
# Determine the next return_number for this task
row = conn.execute(
"SELECT COALESCE(MAX(return_number), 0) FROM task_returns WHERE task_id = ?",
(task_id,),
).fetchone()
next_number = (row[0] if row else 0) + 1
conn.execute(
"""INSERT INTO task_returns
(task_id, return_number, reason_category, reason_text, returned_by, pipeline_id)
VALUES (?, ?, ?, ?, ?, ?)""",
(task_id, next_number, reason_category, reason_text, returned_by, pipeline_id),
)
conn.execute(
"UPDATE tasks SET return_count = return_count + 1 WHERE id = ?",
(task_id,),
)
conn.commit()
inserted = conn.execute(
"SELECT * FROM task_returns WHERE task_id = ? ORDER BY id DESC LIMIT 1",
(task_id,),
).fetchone()
return dict(inserted) if inserted else {}
def get_task_returns(
conn: sqlite3.Connection,
task_id: str,
limit: int = 20,
) -> list[dict]:
"""Return task return history ordered by return_number ASC."""
rows = conn.execute(
"SELECT * FROM task_returns WHERE task_id = ? ORDER BY return_number ASC LIMIT ?",
(task_id, limit),
).fetchall()
return [dict(r) for r in rows]
def check_return_pattern(
conn: sqlite3.Connection,
task_id: str,
) -> dict:
"""Analyse return history for a recurring pattern.
Returns:
{
"pattern_detected": bool, # True if dominant_category appears >= 2 times
"dominant_category": str | None,
"occurrences": int,
}
"""
from collections import Counter
rows = conn.execute(
"SELECT reason_category FROM task_returns WHERE task_id = ?",
(task_id,),
).fetchall()
if not rows:
return {"pattern_detected": False, "dominant_category": None, "occurrences": 0}
counts = Counter(r[0] for r in rows)
dominant_category, occurrences = counts.most_common(1)[0]
pattern_detected = occurrences >= 2
return {
"pattern_detected": pattern_detected,
"dominant_category": dominant_category,
"occurrences": occurrences,
}