diff --git a/linear/src/engine.py b/linear/src/engine.py index 2401849..c0b4a52 100644 --- a/linear/src/engine.py +++ b/linear/src/engine.py @@ -433,7 +433,33 @@ def on_linear_ready_gate(self, issue: Dict[str, Any]) -> List[Action]: Action("linear", "set_status", issue_key, {"status": self.cfg.backlog_status}), ] - return [] + # At this point the issue has a valid classifier intake + canonical type. + # Surface routing defaults with minimal safe automation. + qa_route, pm_route, route_reason = self.classify_route(actual_type, intake) + out: List[Action] = [ + Action( + "linear", + "comment", + issue_key, + { + "body": ( + "Routing recommendation (from `linear_type_classifier_v1.json`): " + f"QA={qa_route}, PM={pm_route}. Reason: {route_reason}" + ) + }, + ) + ] + if qa_route == "skip": + out.insert( + 0, + Action( + "linear", + "set_label", + issue_key, + {"group": self.cfg.qa_gate_group, "value": self.cfg.qa_skipped}, + ), + ) + return out def on_linear_comment( self, diff --git a/linear/tests/test_engine.py b/linear/tests/test_engine.py index b285dab..9761adc 100644 --- a/linear/tests/test_engine.py +++ b/linear/tests/test_engine.py @@ -78,7 +78,21 @@ def test_ready_gate_accepts_emoji_type_label(self): "description": "Linear Classification\n- Output: code\n- Behavior change: yes\n- Broken existing behavior: no\n- Evidence: acceptance criteria\n- Children expected: no\n- PM-testable: yes\n\nObjective\nScope\nRequired outputs\nVerification plan\nRollback note\nAcceptance / closure criteria", } actions = self.bot.on_linear_ready_gate(issue) - self.assertEqual(actions, []) + self.assertTrue(any(a.kind == "comment" and "Routing recommendation" in a.payload["body"] for a in actions)) + self.assertFalse(any(a.kind == "set_status" for a in actions)) + self.assertFalse(any(a.kind == "set_label" and a.payload.get("value") == "qa-skipped" for a in actions)) + + def test_ready_gate_design_autosets_qa_skipped(self): + issue = { + "identifier": "BIT-45", + "status": "Ready", + "labels": ["🎨 Design"], + "estimate": 3, + "description": "Linear Classification\n- Output: design artifact\n- Behavior change: no\n- Broken existing behavior: no\n- Evidence: design acceptance\n- Children expected: no\n- PM-testable: yes\n\nObjective\nScope\nRequired outputs\nVerification plan\nRollback note\nAcceptance / closure criteria", + } + actions = self.bot.on_linear_ready_gate(issue) + self.assertTrue(any(a.kind == "set_label" and a.payload.get("value") == "qa-skipped" for a in actions)) + self.assertTrue(any(a.kind == "comment" and "QA=skip" in a.payload["body"] for a in actions)) def test_ready_gate_plan_parent_requires_estimate(self): issue = {