Create multiple features in a single operation. Features are assigned sequential priorities based on their order. All features start with passes=false. This is typically used by the initializer agent to set up the initial feature list from the app specification. Args:
(
features: Annotated[list[dict], Field(description="List of features to create, each with category, name, description, and steps")]
)
| 524 | |
| 525 | @mcp.tool() |
| 526 | def feature_create_bulk( |
| 527 | features: Annotated[list[dict], Field(description="List of features to create, each with category, name, description, and steps")] |
| 528 | ) -> str: |
| 529 | """Create multiple features in a single operation. |
| 530 | |
| 531 | Features are assigned sequential priorities based on their order. |
| 532 | All features start with passes=false. |
| 533 | |
| 534 | This is typically used by the initializer agent to set up the initial |
| 535 | feature list from the app specification. |
| 536 | |
| 537 | Args: |
| 538 | features: List of features to create, each with: |
| 539 | - category (str): Feature category |
| 540 | - name (str): Feature name |
| 541 | - description (str): Detailed description |
| 542 | - steps (list[str]): Implementation/test steps |
| 543 | - depends_on_indices (list[int], optional): Array indices (0-based) of |
| 544 | features in THIS batch that this feature depends on. Use this instead |
| 545 | of 'dependencies' since IDs aren't known until after creation. |
| 546 | Example: [0, 2] means this feature depends on features at index 0 and 2. |
| 547 | |
| 548 | Returns: |
| 549 | JSON with: created (int) - number of features created, with_dependencies (int) |
| 550 | """ |
| 551 | try: |
| 552 | # Use atomic transaction for bulk inserts to prevent priority conflicts |
| 553 | with atomic_transaction(_session_maker) as session: |
| 554 | # Get the starting priority atomically within the transaction |
| 555 | result = session.execute(text(""" |
| 556 | SELECT COALESCE(MAX(priority), 0) FROM features |
| 557 | """)).fetchone() |
| 558 | start_priority = (result[0] or 0) + 1 |
| 559 | |
| 560 | # First pass: validate all features and their index-based dependencies |
| 561 | for i, feature_data in enumerate(features): |
| 562 | # Validate required fields |
| 563 | if not all(key in feature_data for key in ["category", "name", "description", "steps"]): |
| 564 | return json.dumps({ |
| 565 | "error": f"Feature at index {i} missing required fields (category, name, description, steps)" |
| 566 | }) |
| 567 | |
| 568 | # Validate depends_on_indices |
| 569 | indices = feature_data.get("depends_on_indices", []) |
| 570 | if indices: |
| 571 | # Check max dependencies |
| 572 | if len(indices) > MAX_DEPENDENCIES_PER_FEATURE: |
| 573 | return json.dumps({ |
| 574 | "error": f"Feature at index {i} has {len(indices)} dependencies, max is {MAX_DEPENDENCIES_PER_FEATURE}" |
| 575 | }) |
| 576 | # Check for duplicates |
| 577 | if len(indices) != len(set(indices)): |
| 578 | return json.dumps({ |
| 579 | "error": f"Feature at index {i} has duplicate dependencies" |
| 580 | }) |
| 581 | # Check for forward references (can only depend on earlier features) |
| 582 | for idx in indices: |
| 583 | if not isinstance(idx, int) or idx < 0: |
nothing calls this directly
no test coverage detected