""" SRS Assembler Deterministic assembly of SRS document from agent outputs, followed by a single LLM cross-validation pass. Replaces the spec_coordinator agent. """ import logging from datetime import datetime from langchain_core.prompts import ChatPromptTemplate from .llm_factory import get_chat_model from .schemas import TeamRole logger = logging.getLogger("srs_assembler") # Static documentation guidelines appendix (replaces technical_writer agent) STATIC_DOC_APPENDIX = """# Documentation Guidelines ## Recommended Documentation Structure ### For Development Teams 1. **README.md** - Project overview, setup instructions, quick start 2. **ARCHITECTURE.md** - System architecture, component diagrams, technology decisions 3. **API.md** - API documentation, endpoint reference, authentication 4. **DEPLOYMENT.md** - Deployment guide, environment configuration, CI/CD pipeline 5. **CONTRIBUTING.md** - Coding standards, PR process, code review guidelines ### For Product Teams 1. **PRD.md** - Product requirements, user stories, acceptance criteria 2. **ROADMAP.md** - Feature roadmap, priorities, timeline 3. **DECISIONS.md** - Architecture decision records (ADRs) ### Documentation Standards - Use Markdown format for all documentation - Keep documentation close to code (in repository) - Update documentation with every code change - Use diagrams (Mermaid, PlantUML) for complex concepts - Maintain a single source of truth ## Diátaxis Framework Organize documentation into four categories: 1. **Tutorials** - Learning-oriented, step-by-step guides 2. **How-to Guides** - Problem-oriented, practical instructions 3. **Reference** - Information-oriented, technical specifications 4. **Explanation** - Understanding-oriented, conceptual background """ def assemble_srs( all_outputs: dict[str, str], knowledge_graph: dict | None = None, contradictions: list[dict] | None = None, ) -> str: """ Deterministically assemble SRS document from agent outputs. Args: all_outputs: Dictionary mapping role names to their markdown output knowledge_graph: Optional merged knowledge graph dict for entity context contradictions: Optional list of cross-agent contradiction dicts Returns: Complete SRS document as markdown string """ sections = [ ("1. Product Requirements", all_outputs.get("product_owner", "")), ("2. Functional Requirements", all_outputs.get("business_analyst", "")), ("3. Technical Architecture", all_outputs.get("solution_architect", "")), ("4. Data Architecture", all_outputs.get("data_architect", "")), ("5. Security Requirements", all_outputs.get("security_analyst", "")), ("6. User Experience Design", all_outputs.get("ux_designer", "")), ("7. API Specifications", all_outputs.get("api_designer", "")), ("8. Testing Strategy", all_outputs.get("qa_strategist", "")), ("9. DevOps & Infrastructure", all_outputs.get("devops_architect", "")), ] # Build table of contents toc_lines = ["# Software Requirements Specification\n"] toc_lines.append( f"**Generated:** {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}\n" ) toc_lines.append("---\n") toc_lines.append("## Table of Contents\n") for title, _ in sections: anchor = title.lower().replace(" ", "-").replace(".", "") toc_lines.append(f"- [{title}](#{anchor})") toc_lines.append("- [A. Documentation Guidelines](#a-documentation-guidelines)") if contradictions: toc_lines.append("- [B. Consistency Warnings](#b-consistency-warnings)") toc_lines.append("\n---\n") # Build document body body_parts = ["\n".join(toc_lines)] for title, content in sections: if content and content.strip(): body_parts.append(f"\n## {title}\n\n{content}\n") # Add consistency warnings section if contradictions exist if contradictions: from .consistency_validator import ConsistencyValidator validator = ConsistencyValidator() warnings = validator.format_warnings_section(contradictions) if warnings: body_parts.append(f"\n{warnings}\n") # Add static documentation appendix body_parts.append(f"\n## A. Documentation Guidelines\n\n{STATIC_DOC_APPENDIX}\n") return "\n".join(body_parts) async def validate_srs(srs_document: str, all_outputs: dict[str, str]) -> str: """ Cross-validate assembled SRS for internal consistency. Single LLM pass to check for contradictions, gaps, and completeness. Args: srs_document: The assembled SRS document all_outputs: Original agent outputs for reference Returns: Validation report string """ validator_prompt = """You are a senior technical reviewer validating a Software Requirements Specification. Review the assembled SRS below for: 1. **Internal Consistency**: Are there contradictions between sections? (e.g., architecture says PostgreSQL but data model says MongoDB) 2. **Completeness**: Are all required sections present and substantive? 3. **Traceability**: Do requirements link to architecture, tests, and API specs? 4. **Gaps**: Is there missing critical information? ## SRS Document {srs_document} ## Your Task Return a validation report in this format: ```markdown ## Validation Report ### Consistency Check [PASS/FAIL] - [Details] ### Completeness Check [PASS/FAIL] - [Details] ### Traceability Check [PASS/FAIL] - [Details] ### Identified Gaps - [Gap 1] - [Gap 2] ### Overall Assessment [VALID / NEEDS_REVISION] ``` Be specific and actionable. If the SRS passes all checks, return "VALID" as the overall assessment.""" llm = get_chat_model( role=TeamRole.SPEC_COORDINATOR, temperature=0.2, max_tokens=2048 ) prompt_template = ChatPromptTemplate.from_messages( [ ("system", validator_prompt), ("human", "Please validate the SRS document above."), ] ) chain = prompt_template | llm try: response = await chain.ainvoke({"srs_document": srs_document[:15000]}) return response.content if hasattr(response, "content") else str(response) except Exception as e: logger.error(f"SRS validation failed: {e}") return f"## Validation Report\n\n**Status:** Validation failed due to error: {e}\n\n**Recommendation:** Review SRS manually."