Files
ss-tools/backend/_convert_defs.py
busya 306c5ae742 semantics: complete DEF-to-region migration, fix regressions
- Convert legacy [DEF🆔Type] anchors to #region/#endregion across 329 files
- Reinstate _normalize_timestamp_value in sql_generator.py
- Fix MarkerLogger→logger migration in events.py (molecular CoT markers)
- Fix dataset_review orchestrator dependencies (_build_execution_snapshot)
- Fix config_manager stale-record deletion (moved to save path only)
- Add 77 missing [/DEF:] closers in 5 unbalanced test files
- Update assistant_chat.integration.test.js for #region format
- Apply molecular-cot-logging markers (REASON/REFLECT/EXPLORE) via logger.* methods
2026-05-12 23:54:55 +03:00

233 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""
Convert legacy # [DEF:...] / # [/DEF:...] annotations to #region/#endregion format.
ONLY modifies comment lines (starting with '#').
NEVER touches code, strings, imports, or non-comment lines.
Preserves original line endings (CRLF vs LF).
"""
import re
from pathlib import Path
SRC_DIR = Path(__file__).resolve().parent / "src"
def has_crlf(content: bytes) -> bool:
"""Check if content uses CRLF line endings."""
return b'\r\n' in content
def process_file(filepath: Path) -> tuple[int, int]:
"""Process a single file. Returns (blocks_converted, metadata_merged)."""
# Read as bytes to detect original line endings
with open(filepath, "rb") as f:
raw_bytes = f.read()
original_crlf = has_crlf(raw_bytes)
# Decode text (Python converts CRLF to LF automatically on decode in text mode,
# but we decoded from bytes, so handle it manually)
text = raw_bytes.decode('utf-8')
if original_crlf:
text = text.replace('\r\n', '\n')
lines = text.split('\n')
result = []
blocks_converted = 0
metadata_merged = 0
i = 0
n = len(lines)
while i < n:
line = lines[i]
stripped = line.rstrip('\r')
# Check for [SECTION: ...] or [/SECTION] lines
if re.match(r'^#\s*\[SECTION:\s*.*\]\s*$', stripped):
i += 1
continue
if re.match(r'^#\s*\[/SECTION\]\s*$', stripped):
i += 1
continue
# Check for [/DEF:Name] or [/DEF:Name:Type]
m = re.match(r'^#\s*\[/DEF:(\w+(?:\.\w+)*)(?::\w+)?\]\s*$', stripped)
if m:
name = m.group(1)
result.append(f"# #endregion {name}")
blocks_converted += 1
i += 1
continue
# Check for [DEF:Name:Type]
m = re.match(r'^#\s*\[DEF:(\w+(?:\.\w+)*):(\w+)\]\s*$', stripped)
if m:
name = m.group(1)
typ = m.group(2)
# Collect subsequent metadata lines
complexity = None
semantics = None
metadata_lines = [] # other metadata lines to preserve
j = i + 1
while j < n:
nxt = lines[j].rstrip('\r')
# Stop if not a comment
if not nxt.startswith('#'):
break
# Stop if it's another DEF or SECTION
if re.match(r'^#\s*\[', nxt):
break
# Skip blank comment lines (just '#') but continue collecting
if re.match(r'^#\s*$', nxt):
metadata_lines.append(lines[j])
j += 1
continue
# Check for @COMPLEXITY
cm = re.match(r'^#\s*@COMPLEXITY:\s*(\d+)\s*$', nxt)
if cm:
complexity = int(cm.group(1))
j += 1
continue
# Check for @SEMANTICS
sm = re.match(r'^#\s*@SEMANTICS:\s*(.+?)\s*$', nxt)
if sm:
semantics = sm.group(1).strip()
j += 1
continue
# Skip @PARAM, @RETURN, @THROW
if re.match(r'^#\s*@(PARAM|RETURN|THROW)\b', nxt):
j += 1
continue
# Convert @PURPOSE -> @BRIEF
pm = re.match(r'^#\s*@PURPOSE:\s*(.*?)\s*$', nxt)
if pm:
metadata_lines.append(f"# @BRIEF {pm.group(1).strip()}")
j += 1
continue
# Normalize @RELATION
rm = re.match(r'^#\s*@RELATION:\s*(.+?)\s*$', nxt)
if rm:
rel_text = rm.group(1).strip()
# Remove brackets ONLY from predicate (word before ->)
rel_text = re.sub(r'\[(\w+)\]\s*->', r'\1 ->', rel_text)
# Normalize whitespace around ->
rel_text = re.sub(r'\s*->\s*', ' -> ', rel_text)
metadata_lines.append(f"# @RELATION {rel_text}")
j += 1
continue
# Pass through any other tags as-is
if re.match(r'^#\s*@\w+', nxt):
metadata_lines.append(lines[j])
j += 1
continue
# If it doesn't match any known pattern, keep it but stop collecting
break
# Build the header
header_parts = [f"# #region {name}"]
if complexity is not None:
header_parts.append(f"[C:{complexity}]")
metadata_merged += 1
header_parts.append(f"[TYPE {typ}]")
if semantics is not None:
header_parts.append(f"[SEMANTICS {semantics}]")
metadata_merged += 1
result.append(" ".join(header_parts))
# Add preserved metadata lines
result.extend(metadata_lines)
blocks_converted += 1
i = j
continue
# Check for standalone @PURPOSE (not in a DEF block)
pm = re.match(r'^#\s*@PURPOSE:\s*(.*?)\s*$', stripped)
if pm:
result.append(f"# @BRIEF {pm.group(1).strip()}")
i += 1
continue
# Check for standalone @RELATION normalization
rm = re.match(r'^#\s*@RELATION:\s*(.+?)\s*$', stripped)
if rm:
rel_text = rm.group(1).strip()
# Remove brackets ONLY from predicate (word before ->)
rel_text = re.sub(r'\[(\w+)\]\s*->', r'\1 ->', rel_text)
# Normalize whitespace around ->
rel_text = re.sub(r'\s*->\s*', ' -> ', rel_text)
result.append(f"# @RELATION {rel_text}")
i += 1
continue
# Skip @PARAM, @RETURN, @THROW lines (standalone)
if re.match(r'^#\s*@(PARAM|RETURN|THROW)\b', stripped):
i += 1
continue
# Pass through everything else unchanged
result.append(line)
i += 1
# Build new content with preserved line endings
new_text = '\n'.join(result)
if new_text == text:
return 0, 0
# Convert back to CRLF if original had it
if original_crlf:
new_raw = new_text.replace('\n', '\r\n').encode('utf-8')
else:
new_raw = new_text.encode('utf-8')
with open(filepath, "wb") as f:
f.write(new_raw)
return blocks_converted, metadata_merged
def main():
total_blocks = 0
total_merged = 0
total_files = 0
# Collect all Python files
py_files = sorted(SRC_DIR.rglob("*.py"))
for filepath in py_files:
# Skip __pycache__, .venv, tests
rel = filepath.relative_to(SRC_DIR)
parts = list(rel.parts)
if "__pycache__" in parts or ".venv" in parts:
continue
if any(p.startswith("__tests__") or p == "tests" for p in parts):
continue
try:
blocks, merged = process_file(filepath)
if blocks > 0 or merged > 0:
total_files += 1
total_blocks += blocks
total_merged += merged
print(f" {rel}: {blocks} blocks, {merged} metadata merged")
except Exception as e:
print(f" ERROR processing {rel}: {e}")
print(f"\nTotal: {total_files} files processed, {total_blocks} DEF blocks converted, {total_merged} metadata merged")
if __name__ == "__main__":
main()