# [DEF:MergeSpec:Module] # @TIER: TRIVIAL # @COMPLEXITY: 1 # @LAYER: Infra # [DEF:merge_spec:Module] # @TIER: TRIVIAL # @COMPLEXITY: 1 # @LAYER: Infra import sys from datetime import datetime from pathlib import Path REVIEW_PROMPT = ( "Другая LLM создала этот feature-пакет. Твоя задача - провести независимое " "ортогональное spec review, оценить готовность спецификации, найти противоречия, " "пробелы, риски реализации и подготовить структурированный отчет с корректировками. " "Сфокусируйся именно на review пакета спецификации, а не на переписывании реализации." ) CANONICAL_MD_STAGES = ( ("exact", "spec.md"), ("exact", "ux_reference.md"), ("prefix", "checklists/"), ("exact", "plan.md"), ("exact", "research.md"), ("exact", "data-model.md"), ("prefix", "contracts/"), ("exact", "quickstart.md"), ("exact", "tasks.md"), ) def relative_key(path: Path, root: Path) -> str: return path.relative_to(root).as_posix() def ordered_markdown_files(target_dir: Path) -> list[Path]: markdown_files = [path for path in target_dir.rglob("*.md") if path.is_file()] remaining = {relative_key(path, target_dir): path for path in markdown_files} ordered: list[Path] = [] for stage_type, stage_value in CANONICAL_MD_STAGES: if stage_type == "exact": path = remaining.pop(stage_value, None) if path is not None: ordered.append(path) continue stage_matches = sorted( [ path for relative_path, path in remaining.items() if relative_path.startswith(stage_value) ], key=lambda path: relative_key(path, target_dir), ) ordered.extend(stage_matches) for path in stage_matches: remaining.pop(relative_key(path, target_dir), None) ordered.extend( sorted(remaining.values(), key=lambda path: relative_key(path, target_dir)) ) return ordered def merge_specs(feature_number): specs_dir = Path("specs") if not specs_dir.exists(): print("Error: 'specs' directory not found.") return # Find the directory starting with the feature number target_dir = None for item in specs_dir.iterdir(): if item.is_dir() and item.name.startswith(f"{feature_number}-"): target_dir = item break if not target_dir: print( f"Error: No directory found for feature number '{feature_number}' in 'specs/'." ) return feature_name = target_dir.name now = datetime.now().strftime("%Y%m%d-%H%M%S") output_filename = f"{feature_name}-{now}.md" content_blocks = [ REVIEW_PROMPT, "", "Порядок артефактов: spec -> ux_reference -> checklist -> plan -> research -> data-model -> contracts -> quickstart -> tasks -> remaining markdown.", "", ] files_to_merge = ordered_markdown_files(target_dir) for file_path in files_to_merge: relative_path = file_path.relative_to(target_dir) try: with open(file_path, "r", encoding="utf-8") as f: file_content = f.read() content_blocks.append(f"--- FILE: {relative_path} ---\n") content_blocks.append(file_content) content_blocks.append("\n") except Exception as e: print(f"Skipping file {file_path} due to error: {e}") with open(output_filename, "w", encoding="utf-8") as f: f.write("\n".join(content_blocks)) print(f"Successfully created: {output_filename}") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python merge_spec.py ") sys.exit(1) merge_specs(sys.argv[1]) # [/DEF:merge_spec:Module] # [/DEF:MergeSpec:Module]