#!/usr/bin/env python3 """ Spec Generator - Generates a feature specification template from a name and description. Produces a complete spec document with all required sections pre-filled with guidance prompts. Output can be markdown or structured JSON. No external dependencies - uses only Python standard library. """ import argparse import json import sys import textwrap from datetime import date from pathlib import Path from typing import Dict, Any, Optional SPEC_TEMPLATE = """\ # Spec: {name} **Author:** [your name] **Date:** {date} **Status:** Draft **Reviewers:** [list reviewers] **Related specs:** [links to related specs, or "None"] --- ## Context {context_prompt} --- ## Functional Requirements _Use RFC 2119 keywords: MUST, MUST NOT, SHOULD, SHOULD NOT, MAY._ _Each requirement is a single, testable statement. Number sequentially._ - FR-1: The system MUST [describe required behavior]. - FR-2: The system MUST [describe another required behavior]. - FR-3: The system SHOULD [describe recommended behavior]. - FR-4: The system MAY [describe optional behavior]. - FR-5: The system MUST NOT [describe prohibited behavior]. --- ## Non-Functional Requirements ### Performance - NFR-P1: [Operation] MUST complete in < [threshold] (p95) under [conditions]. - NFR-P2: [Operation] SHOULD handle [throughput] requests per second. ### Security - NFR-S1: All data in transit MUST be encrypted via TLS 1.2+. - NFR-S2: The system MUST rate-limit [operation] to [limit] per [period] per [scope]. ### Accessibility - NFR-A1: [UI component] MUST meet WCAG 2.1 AA standards. - NFR-A2: Error messages MUST be announced to screen readers. ### Scalability - NFR-SC1: The system SHOULD handle [number] concurrent [entities]. ### Reliability - NFR-R1: The [service] MUST maintain [percentage]% uptime. --- ## Acceptance Criteria _Write in Given/When/Then (Gherkin) format._ _Each criterion MUST reference at least one FR-* or NFR-*._ ### AC-1: [Descriptive name] (FR-1) Given [precondition] When [action] Then [expected result] And [additional assertion] ### AC-2: [Descriptive name] (FR-2) Given [precondition] When [action] Then [expected result] ### AC-3: [Descriptive name] (NFR-S2) Given [precondition] When [action] Then [expected result] And [additional assertion] --- ## Edge Cases _For every external dependency (API, database, file system, user input), specify at least one failure scenario._ - EC-1: [Input/condition] -> [expected behavior]. - EC-2: [Input/condition] -> [expected behavior]. - EC-3: [External service] is unavailable -> [expected behavior]. - EC-4: [Concurrent/race condition] -> [expected behavior]. - EC-5: [Boundary value] -> [expected behavior]. --- ## API Contracts _Define request/response shapes using TypeScript-style notation._ _Cover all endpoints referenced in functional requirements._ ### [METHOD] [endpoint] Request: ```typescript interface [Name]Request {{ field: string; // Description, constraints optional?: number; // Default: [value] }} ``` Success Response ([status code]): ```typescript interface [Name]Response {{ id: string; field: string; createdAt: string; // ISO 8601 }} ``` Error Response ([status code]): ```typescript interface [Name]Error {{ error: "[ERROR_CODE]"; message: string; }} ``` --- ## Data Models _Define all entities referenced in requirements._ ### [Entity Name] | Field | Type | Constraints | |-------|------|-------------| | id | UUID | Primary key, auto-generated | | [field] | [type] | [constraints] | | createdAt | timestamp | UTC, immutable | | updatedAt | timestamp | UTC, auto-updated | --- ## Out of Scope _Explicit exclusions prevent scope creep. If someone asks for these during implementation, point them here._ - OS-1: [Feature/capability] — [reason for exclusion or link to future spec]. - OS-2: [Feature/capability] — [reason for exclusion]. - OS-3: [Feature/capability] — deferred to [version/sprint]. --- ## Open Questions _Track unresolved questions here. Each must be resolved before status moves to "Approved"._ - [ ] Q1: [Question] — Owner: [name], Due: [date] - [ ] Q2: [Question] — Owner: [name], Due: [date] """ def generate_context_prompt(description: str) -> str: """Generate a context section prompt based on the provided description.""" if description: return textwrap.dedent(f"""\ {description} _Expand this context section to include:_ _- Why does this feature exist? What problem does it solve?_ _- What is the business motivation? (link to user research, support tickets, metrics)_ _- What is the current state? (what exists today, what pain points exist)_ _- 2-4 paragraphs maximum._""") return textwrap.dedent("""\ _Why does this feature exist? What problem does it solve? What is the business motivation? Include links to user research, support tickets, or metrics that justify this work. 2-4 paragraphs maximum._""") def generate_spec(name: str, description: str) -> str: """Generate a spec document from name and description.""" context_prompt = generate_context_prompt(description) return SPEC_TEMPLATE.format( name=name, date=date.today().isoformat(), context_prompt=context_prompt, ) def generate_spec_json(name: str, description: str) -> Dict[str, Any]: """Generate structured JSON representation of the spec template.""" return { "spec": { "title": f"Spec: {name}", "metadata": { "author": "[your name]", "date": date.today().isoformat(), "status": "Draft", "reviewers": [], "related_specs": [], }, "context": description or "[Describe why this feature exists]", "functional_requirements": [ {"id": "FR-1", "keyword": "MUST", "description": "[describe required behavior]"}, {"id": "FR-2", "keyword": "MUST", "description": "[describe another required behavior]"}, {"id": "FR-3", "keyword": "SHOULD", "description": "[describe recommended behavior]"}, {"id": "FR-4", "keyword": "MAY", "description": "[describe optional behavior]"}, {"id": "FR-5", "keyword": "MUST NOT", "description": "[describe prohibited behavior]"}, ], "non_functional_requirements": { "performance": [ {"id": "NFR-P1", "description": "[operation] MUST complete in < [threshold]"}, ], "security": [ {"id": "NFR-S1", "description": "All data in transit MUST be encrypted via TLS 1.2+"}, ], "accessibility": [ {"id": "NFR-A1", "description": "[UI component] MUST meet WCAG 2.1 AA"}, ], "scalability": [ {"id": "NFR-SC1", "description": "[system] SHOULD handle [N] concurrent [entities]"}, ], "reliability": [ {"id": "NFR-R1", "description": "[service] MUST maintain [N]% uptime"}, ], }, "acceptance_criteria": [ { "id": "AC-1", "name": "[descriptive name]", "references": ["FR-1"], "given": "[precondition]", "when": "[action]", "then": "[expected result]", }, ], "edge_cases": [ {"id": "EC-1", "condition": "[input/condition]", "behavior": "[expected behavior]"}, ], "api_contracts": [ { "method": "[METHOD]", "endpoint": "[/api/path]", "request_fields": [{"name": "field", "type": "string", "constraints": "[description]"}], "success_response": {"status": 200, "fields": []}, "error_response": {"status": 400, "fields": []}, }, ], "data_models": [ { "name": "[Entity]", "fields": [ {"name": "id", "type": "UUID", "constraints": "Primary key, auto-generated"}, ], }, ], "out_of_scope": [ {"id": "OS-1", "description": "[feature/capability]", "reason": "[reason]"}, ], "open_questions": [], }, "metadata": { "generated_by": "spec_generator.py", "feature_name": name, "feature_description": description, }, } def main(): parser = argparse.ArgumentParser( description="Generate a feature specification template from a name and description.", epilog="Example: python spec_generator.py --name 'User Auth' --description 'OAuth 2.0 login flow'", ) parser.add_argument( "--name", required=True, help="Feature name (used as spec title)", ) parser.add_argument( "--description", default="", help="Brief feature description (used to seed the context section)", ) parser.add_argument( "--output", "-o", default=None, help="Output file path (default: stdout)", ) parser.add_argument( "--format", choices=["md", "json"], default="md", help="Output format: md (markdown) or json (default: md)", ) parser.add_argument( "--json", action="store_true", dest="json_flag", help="Shorthand for --format json", ) args = parser.parse_args() output_format = "json" if args.json_flag else args.format if output_format == "json": result = generate_spec_json(args.name, args.description) output = json.dumps(result, indent=2) else: output = generate_spec(args.name, args.description) if args.output: out_path = Path(args.output) out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(output, encoding="utf-8") print(f"Spec template written to {out_path}", file=sys.stderr) else: print(output) sys.exit(0) if __name__ == "__main__": main()