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
This commit is contained in:
2026-05-17 19:22:09 +03:00
parent 3379985ef8
commit ac468121f5
48 changed files with 180 additions and 5427 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-0/test_save_profile_and_findings0

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_analyze_dashboard_provide0

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_configure_logger_post_con0

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_dashboard_validation_plug0

View File

@@ -1 +0,0 @@
_ = Depends(has_permission("plugin:ignored", "READ"))

View File

@@ -1,3 +0,0 @@
_ = Depends(has_permission("plugin:migration", "READ"))
_ = Depends(has_permission("plugin:migration", "EXECUTE"))
_ = Depends(has_permission("tasks", "WRITE"))

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_discover_route_permission0

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_save_profile_and_findings0

View File

@@ -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"}]}

View File

@@ -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"]}

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-1/test_tui_real_mode_bootstrap_i0

View File

@@ -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"}]}

View File

@@ -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"]}

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-2/test_tui_real_mode_bootstrap_i0

View File

@@ -1 +0,0 @@
/home/busya/dev/ss-tools/.axiom/temp/pytest-of-busya/pytest-2

179
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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;
}