@@ -1590,6 +1590,167 @@ def prd_generate(
15901590 raise typer .Exit (1 )
15911591
15921592
1593+ @prd_app .command ("stress-test" )
1594+ def prd_stress_test (
1595+ repo_path : Optional [Path ] = typer .Option (
1596+ None ,
1597+ "--workspace" , "-w" ,
1598+ help = "Workspace path (defaults to current directory)" ,
1599+ ),
1600+ prd_id : Optional [str ] = typer .Option (
1601+ None ,
1602+ "--prd-id" ,
1603+ help = "Specific PRD ID (defaults to latest)" ,
1604+ ),
1605+ max_depth : int = typer .Option (
1606+ 3 ,
1607+ "--max-depth" ,
1608+ min = 1 ,
1609+ max = 10 ,
1610+ help = "Maximum recursion depth (default: 3, range: 1-10)" ,
1611+ ),
1612+ output : Optional [Path ] = typer .Option (
1613+ None ,
1614+ "--output" , "-o" ,
1615+ help = "Write tech spec to this file" ,
1616+ ),
1617+ interactive : bool = typer .Option (
1618+ False ,
1619+ "--interactive" , "-i" ,
1620+ help = "Resolve ambiguities inline and update PRD" ,
1621+ ),
1622+ ) -> None :
1623+ """Stress-test a PRD via recursive decomposition.
1624+
1625+ Recursively decomposes PRD goals using a tri-state classification
1626+ (atomic / composite / ambiguous) to surface requirements gaps and
1627+ generate a technical specification.
1628+
1629+ Produces two outputs:
1630+ 1. Ambiguity Report — questions the PRD doesn't answer
1631+ 2. Technical Specification — decomposition tree as markdown
1632+
1633+ Use --interactive to resolve ambiguities inline and update the PRD.
1634+
1635+ Requires ANTHROPIC_API_KEY environment variable.
1636+
1637+ Example:
1638+ codeframe prd stress-test
1639+ codeframe prd stress-test --max-depth 4
1640+ codeframe prd stress-test --output spec.md
1641+ codeframe prd stress-test --interactive
1642+ """
1643+ import os
1644+ from codeframe .core .workspace import get_workspace
1645+ from codeframe .core import prd as prd_module
1646+ from codeframe .adapters .llm .anthropic import AnthropicProvider
1647+ from codeframe .core .prd_stress_test import (
1648+ stress_test_prd ,
1649+ resolve_ambiguities_into_prd ,
1650+ )
1651+ from codeframe .core .events import emit_for_workspace , EventType
1652+ from rich .panel import Panel
1653+
1654+ workspace_path = repo_path or Path .cwd ()
1655+
1656+ try :
1657+ workspace = get_workspace (workspace_path )
1658+ except Exception as e :
1659+ console .print (f"[red]Error:[/red] { e } " )
1660+ raise typer .Exit (1 )
1661+
1662+ # Load PRD
1663+ if prd_id :
1664+ record = prd_module .get_by_id (workspace , prd_id )
1665+ else :
1666+ record = prd_module .get_latest (workspace )
1667+
1668+ if not record :
1669+ console .print ("[red]Error:[/red] No PRD found. Run 'codeframe prd generate' first." )
1670+ raise typer .Exit (1 )
1671+
1672+ console .print (f"[dim]Stress-testing PRD: { record .title } (v{ record .version } )[/dim]" )
1673+
1674+ # Build provider
1675+ api_key = os .getenv ("ANTHROPIC_API_KEY" )
1676+ if not api_key :
1677+ console .print ("[red]Error:[/red] ANTHROPIC_API_KEY environment variable required." )
1678+ raise typer .Exit (1 )
1679+
1680+ provider = AnthropicProvider (api_key = api_key )
1681+
1682+ # Run stress test
1683+ console .print (f"[dim]Recursively decomposing (max depth: { max_depth } )...[/dim]" )
1684+ result = stress_test_prd (record .content , provider , max_depth = max_depth )
1685+
1686+ # Show ambiguity report
1687+ if result .ambiguities :
1688+ console .print (f"\n [bold yellow]⚠ { len (result .ambiguities )} ambiguities found[/bold yellow]\n " )
1689+ for i , amb in enumerate (result .ambiguities , 1 ):
1690+ console .print (f"[bold yellow]{ i } . { amb .label } [/bold yellow] (from \" { amb .source_node_title } \" )" )
1691+ console .print (" The PRD doesn't specify:" )
1692+ for q in amb .questions :
1693+ console .print (f" [cyan]- { q } [/cyan]" )
1694+ console .print (f" → { amb .recommendation } " )
1695+ console .print ()
1696+ else :
1697+ console .print ("\n [green]✓ No ambiguities found — PRD is well-specified.[/green]\n " )
1698+
1699+ # Interactive resolution
1700+ if interactive and result .ambiguities :
1701+ console .print ("[bold]Interactive mode — resolve ambiguities:[/bold]\n " )
1702+ for amb in result .ambiguities :
1703+ console .print (f"[yellow]{ amb .label } [/yellow]: { ', ' .join (amb .questions )} " )
1704+ answer = typer .prompt ("Your answer" )
1705+ amb .resolved_answer = answer
1706+ console .print ("[green]✓[/green] Recorded.\n " )
1707+
1708+ # Update PRD with resolved answers
1709+ console .print ("[dim]Updating PRD with resolved ambiguities...[/dim]" )
1710+ updated_content = resolve_ambiguities_into_prd (
1711+ record .content , result .ambiguities , provider ,
1712+ )
1713+ new_record = prd_module .create_new_version (
1714+ workspace , record .id , updated_content ,
1715+ f"Stress-test: resolved { len ([a for a in result .ambiguities if a .resolved_answer ])} ambiguities" ,
1716+ )
1717+ if new_record :
1718+ console .print (f"[green]✓[/green] PRD updated to version { new_record .version } " )
1719+ emit_for_workspace (
1720+ workspace , EventType .PRD_UPDATED ,
1721+ {"prd_id" : new_record .id , "source" : "stress_test_resolution" },
1722+ print_event = False ,
1723+ )
1724+ # Re-run stress test on updated PRD to reflect resolved ambiguities
1725+ console .print ("[dim]Re-analyzing updated PRD...[/dim]" )
1726+ result = stress_test_prd (new_record .content , provider , max_depth = max_depth )
1727+ else :
1728+ console .print ("[yellow]Warning:[/yellow] Failed to create new PRD version." )
1729+
1730+ # Show tech spec
1731+ console .print (Panel (result .tech_spec_markdown [:2000 ], title = "Technical Specification" , border_style = "blue" ))
1732+
1733+ # Write to file
1734+ if output :
1735+ output .write_text (result .tech_spec_markdown )
1736+ console .print (f"\n [green]✓[/green] Tech spec written to [bold]{ output } [/bold]" )
1737+
1738+ # Summary
1739+ node_count = _count_nodes (result .tree )
1740+ console .print (f"\n [bold]Summary:[/bold] { len (result .tree )} goals, { node_count } nodes, { len (result .ambiguities )} ambiguities" )
1741+ if result .ambiguities and not interactive :
1742+ console .print ("[dim]Tip: Run with --interactive to resolve ambiguities and update the PRD[/dim]" )
1743+
1744+
1745+ def _count_nodes (tree : list ) -> int :
1746+ """Count total nodes in the decomposition tree."""
1747+ count = 0
1748+ for node in tree :
1749+ count += 1
1750+ count += _count_nodes (node .children )
1751+ return count
1752+
1753+
15931754# Tasks commands
15941755tasks_app = typer .Typer (
15951756 name = "tasks" ,
0 commit comments