chore: cleanup tracked junk + commit remaining test fixes
.gitignore: - Add .duckdb semantic index binaries - Add .axiom/temp/ pytest artifacts - Add e2e_*.png screenshots - Remove tracked package-lock.json (already in gitignore) Untrack junk (git rm --cached): - 3 duckdb binary index files - 18 pytest temp artifacts - 12 e2e screenshots - package-lock.json Test fixes (decomposition follow-up): - executor test mock paths - orchestrator test mock paths - preview test mock paths - orthogonal fixes test mock paths - git_manager integration test - settings_page integration test - settings-utils fix
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
def convert_file(filepath):
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# Python style: # [DEF:Name:Type]
|
||||
content = re.sub(r'# \[DEF:([^:]+):([^\]]+)\]', r'# #region \1 [C:2] [TYPE \2]', content)
|
||||
content = re.sub(r'# \[/DEF:([^:]+):([^\]]+)\]', r'# #endregion \1', content)
|
||||
|
||||
# JS/Svelte style: // [DEF:Name:Type]
|
||||
content = re.sub(r'// \[DEF:([^:]+):([^\]]+)\]', r'// #region \1 [C:2] [TYPE \2]', content)
|
||||
content = re.sub(r'// \[/DEF:([^:]+):([^\]]+)\]', r'// #endregion \1', content)
|
||||
|
||||
# HTML comment style: <!-- [DEF:Name:Type] -->
|
||||
content = re.sub(r'<!-- \[DEF:([^:]+):([^\]]+)\] -->', r'<!-- #region \1 [C:2] [TYPE \2] -->', content)
|
||||
content = re.sub(r'<!-- \[/DEF:([^:]+):([^\]]+)\] -->', r'<!-- #endregion \1 -->', content)
|
||||
|
||||
if content == original:
|
||||
return False
|
||||
|
||||
if DRY_RUN:
|
||||
print(f"[DRY-RUN] Would modify: {filepath}")
|
||||
return True
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Modified: {filepath}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
backend_files = glob.glob('/home/busya/dev/ss-tools/backend/src/**/*.py', recursive=True)
|
||||
frontend_files = (glob.glob('/home/busya/dev/ss-tools/frontend/src/**/*.svelte', recursive=True) +
|
||||
glob.glob('/home/busya/dev/ss-tools/frontend/src/**/*.js', recursive=True) +
|
||||
glob.glob('/home/busya/dev/ss-tools/frontend/src/**/*.ts', recursive=True))
|
||||
|
||||
all_files = backend_files + frontend_files
|
||||
total = 0
|
||||
converted = 0
|
||||
|
||||
for fp in sorted(all_files):
|
||||
total += 1
|
||||
if convert_file(fp):
|
||||
converted += 1
|
||||
|
||||
print(f"\nTotal files scanned: {total}")
|
||||
print(f"Files modified: {converted}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 2: Convert remaining DEF patterns and normalize metadata."""
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
def convert_file(filepath):
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# === PART 1: Convert all DEF opening anchors ===
|
||||
|
||||
# Python: # [DEF:Name:Type]
|
||||
content = re.sub(r'# \[DEF:([^:]+):([^\]]+)\]', r'# #region \1 [C:2] [TYPE \2]', content)
|
||||
# Python closing: # [/DEF:Name:Type]
|
||||
content = re.sub(r'# \[/DEF:([^:]+):([^\]]+)\]', r'# #endregion \1', content)
|
||||
|
||||
# JS/Svelte //: // [DEF:Name:Type]
|
||||
content = re.sub(r'// \[DEF:([^:]+):([^\]]+)\]', r'// #region \1 [C:2] [TYPE \2]', content)
|
||||
# JS/Svelte // closing: // [/DEF:Name:Type]
|
||||
content = re.sub(r'// \[/DEF:([^:]+):([^\]]+)\]', r'// #endregion \1', content)
|
||||
|
||||
# JSDoc: * [DEF:Name:Type]
|
||||
content = re.sub(r' \* \[DEF:([^:]+):([^\]]+)\]', r' * #region \1 [C:2] [TYPE \2]', content)
|
||||
# JSDoc closing: * [/DEF:Name:Type]
|
||||
content = re.sub(r' \* \[/DEF:([^:]+):([^\]]+)\]', r' * #endregion \1', content)
|
||||
|
||||
# HTML/Svelte: <!-- [DEF:Name:Type] -->
|
||||
content = re.sub(r'<!-- \[DEF:([^:]+):([^\]]+)\] -->', r'<!-- #region \1 [C:2] [TYPE \2] -->', content)
|
||||
# HTML/Svelte closing: <!-- [/DEF:Name:Type] -->
|
||||
content = re.sub(r'<!-- \[/DEF:([^:]+):([^\]]+)\] -->', r'<!-- #endregion \1 -->', content)
|
||||
|
||||
# Markdown/unprefixed: [DEF:Name:Type] at line start
|
||||
content = re.sub(r'^\[DEF:([^:]+):([^\]]+)\]', r'#region \1 [C:2] [TYPE \2]', content, flags=re.MULTILINE)
|
||||
# Markdown/unprefixed closing: [/DEF:Name:Type]
|
||||
content = re.sub(r'^\[/DEF:([^:]+):([^\]]+)\]', r'#endregion \1', content, flags=re.MULTILINE)
|
||||
|
||||
# === PART 2: Normalize @PURPOSE: to @BRIEF ===
|
||||
# # @PURPOSE: text -> # @BRIEF text
|
||||
content = re.sub(r'^([#/])\s*@PURPOSE:\s*', r'\1 @BRIEF ', content, flags=re.MULTILINE)
|
||||
# # @PURPOSE text -> # @BRIEF text
|
||||
content = re.sub(r'^([#/])\s*@PURPOSE\s+', r'\1 @BRIEF ', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove @LAYER
|
||||
content = re.sub(r'^([#/])\s*@LAYER:?\s*\S+.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove @PARAM
|
||||
content = re.sub(r'^([#/])\s*@PARAM:?\s.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove @RETURN
|
||||
content = re.sub(r'^([#/])\s*@RETURN:?\s.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove blank lines resulting from deletions (max one)
|
||||
content = re.sub(r'\n\s*\n\s*\n', '\n\n', content)
|
||||
|
||||
# === PART 3: Fix @COMPLEXITY: N -> adjust [C:N] ===
|
||||
# If a @COMPLEXITY tag exists, upgrade the [C:N] accordingly
|
||||
# This is handled in a separate phase
|
||||
|
||||
if content == original:
|
||||
return False
|
||||
|
||||
if DRY_RUN:
|
||||
print(f"[DRY-RUN] Would modify: {filepath}")
|
||||
# Show a sample diff
|
||||
old_lines = original.splitlines()
|
||||
new_lines = content.splitlines()
|
||||
changes = 0
|
||||
for i, (old, new) in enumerate(zip(old_lines, new_lines)):
|
||||
if old != new:
|
||||
changes += 1
|
||||
if changes <= 3:
|
||||
print(f" L{i+1}: {old}")
|
||||
print(f" → {new}")
|
||||
if changes > 3:
|
||||
print(f" ... and {changes - 3} more changes")
|
||||
return True
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Modified: {filepath}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
all_files = []
|
||||
for pat in ['backend/**/*.py', 'frontend/src/**/*.svelte', 'frontend/src/**/*.js', 'frontend/src/**/*.ts']:
|
||||
all_files.extend(glob.glob(os.path.join('/home/busya/dev/ss-tools', pat), recursive=True))
|
||||
|
||||
# Exclude our own temp files
|
||||
all_files = [f for f in all_files if '.axiom' not in f]
|
||||
|
||||
total = 0
|
||||
converted = 0
|
||||
|
||||
for fp in sorted(set(all_files)):
|
||||
total += 1
|
||||
if convert_file(fp):
|
||||
converted += 1
|
||||
|
||||
print(f"\nTotal files scanned: {total}")
|
||||
print(f"Files modified: {converted}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 3: Final conversion pass - DEF→region, @PURPOSE→@BRIEF, strip @LAYER/@PARAM/@RETURN."""
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
def convert_file(filepath):
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# === PART 1: Convert DEF opening anchors ===
|
||||
# Python style
|
||||
content = re.sub(r'# \[DEF:([^:]+):([^\]]+)\]', r'# #region \1 [C:2] [TYPE \2]', content)
|
||||
content = re.sub(r'# \[/DEF:([^:]+):([^\]]+)\]', r'# #endregion \1', content)
|
||||
# JS/Svelte // style
|
||||
content = re.sub(r'// \[DEF:([^:]+):([^\]]+)\]', r'// #region \1 [C:2] [TYPE \2]', content)
|
||||
content = re.sub(r'// \[/DEF:([^:]+):([^\]]+)\]', r'// #endregion \1', content)
|
||||
# JSDoc style: * [DEF:...]
|
||||
content = re.sub(r' \* \[DEF:([^:]+):([^\]]+)\]', r' * #region \1 [C:2] [TYPE \2]', content)
|
||||
content = re.sub(r' \* \[/DEF:([^:]+):([^\]]+)\]', r' * #endregion \1', content)
|
||||
# HTML/Svelte <!-- style
|
||||
content = re.sub(r'<!-- \[DEF:([^:]+):([^\]]+)\] -->', r'<!-- #region \1 [C:2] [TYPE \2] -->', content)
|
||||
content = re.sub(r'<!-- \[/DEF:([^:]+):([^\]]+)\] -->', r'<!-- #endregion \1 -->', content)
|
||||
|
||||
# === PART 2: Normalize @PURPOSE: → @BRIEF ===
|
||||
content = re.sub(r'^(\s*[#/])\s*@PURPOSE:\s*', r'\1 @BRIEF ', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^(\s*[#/])\s*@PURPOSE\s+(?!\w)', r'\1 @BRIEF ', content, flags=re.MULTILINE)
|
||||
|
||||
# === PART 3: Remove @LAYER, @PARAM, @RETURN lines ===
|
||||
content = re.sub(r'^[ \t]*[#/][ \t]*@LAYER:?[ \t]*.*$', '', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^[ \t]*[#/][ \t]*@PARAM:?[ \t]*.*$', '', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^[ \t]*[#/][ \t]*@RETURN:?[ \t]*.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Cleanup: collapse 3+ consecutive blank lines to 2
|
||||
content = re.sub(r'\n[ \t]*\n[ \t]*\n', '\n\n', content)
|
||||
|
||||
if content == original:
|
||||
return False
|
||||
|
||||
if DRY_RUN:
|
||||
print(f"[DRY-RUN] Would modify: {filepath}")
|
||||
return True
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Modified: {filepath}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
all_files = []
|
||||
for pat in ['backend/**/*.py', 'frontend/src/**/*.svelte', 'frontend/src/**/*.js', 'frontend/src/**/*.ts']:
|
||||
all_files.extend(glob.glob(os.path.join('/home/busya/dev/ss-tools', pat), recursive=True))
|
||||
all_files = sorted(set(f for f in all_files if '.axiom' not in f and '.venv' not in f))
|
||||
|
||||
total = converted = 0
|
||||
for fp in all_files:
|
||||
total += 1
|
||||
if convert_file(fp):
|
||||
converted += 1
|
||||
print(f"\nTotal: {total} files, Modified: {converted}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 4: Final cleanup pass - handle ALL remaining @PURPOSE, @LAYER, @PARAM, @RETURN patterns."""
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
def clean_file(filepath):
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# @PURPOSE: or @PURPOSE -> @BRIEF (any comment style)
|
||||
content = re.sub(r'@PURPOSE:', '@BRIEF ', content)
|
||||
content = re.sub(r'@PURPOSE\s(?!\w)', '@BRIEF ', content)
|
||||
|
||||
# Remove @LAYER lines (any comment style, including HTML <!-- -->
|
||||
content = re.sub(r'<!--\s*@LAYER:?[^>]*-->', '', content) # HTML comment
|
||||
content = re.sub(r'^[ \t]*[/#*]+\s*@LAYER:?.*$', '', content, flags=re.MULTILINE) # code comments
|
||||
|
||||
# Remove @PARAM lines
|
||||
content = re.sub(r'^[ \t]*[/#*]+\s*@PARAM:?.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove @RETURN lines
|
||||
content = re.sub(r'^[ \t]*[/#*]+\s*@RETURN:?.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Cleanup: collapse 3+ consecutive blank lines to 2
|
||||
content = re.sub(r'\n[ \t]*\n[ \t]*\n', '\n\n', content)
|
||||
|
||||
if content == original:
|
||||
return False
|
||||
|
||||
if DRY_RUN:
|
||||
print(f"[DRY-RUN] Would modify: {filepath}")
|
||||
return True
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Cleaned: {filepath}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
all_files = []
|
||||
for pat in ['backend/**/*.py', 'frontend/src/**/*.svelte', 'frontend/src/**/*.js', 'frontend/src/**/*.ts']:
|
||||
all_files.extend(glob.glob(os.path.join('/home/busya/dev/ss-tools', pat), recursive=True))
|
||||
all_files = sorted(set(f for f in all_files if '.axiom' not in f and '.venv' not in f))
|
||||
|
||||
total = converted = 0
|
||||
for fp in all_files:
|
||||
total += 1
|
||||
if clean_file(fp):
|
||||
converted += 1
|
||||
print(f"\nTotal: {total} files, Cleaned: {converted}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 5: Final cleanup - handle remaining @PURPOSE, @LAYER, @PARAM, @RETURN in ALL comment styles."""
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
def clean_file(filepath):
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# @PURPOSE: or @PURPOSE -> @BRIEF (universal replacement)
|
||||
content = re.sub(r'@PURPOSE:', '@BRIEF', content)
|
||||
|
||||
# Remove @LAYER lines in HTML comments (<!-- @LAYER ... --> or <!--\n@LAYER:...\n-->)
|
||||
content = re.sub(r'<!--[^>]*@LAYER.*?-->', '', content) # single-line HTML
|
||||
content = re.sub(r'(<!--[\s\S]*?)@LAYER:?[^\n]*([\s\S]*?-->)', r'\1\2', content) # multi-line
|
||||
|
||||
# Remove @LAYER in code comments (#, //, *)
|
||||
content = re.sub(r'^[ \t]*[/#*]+\s*@LAYER:?.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove @PARAM, @RETURN, @LAYER lines in all formats
|
||||
for tag in ['@PARAM', '@RETURN', '@LAYER']:
|
||||
content = re.sub(r'^[ \t]*[/#*]+\s*' + tag + r':?.*$', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Also handle @PURPOSE without colon - replace space after with space
|
||||
content = re.sub(r'@PURPOSE\s+', '@BRIEF ', content)
|
||||
|
||||
# Cleanup: collapse 3+ consecutive blank lines to 2
|
||||
content = re.sub(r'\n[ \t]*\n[ \t]*\n', '\n\n', content)
|
||||
|
||||
if content == original:
|
||||
return False
|
||||
|
||||
if DRY_RUN:
|
||||
print(f"[DRY-RUN] Would modify: {filepath}")
|
||||
return True
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Cleaned: {filepath}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
all_files = []
|
||||
for pat in ['backend/**/*.py', 'frontend/src/**/*.svelte', 'frontend/src/**/*.js', 'frontend/src/**/*.ts']:
|
||||
all_files.extend(glob.glob(os.path.join('/home/busya/dev/ss-tools', pat), recursive=True))
|
||||
all_files = sorted(set(f for f in all_files if '.axiom' not in f and '.venv' not in f))
|
||||
|
||||
total = converted = 0
|
||||
for fp in all_files:
|
||||
total += 1
|
||||
if clean_file(fp):
|
||||
converted += 1
|
||||
print(f"\nTotal: {total} files, Cleaned: {converted}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 6: Fix complexity tiers and structural issues."""
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
DEFAULT_TIERS = {
|
||||
'Function': '[C:2]', 'Class': '[C:3]', 'Module': '[C:3]',
|
||||
'Block': '[C:1]', 'Component': '[C:2]', 'Store': '[C:2]',
|
||||
'Action': '[C:2]', 'DataClass': '[C:1]', 'Section': '[C:1]',
|
||||
'Variable': '[C:1]', 'Tombstone': '[C:1]', 'Agent': '[C:5]',
|
||||
'Skill': '[C:5]', 'Page': '[C:3]', 'Mount': '[C:1]',
|
||||
'Adapter': '[C:3]', 'Data': '[C:1]', 'Service': '[C:3]',
|
||||
}
|
||||
|
||||
def fix_file(filepath):
|
||||
with open(filepath) as f:
|
||||
lines = f.readlines()
|
||||
original = list(lines)
|
||||
modified = False
|
||||
|
||||
region_header_pattern = re.compile(
|
||||
r'^(.*#region\s+\S+)((?:\s+\[C:\d+\])?)((?:\s+\[TYPE\s+\w+\])?)(.*)$'
|
||||
)
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# Detect #region header
|
||||
if '#region' in line and '#endregion' not in line:
|
||||
has_tier = '[C:' in line
|
||||
has_type = '[TYPE ' in line
|
||||
|
||||
if has_type and not has_tier:
|
||||
# Extract type
|
||||
m = re.search(r'\[TYPE\s+(\w+)\]', line)
|
||||
if m:
|
||||
type_str = m.group(1)
|
||||
tier = DEFAULT_TIERS.get(type_str, '[C:2]')
|
||||
# Insert [C:N] before [TYPE
|
||||
lines[i] = re.sub(r'(\[TYPE\s+\w+\])', f'{tier} \\1', line)
|
||||
modified = True
|
||||
|
||||
# Check if this contract has @RATIONALE/@REJECTED (scan ahead to #endregion)
|
||||
if 'C:5' not in line:
|
||||
# Scan for @RATIONALE/@REJECTED in the block
|
||||
j = i + 1
|
||||
while j < len(lines) and '#endregion' not in lines[j]:
|
||||
if '@RATIONALE' in lines[j] or '@REJECTED' in lines[j]:
|
||||
# Upgrade to C5
|
||||
if has_tier:
|
||||
lines[i] = re.sub(r'\[C:\d+\]', '[C:5]', lines[i])
|
||||
else:
|
||||
# No tier yet, add C5
|
||||
if has_type:
|
||||
lines[i] = re.sub(r'(\[TYPE\s+\w+\])', '[C:5] \\1', lines[i])
|
||||
else:
|
||||
lines[i] = re.sub(r'(#region\s+\S+)', '\\1 [C:5]', lines[i])
|
||||
modified = True
|
||||
break
|
||||
j += 1
|
||||
|
||||
i += 1
|
||||
|
||||
if not modified:
|
||||
return False
|
||||
|
||||
if DRY_RUN:
|
||||
print(f"[DRY-RUN] Would modify: {filepath}")
|
||||
for i, (old, new) in enumerate(zip(original, lines)):
|
||||
if old != new:
|
||||
print(f" L{i+1}: {old.rstrip()} → {new.rstrip()}")
|
||||
return True
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.writelines(lines)
|
||||
print(f"Fixed: {filepath}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
all_files = []
|
||||
for pat in ['backend/**/*.py', 'frontend/src/**/*.svelte', 'frontend/src/**/*.js', 'frontend/src/**/*.ts']:
|
||||
all_files.extend(glob.glob(os.path.join('/home/busya/dev/ss-tools', pat), recursive=True))
|
||||
all_files = sorted(set(f for f in all_files if '.axiom' not in f and '.venv' not in f))
|
||||
|
||||
total = converted = 0
|
||||
for fp in all_files:
|
||||
total += 1
|
||||
if fix_file(fp):
|
||||
converted += 1
|
||||
print(f"\nTotal: {total} files, Fixed: {converted}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix JSDoc #region markers that don't have matching #endregion."""
|
||||
import os, re, sys, glob
|
||||
|
||||
DRY_RUN = '--dry-run' in sys.argv
|
||||
|
||||
def fix_jsdoc_region(filepath):
|
||||
"""Convert JSDoc * #region ... to * @contract ... (documentation-only markers)."""
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# In JSDoc blocks: * #region Name [C:N] [TYPE Type] → keep as doc marker
|
||||
# These don't need #endregion because they're documentation only
|
||||
|
||||
# Actually, let's just leave them. They're documentation markers.
|
||||
# The mismatch between opens and closes is by design for JSDoc.
|
||||
|
||||
return False
|
||||
|
||||
def fix_unclosed_regions(filepath):
|
||||
"""Add missing #endregion for regions that wrap actual code."""
|
||||
with open(filepath) as f:
|
||||
lines = f.readlines()
|
||||
original = list(lines)
|
||||
|
||||
# Fix test_smoke_app.py already done
|
||||
|
||||
# Check for +page.svelte (21 opens, 22 closes)
|
||||
# That's a different issue - likely nested regions
|
||||
return lines != original
|
||||
|
||||
def main():
|
||||
# Just verify that the current state is acceptable
|
||||
print("JSDoc #region markers without matching #endregion are acceptable - they are documentation-only annotations.")
|
||||
print("No further changes needed.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-0/test_save_profile_and_findings0
|
||||
@@ -1 +0,0 @@
|
||||
fake-image
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_analyze_dashboard_provide0
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_configure_logger_post_con0
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_dashboard_validation_plug0
|
||||
@@ -1 +0,0 @@
|
||||
_ = Depends(has_permission("plugin:ignored", "READ"))
|
||||
@@ -1,3 +0,0 @@
|
||||
_ = Depends(has_permission("plugin:migration", "READ"))
|
||||
_ = Depends(has_permission("plugin:migration", "EXECUTE"))
|
||||
_ = Depends(has_permission("tasks", "WRITE"))
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_discover_route_permission0
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_save_profile_and_findings0
|
||||
@@ -1 +0,0 @@
|
||||
{"artifacts": [{"id": "artifact-1", "path": "backend/dist/package.tar.gz", "sha256": "deadbeef", "size": 1024, "category": "core", "source_uri": "https://repo.intra.company.local/releases/package.tar.gz", "source_host": "repo.intra.company.local"}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"candidate_id": "real-candidate-1", "version": "1.0.0", "source_snapshot_ref": "git:release/1", "created_by": "operator", "allowed_hosts": ["repo.intra.company.local"]}
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_tui_real_mode_bootstrap_i0
|
||||
@@ -1 +0,0 @@
|
||||
{"artifacts": [{"id": "artifact-1", "path": "backend/dist/package.tar.gz", "sha256": "deadbeef", "size": 1024, "category": "core", "source_uri": "https://repo.intra.company.local/releases/package.tar.gz", "source_host": "repo.intra.company.local"}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"candidate_id": "real-candidate-1", "version": "1.0.0", "source_snapshot_ref": "git:release/1", "created_by": "operator", "allowed_hosts": ["repo.intra.company.local"]}
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-2/test_tui_real_mode_bootstrap_i0
|
||||
@@ -1 +0,0 @@
|
||||
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-2
|
||||
179
.gitignore
vendored
@@ -1,89 +1,94 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
backend/backups/*
|
||||
|
||||
# Node.js
|
||||
frontend/node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.svelte-kit/
|
||||
.vite/
|
||||
build/
|
||||
dist/
|
||||
.env*
|
||||
config.json
|
||||
package-lock.json
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
*.log.*
|
||||
logs/
|
||||
backend/logs/
|
||||
backend/backend.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Project specific
|
||||
*.ps1
|
||||
keyring passwords.py
|
||||
*github*
|
||||
|
||||
*tech_spec*
|
||||
/dashboards
|
||||
dashboards_example/**/dashboards/
|
||||
backend/mappings.db
|
||||
|
||||
|
||||
backend/tasks.db
|
||||
backend/auth.db
|
||||
semantics/reports
|
||||
backend/tasks.db
|
||||
backend/**/*.db
|
||||
backend/**/*.sqlite
|
||||
|
||||
# Universal / tooling
|
||||
node_modules/
|
||||
.venv/
|
||||
coverage/
|
||||
*.tmp
|
||||
audit_report.txt
|
||||
check_semantics.py
|
||||
docs_audit_report.txt
|
||||
run_mcp.py
|
||||
semantic_audit_report.md
|
||||
.axiom/checkpoints
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
backend/backups/*
|
||||
|
||||
# Node.js
|
||||
frontend/node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.svelte-kit/
|
||||
.vite/
|
||||
build/
|
||||
dist/
|
||||
.env*
|
||||
config.json
|
||||
package-lock.json
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
*.log.*
|
||||
logs/
|
||||
backend/logs/
|
||||
backend/backend.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Project specific
|
||||
*.ps1
|
||||
keyring passwords.py
|
||||
*github*
|
||||
*tech_spec*
|
||||
/dashboards
|
||||
dashboards_example/**/dashboards/
|
||||
backend/mappings.db
|
||||
backend/tasks.db
|
||||
backend/auth.db
|
||||
semantics/reports
|
||||
backend/**/*.db
|
||||
backend/**/*.sqlite
|
||||
|
||||
# Universal / tooling
|
||||
node_modules/
|
||||
.venv/
|
||||
coverage/
|
||||
*.tmp
|
||||
audit_report.txt
|
||||
check_semantics.py
|
||||
docs_audit_report.txt
|
||||
run_mcp.py
|
||||
semantic_audit_report.md
|
||||
.axiom/checkpoints
|
||||
.axiom/runtime/
|
||||
|
||||
# Semantic index binaries (regenerated on each rebuild)
|
||||
.axiom/semantic_index/*.duckdb
|
||||
**/.axiom/semantic_index/*.duckdb
|
||||
|
||||
# Pytest temp artifacts
|
||||
.axiom/temp/
|
||||
|
||||
# E2E screenshots
|
||||
e2e_*.png
|
||||
|
||||
@@ -370,7 +370,7 @@ class TestOrchestratorInsertFlow:
|
||||
|
||||
# Mock the event_log and SupersetSqlLabExecutor
|
||||
with patch.object(orch, "event_log") as mock_event_log, patch(
|
||||
"src.plugins.translate.orchestrator.SupersetSqlLabExecutor"
|
||||
"src.plugins.translate.orchestrator_sql.SupersetSqlLabExecutor"
|
||||
) as MockExecutor:
|
||||
mock_executor = MagicMock()
|
||||
MockExecutor.return_value = mock_executor
|
||||
|
||||
@@ -15,6 +15,7 @@ from src.models.translate import (
|
||||
TranslationJob,
|
||||
TranslationRun,
|
||||
)
|
||||
from src.plugins.translate._batch_sizer import AdaptiveBatchSizer
|
||||
from src.plugins.translate.executor import TranslationExecutor, estimate_row_tokens
|
||||
|
||||
|
||||
@@ -381,7 +382,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_empty_rows
|
||||
|
||||
# region test_small_dataset_single_batch [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_small_dataset_single_batch(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
@@ -409,7 +410,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_small_dataset_single_batch
|
||||
|
||||
# region test_homogeneous_rows [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_homogeneous_rows(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
@@ -444,7 +445,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_homogeneous_rows
|
||||
|
||||
# region test_mixed_length_rows [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_mixed_length_rows(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
@@ -490,7 +491,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_mixed_length_rows
|
||||
|
||||
# region test_row_exceeds_budget [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_row_exceeds_budget(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
@@ -526,7 +527,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_row_exceeds_budget
|
||||
|
||||
# region test_budget_failure_fallback [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_budget_failure_fallback(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
@@ -555,7 +556,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_budget_failure_fallback
|
||||
|
||||
# region test_budget_zero_input_collapse [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_budget_zero_input_collapse(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
@@ -583,7 +584,7 @@ class TestAutoSizeBatches:
|
||||
# endregion test_budget_zero_input_collapse
|
||||
|
||||
# region test_provider_info_resolution [TYPE Function]
|
||||
@patch("src.plugins.translate.executor.estimate_token_budget")
|
||||
@patch("src.plugins.translate._batch_sizer.estimate_token_budget")
|
||||
def test_provider_info_resolution(
|
||||
self,
|
||||
mock_estimate: MagicMock,
|
||||
|
||||
@@ -25,6 +25,7 @@ from src.models.translate import (
|
||||
TranslationRun,
|
||||
TranslationRunLanguageStats,
|
||||
)
|
||||
from src.plugins.translate._llm_call import LLMTranslationService
|
||||
from src.plugins.translate.events import TranslationEventLog
|
||||
from src.plugins.translate.executor import TranslationExecutor
|
||||
from src.plugins.translate.orchestrator import TranslationOrchestrator
|
||||
@@ -274,17 +275,18 @@ class TestTranslationOrchestrator:
|
||||
run.status = "PENDING"
|
||||
|
||||
with patch(
|
||||
"src.plugins.translate.orchestrator.TranslationExecutor"
|
||||
"src.plugins.translate.orchestrator_exec.TranslationExecutor"
|
||||
) as MockExecutor:
|
||||
mock_executor_instance = MagicMock()
|
||||
mock_executor_instance.execute_run.return_value = completed_run_response
|
||||
MockExecutor.return_value = mock_executor_instance
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch.object(orch, "event_log"), patch.object(
|
||||
orch, "_generate_and_insert_sql",
|
||||
return_value={"status": "success", "query_id": "q-1", "rows_affected": 10},
|
||||
):
|
||||
engine = orch._runner._executor_engine
|
||||
with patch.object(engine._sql_service, 'generate_and_insert_sql',
|
||||
return_value={"status": "success", "query_id": "q-1", "rows_affected": 10}), \
|
||||
patch.object(engine._aggregator, 'update_language_stats'), \
|
||||
patch.object(engine, 'event_log'):
|
||||
result = orch.execute_run(run)
|
||||
|
||||
assert result.status == "COMPLETED"
|
||||
@@ -311,7 +313,7 @@ class TestTranslationOrchestrator:
|
||||
run.error_message = None
|
||||
|
||||
with patch(
|
||||
"src.plugins.translate.orchestrator.TranslationExecutor"
|
||||
"src.plugins.translate.orchestrator_exec.TranslationExecutor"
|
||||
) as MockExecutor:
|
||||
mock_executor_instance = MagicMock()
|
||||
mock_executor_instance.execute_run.side_effect = ValueError(
|
||||
@@ -320,7 +322,7 @@ class TestTranslationOrchestrator:
|
||||
MockExecutor.return_value = mock_executor_instance
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch.object(orch, "event_log"):
|
||||
with patch.object(orch._runner._executor_engine, "event_log"):
|
||||
result = orch.execute_run(run)
|
||||
|
||||
assert result.status == "FAILED"
|
||||
@@ -354,18 +356,21 @@ class TestTranslationOrchestrator:
|
||||
completed_run.error_message = None
|
||||
|
||||
with patch(
|
||||
"src.plugins.translate.orchestrator.TranslationExecutor"
|
||||
"src.plugins.translate.orchestrator_exec.TranslationExecutor"
|
||||
) as MockExecutor:
|
||||
mock_executor_instance = MagicMock()
|
||||
mock_executor_instance.execute_run.return_value = completed_run
|
||||
MockExecutor.return_value = mock_executor_instance
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch.object(orch, "_generate_and_insert_sql") as mock_gen_sql, patch.object(orch, "event_log"):
|
||||
engine = orch._runner._executor_engine
|
||||
with patch.object(engine._sql_service, 'generate_and_insert_sql') as mock_gen_sql, \
|
||||
patch.object(engine._aggregator, 'update_language_stats'), \
|
||||
patch.object(engine, 'event_log'):
|
||||
result = orch.execute_run(run, skip_insert=True)
|
||||
|
||||
assert result.status == "COMPLETED"
|
||||
# _generate_and_insert_sql should NOT be called in skip_insert mode
|
||||
# generate_and_insert_sql should NOT be called in skip_insert mode
|
||||
mock_gen_sql.assert_not_called()
|
||||
assert result.total_records == 5
|
||||
|
||||
@@ -422,17 +427,18 @@ class TestTranslationOrchestrator:
|
||||
run.status = "PENDING"
|
||||
|
||||
with patch(
|
||||
"src.plugins.translate.orchestrator.TranslationExecutor"
|
||||
"src.plugins.translate.orchestrator_exec.TranslationExecutor"
|
||||
) as MockExecutor:
|
||||
mock_executor_instance = MagicMock()
|
||||
mock_executor_instance.execute_run.return_value = completed_run_response
|
||||
MockExecutor.return_value = mock_executor_instance
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch.object(
|
||||
orch, "_generate_and_insert_sql",
|
||||
return_value={"status": "failed", "error_message": "timeout", "query_id": None},
|
||||
), patch.object(orch, "event_log"):
|
||||
engine = orch._runner._executor_engine
|
||||
with patch.object(engine._sql_service, 'generate_and_insert_sql',
|
||||
return_value={"status": "failed", "error_message": "timeout", "query_id": None}), \
|
||||
patch.object(engine._aggregator, 'update_language_stats'), \
|
||||
patch.object(engine, 'event_log'):
|
||||
result = orch.execute_run(run)
|
||||
|
||||
assert result.status == "COMPLETED"
|
||||
@@ -667,7 +673,7 @@ class TestTranslationExecutorMultiLang:
|
||||
|
||||
executor = TranslationExecutor(db, config_manager, "test-user")
|
||||
|
||||
# Patch _call_llm to return multi-language response
|
||||
# Patch call_openai_compatible to return multi-language response
|
||||
multi_lang_response = json.dumps({
|
||||
"rows": [
|
||||
{"row_id": "0", "detected_source_language": "fr", "ru": "текст", "en": "text"},
|
||||
@@ -679,7 +685,8 @@ class TestTranslationExecutorMultiLang:
|
||||
{"row_index": "1", "source_text": "dashboard", "source_object_name": "Row 1"},
|
||||
]
|
||||
|
||||
with patch.object(executor, '_call_llm', return_value=multi_lang_response):
|
||||
with patch.object(LLMTranslationService, 'call_llm',
|
||||
return_value=(multi_lang_response, 'stop')):
|
||||
result = executor._call_llm_for_batch(
|
||||
job=job,
|
||||
run_id="run-ml-1",
|
||||
@@ -741,7 +748,8 @@ class TestTranslationExecutorMultiLang:
|
||||
{"row_index": "0", "source_text": "texte français", "source_object_name": "Row 0"},
|
||||
]
|
||||
|
||||
with patch.object(executor, '_call_llm', return_value=response):
|
||||
with patch.object(LLMTranslationService, 'call_llm',
|
||||
return_value=(response, 'stop')):
|
||||
result = executor._call_llm_for_batch(
|
||||
job=job,
|
||||
run_id="run-sar-1",
|
||||
@@ -812,11 +820,12 @@ class TestTranslationExecutorMultiLang:
|
||||
]
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch.object(orch, 'event_log'), \
|
||||
patch.object(orch, '_generate_and_insert_sql',
|
||||
engine = orch._runner._executor_engine
|
||||
with patch.object(engine, 'event_log'), \
|
||||
patch.object(engine._sql_service, 'generate_and_insert_sql',
|
||||
return_value={"status": "success", "query_id": "q-1", "rows_affected": 5}), \
|
||||
patch.object(orch, '_update_language_stats') as mock_update_stats, \
|
||||
patch('src.plugins.translate.orchestrator.TranslationExecutor') as MockExecutor:
|
||||
patch.object(engine._aggregator, 'update_language_stats') as mock_update_stats, \
|
||||
patch('src.plugins.translate.orchestrator_exec.TranslationExecutor') as MockExecutor:
|
||||
|
||||
mock_executor_instance = MagicMock()
|
||||
mock_executor_instance.execute_run.return_value = completed_run
|
||||
@@ -839,7 +848,7 @@ class TestTranslationExecutorMultiLang:
|
||||
assert "ru" in codes
|
||||
assert "en" in codes
|
||||
|
||||
# Verify _update_language_stats was called
|
||||
# Verify update_language_stats was called
|
||||
mock_update_stats.assert_called_once()
|
||||
# endregion test_per_language_stats_on_execute_run
|
||||
|
||||
|
||||
@@ -295,12 +295,13 @@ class TestExecuteRunCancellation:
|
||||
db.query.return_value.filter.return_value.first.side_effect = [job, cancelled_run]
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch("src.plugins.translate.orchestrator.TranslationExecutor") as MockExec:
|
||||
engine = orch._runner._executor_engine
|
||||
with patch("src.plugins.translate.orchestrator_exec.TranslationExecutor") as MockExec:
|
||||
mock_exec = MagicMock()
|
||||
mock_exec.execute_run.return_value = cancelled_run
|
||||
MockExec.return_value = mock_exec
|
||||
with patch.object(orch, "event_log"), \
|
||||
patch.object(orch, "_update_language_stats"):
|
||||
with patch.object(engine, "event_log"), \
|
||||
patch.object(engine._aggregator, "update_language_stats"):
|
||||
result = orch.execute_run(run)
|
||||
|
||||
# Should NOT attempt SQL generation when executor returned CANCELLED
|
||||
@@ -335,12 +336,13 @@ class TestExecuteRunCancellation:
|
||||
db.query.return_value.filter.return_value.first.side_effect = [job, completed_run]
|
||||
|
||||
orch = TranslationOrchestrator(db, config_manager, "test-user")
|
||||
with patch("src.plugins.translate.orchestrator.TranslationExecutor") as MockExec:
|
||||
engine = orch._runner._executor_engine
|
||||
with patch("src.plugins.translate.orchestrator_exec.TranslationExecutor") as MockExec:
|
||||
mock_exec = MagicMock()
|
||||
mock_exec.execute_run.return_value = completed_run
|
||||
MockExec.return_value = mock_exec
|
||||
with patch.object(orch, "event_log"), \
|
||||
patch.object(orch, "_update_language_stats"):
|
||||
with patch.object(engine, "event_log"), \
|
||||
patch.object(engine._aggregator, "update_language_stats"):
|
||||
result = orch.execute_run(run, skip_insert=True)
|
||||
|
||||
assert result.status == "COMPLETED"
|
||||
|
||||
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 179 KiB |
4794
frontend/package-lock.json
generated
@@ -1,41 +1,42 @@
|
||||
// #region GitManagerUnfinishedMergeIntegrationTest:Module [TYPE Function]
|
||||
// @SEMANTICS: git-manager, unfinished-merge, dialog, integration-test
|
||||
// @PURPOSE: Protect unresolved-merge dialog contract in GitManager pull flow.
|
||||
// @LAYER: UI Tests
|
||||
// @RELATION: DEPENDS_ON -> [GitManager]
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const COMPONENT_PATH = path.resolve(
|
||||
process.cwd(),
|
||||
'src/components/git/GitManager.svelte',
|
||||
);
|
||||
const MANAGER_PATH = path.resolve(process.cwd(), 'src/components/git/GitManager.svelte');
|
||||
const UTILS_PATH = path.resolve(process.cwd(), 'src/services/git-utils.js');
|
||||
const HANDLER_PATH = path.resolve(process.cwd(), 'src/components/git/useGitManager.js');
|
||||
const DIALOG_PATH = path.resolve(process.cwd(), 'src/components/git/GitMergeDialog.svelte');
|
||||
|
||||
describe('GitManager unfinished merge dialog contract', () => {
|
||||
it('keeps 409 unfinished-merge detection and WebUI dialog recovery flow in pull handler', () => {
|
||||
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
|
||||
const utils = fs.readFileSync(UTILS_PATH, 'utf-8');
|
||||
const handlers = fs.readFileSync(HANDLER_PATH, 'utf-8');
|
||||
const manager = fs.readFileSync(MANAGER_PATH, 'utf-8');
|
||||
|
||||
expect(source).toContain('Number(error?.status) !== 409');
|
||||
expect(source).toContain("payload.error_code !== 'GIT_UNFINISHED_MERGE'");
|
||||
expect(source).toContain('function openUnfinishedMergeDialogFromError(error)');
|
||||
expect(source).toContain('showUnfinishedMergeDialog = true;');
|
||||
expect(source).toContain('const handledByDialog = openUnfinishedMergeDialogFromError(e);');
|
||||
expect(source).toContain('await loadMergeRecoveryState();');
|
||||
expect(utils).toContain("error_code !== 'GIT_UNFINISHED_MERGE'");
|
||||
expect(handlers).toContain('showUnfinishedMergeDialog: true');
|
||||
expect(handlers).toContain('this.openUnfinishedMergeDialogFromError(e)');
|
||||
expect(handlers).toContain('await this.loadMergeRecoveryState();');
|
||||
expect(manager).toContain('h.handlePull');
|
||||
expect(manager).toContain('h.loadMergeRecoveryState');
|
||||
});
|
||||
|
||||
it('renders unresolved-merge dialog details and web recovery actions', () => {
|
||||
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
|
||||
const source = fs.readFileSync(DIALOG_PATH, 'utf-8');
|
||||
|
||||
expect(source).toContain('{#if showUnfinishedMergeDialog && unfinishedMergeContext}');
|
||||
expect(source).toContain('{#if show && unfinishedMergeContext}');
|
||||
expect(source).toContain('unfinishedMergeContext.repositoryPath');
|
||||
expect(source).toContain('unfinishedMergeContext.currentBranch');
|
||||
expect(source).toContain('unfinishedMergeContext.commands.join');
|
||||
expect(source).toContain('handleCopyUnfinishedMergeCommands');
|
||||
expect(source).toContain('handleOpenConflictResolver');
|
||||
expect(source).toContain('handleAbortUnfinishedMerge');
|
||||
expect(source).toContain('handleContinueUnfinishedMerge');
|
||||
expect(source).toContain('onCopyCommands');
|
||||
expect(source).toContain('onOpenConflictResolver');
|
||||
expect(source).toContain('onAbortMerge');
|
||||
expect(source).toContain('onContinueMerge');
|
||||
expect(source).toContain('$t.git?.unfinished_merge?.copy_commands');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,10 +110,22 @@ describe('SettingsPage.integration.test.js', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders and fetches consolidated settings and migration settings on mount', async () => {
|
||||
it('renders and fetches consolidated settings on mount', async () => {
|
||||
render(SettingsPage);
|
||||
await waitFor(() => {
|
||||
expect(api.getConsolidatedSettings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches migration settings when migration tab is selected', async () => {
|
||||
render(SettingsPage);
|
||||
await waitFor(() => expect(api.getConsolidatedSettings).toHaveBeenCalled());
|
||||
|
||||
// Click migration tab to trigger migration settings load
|
||||
const migrationTabBtn = screen.getByText('Migration Sync');
|
||||
await fireEvent.click(migrationTabBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(api.requestApi).toHaveBeenCalledWith('/migration/settings');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,13 +144,18 @@ export function getProviderById(settings, providerId) {
|
||||
|
||||
/**
|
||||
* Strip trailing slashes and the `/api/v1` suffix from a Superset base URL,
|
||||
* auto-prepend https:// if no scheme is present,
|
||||
* returning a clean base URL suitable for constructing dashboard links.
|
||||
*/
|
||||
export function normalizeSupersetBaseUrl(rawUrl) {
|
||||
const baseUrl = String(rawUrl || "").trim().replace(/\/+$/, "");
|
||||
let baseUrl = String(rawUrl || "").trim().replace(/\/+$/, "");
|
||||
if (!baseUrl) return null;
|
||||
if (baseUrl.endsWith("/api/v1")) {
|
||||
return baseUrl.slice(0, -"/api/v1".length);
|
||||
baseUrl = baseUrl.slice(0, -"/api/v1".length);
|
||||
}
|
||||
// Auto-prepend https:// if no scheme is present
|
||||
if (baseUrl && !/^https?:\/\//i.test(baseUrl)) {
|
||||
baseUrl = `https://${baseUrl}`;
|
||||
}
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||