diff --git a/backend/routers/backlog.py b/backend/routers/backlog.py
index e2d806c..0d14adc 100644
--- a/backend/routers/backlog.py
+++ b/backend/routers/backlog.py
@@ -34,6 +34,8 @@ class BacklogUpdateRequest(BaseModel):
class BacklogConvertRequest(BaseModel):
assigned_actor_ids: list[str] = Field(default_factory=list)
+ work_cycle_id: Optional[str] = None
+ assign_active_sprint: bool = True
@router.get("/{backlog_item_id}")
@@ -103,6 +105,8 @@ def convert_backlog_to_action(
backlog_item_id=backlog_item_id,
user_id=ctx.user_id,
assigned_actor_ids=assigned,
+ work_cycle_id=body.work_cycle_id,
+ assign_active_sprint=body.assign_active_sprint,
)
except ValueError as exc:
detail = str(exc)
diff --git a/backend/services/backlog.py b/backend/services/backlog.py
index ccb0f78..d509e4b 100644
--- a/backend/services/backlog.py
+++ b/backend/services/backlog.py
@@ -295,6 +295,8 @@ def convert_backlog_to_action(
backlog_item_id: str,
user_id: Optional[str] = None,
assigned_actor_ids: Optional[list[str]] = None,
+ work_cycle_id: Optional[str] = None,
+ assign_active_sprint: bool = True,
) -> dict[str, Any]:
existing = get_backlog_item(tenant_id=tenant_id, backlog_item_id=backlog_item_id)
if not existing:
@@ -304,6 +306,16 @@ def convert_backlog_to_action(
if existing["status"] not in ("accepted", "triaged", "new"):
raise ValueError("Backlog-Item kann in diesem Status nicht konvertiert werden")
+ resolved_cycle_id = work_cycle_id
+ if not resolved_cycle_id and assign_active_sprint:
+ from services.work_cycle import get_active_work_cycle
+
+ active = get_active_work_cycle(
+ tenant_id=tenant_id, initiative_id=existing["initiative_id"]
+ )
+ if active:
+ resolved_cycle_id = active["id"]
+
action = create_action(
tenant_id=tenant_id,
initiative_id=existing["initiative_id"],
@@ -311,6 +323,7 @@ def convert_backlog_to_action(
description=existing["description"] or "",
priority=existing["priority"],
roadmap_item_id=existing.get("roadmap_item_id"),
+ work_cycle_id=resolved_cycle_id,
assigned_actor_ids=assigned_actor_ids or [],
user_id=user_id,
)
diff --git a/backend/steering/signals/engine.py b/backend/steering/signals/engine.py
index a96db70..76e49db 100644
--- a/backend/steering/signals/engine.py
+++ b/backend/steering/signals/engine.py
@@ -25,6 +25,25 @@ def _resolve_method_key(ctx: TenantContext, initiative_id: str | None) -> str:
return "generic_operating"
+def _resolve_next_action_strategy_key(
+ ctx: TenantContext, initiative_id: str | None
+) -> str:
+ method_key = _resolve_method_key(ctx, initiative_id)
+ method = get_method(method_key)
+ strategy_key = method.next_action_strategy_key if method else default_strategy.key
+ if not initiative_id:
+ return strategy_key
+
+ from services.work_cycle import get_active_work_cycle
+
+ active = get_active_work_cycle(tenant_id=ctx.tenant_id, initiative_id=initiative_id)
+ if active and strategy_key == "continuous_product":
+ agile = get_next_action_strategy("agile_iteration")
+ if agile:
+ return agile.key
+ return strategy_key
+
+
def evaluate(
ctx: TenantContext,
kind: SignalKind = "attention",
@@ -35,10 +54,6 @@ def evaluate(
if kind == "attention":
return default_rules.get_attention_items(ctx)
- method_key = _resolve_method_key(ctx, initiative_id)
- method = get_method(method_key)
- strategy_key = (
- method.next_action_strategy_key if method else default_strategy.key
- )
+ strategy_key = _resolve_next_action_strategy_key(ctx, initiative_id)
strategy = get_next_action_strategy(strategy_key) or default_strategy
return strategy.evaluate(ctx, initiative_id=initiative_id, limit=limit)
diff --git a/backend/tests/test_ap22d_sprint_backlog.py b/backend/tests/test_ap22d_sprint_backlog.py
new file mode 100644
index 0000000..7fe9341
--- /dev/null
+++ b/backend/tests/test_ap22d_sprint_backlog.py
@@ -0,0 +1,44 @@
+"""AP2.2d — Backlog convert assigns active sprint."""
+
+from __future__ import annotations
+
+from tests.factories import provision_user_in_tenant
+from tests.test_initiatives_actions import _auth, _create_initiative, _login
+
+
+def test_convert_backlog_assigns_active_work_cycle(client):
+ user = provision_user_in_tenant(tenant_role="admin")
+ token = _login(client, user)
+
+ created = _create_initiative(
+ client,
+ token,
+ title="Product Backlog",
+ archetype_key="initiative.product",
+ )
+ initiative_id = created.json()["id"]
+
+ cycle = client.post(
+ f"/api/initiatives/{initiative_id}/work-cycles",
+ json={"title": "Sprint R1", "status": "active"},
+ headers=_auth(token),
+ )
+ assert cycle.status_code == 201
+ cycle_id = cycle.json()["id"]
+
+ backlog = client.post(
+ f"/api/initiatives/{initiative_id}/backlog",
+ json={"title": "Feature X", "status": "accepted"},
+ headers=_auth(token),
+ )
+ assert backlog.status_code == 201
+ backlog_id = backlog.json()["id"]
+
+ converted = client.post(
+ f"/api/backlog/{backlog_id}/convert-to-action",
+ json={"assign_active_sprint": True},
+ headers=_auth(token),
+ )
+ assert converted.status_code == 201
+ action = converted.json()["action"]
+ assert action["work_cycle_id"] == cycle_id
diff --git a/backend/version.py b/backend/version.py
index 0c74c91..55795b0 100644
--- a/backend/version.py
+++ b/backend/version.py
@@ -1,3 +1,3 @@
-APP_VERSION = "0.20.1-ap2.2bc"
+APP_VERSION = "0.20.2-ap2.2d"
DB_SCHEMA_VERSION = "025"
APP_NAME = "jinkendo-kairo"
diff --git a/docs/product/Kairo_Implementation_Truth_Table_v0.1.md b/docs/product/Kairo_Implementation_Truth_Table_v0.1.md
index 859eb76..b4c40ee 100644
--- a/docs/product/Kairo_Implementation_Truth_Table_v0.1.md
+++ b/docs/product/Kairo_Implementation_Truth_Table_v0.1.md
@@ -148,8 +148,8 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
|----------|------------|------------------|-----------|--------|-------|
| A1 Reifegrad | ◐ AP2.2a | ◐ | ◐ | ◐ | AP2.2c + AP2.0e |
| A2 Linear | ◐ AP2.2a | ◐ | ◐ | ◐ | AP2.2b |
-| B2b Product | ◐ AP2.2a | ◐ | ◐ | ◐ | AP2.2d |
-| B3 Sprint-Profil | ◐ | ◐ | ◐ | ◐ | AP2.2d |
+| B2b Product | ◐ AP2.2a | ◐ | ◐ | ◐ | AP2.2d ✓ |
+| B3 Sprint-Profil | ◐ | ◐ | ◐ | ◐ | AP2.2d ✓ |
| B2a Programm | ◐ AP2.2a | ◐ | ◐ | ◐ | AP2.2e |
---
diff --git a/docs/product/Kairo_MVP_Execution_Plan_v0.2.md b/docs/product/Kairo_MVP_Execution_Plan_v0.2.md
index cbed10a..fd1d1ca 100644
--- a/docs/product/Kairo_MVP_Execution_Plan_v0.2.md
+++ b/docs/product/Kairo_MVP_Execution_Plan_v0.2.md
@@ -75,7 +75,7 @@ Phase 1 AP2.2a Archetyp-geführte Anlage + Starter-Kits ✓
Phase 2 AP1.9d Methoden-Default-Ansichten (Anti-Todo-Wand) ◐ (Hints + Recurring)
Phase 3 AP2.2b A2 Linear End-to-End ◐
AP2.2c A1 Reifegrad + AP2.0e ◐ Code
- AP2.2d B2b Product + B3 Sprint
+ AP2.2d B2b Product + B3 Sprint ✓
AP2.2e B2a Programm (optional vor AP2.1)
Phase 4 AP1.7b Operational API — Pflege-Parität
Phase 5 AP2.1 Validation Report v0.3 (alle Stufe-A-Szenarien)
diff --git a/frontend/package.json b/frontend/package.json
index 091a44c..32b3f44 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "kairo-jinkendo-frontend",
- "version": "0.20.1-ap2.2bc",
+ "version": "0.20.2-ap2.2d",
"private": true,
"type": "module",
"scripts": {
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 0c9eec1..74cb635 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -63,6 +63,7 @@ function AppRoutes() {