Files
ss-tools/merge_spec.py
2026-05-08 18:01:49 +03:00

130 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [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 <feature_number>")
sys.exit(1)
merge_specs(sys.argv[1])
# [/DEF:merge_spec:Module]
# [/DEF:MergeSpec:Module]