synapse-hub 2.4.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/orchestrator.md +1 -0
- package/.agents/agents/pom-crafter.md +1 -0
- package/.agents/agents/quality-gatekeeper.md +6 -0
- package/.agents/rules/skill-precedence.md +38 -18
- package/.agents/rules/systematic-debugging.md +2 -0
- package/.agents/skills/lean-code-ladder/SKILL.md +12 -1
- package/.agents/skills/lean-impact-scoreboard/SKILL.md +48 -0
- package/.agents/skills/lean-impact-scoreboard/examples/sample_benchmark_gain.md +20 -0
- package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +15 -0
- package/.agents/skills/repo-quality-audit/SKILL.md +68 -0
- package/.agents/skills/repo-quality-audit/examples/sample_audit_walkthrough.md +15 -0
- package/.agents/skills/repo-quality-audit/examples/sample_repo_audit.md +33 -0
- package/.agents/skills/senior-code-review/SKILL.md +90 -0
- package/.agents/skills/senior-code-review/examples/sample_review_walkthrough.md +29 -0
- package/.agents/skills/technical-debt-harvester/SKILL.md +70 -0
- package/.agents/skills/technical-debt-harvester/examples/sample_debt_ledger.md +25 -0
- package/.agents/skills/technical-debt-harvester/examples/sample_ledger_output.md +20 -0
- package/.cursor/rules/synapse-qa.mdc +4 -3
- package/.cursorrules +1 -0
- package/.windsurfrules +1 -0
- package/AGENTS.md +51 -179
- package/CLAUDE.md +5 -0
- package/README.en.md +12 -8
- package/README.md +12 -8
- package/bin/agent-qa.js +3 -2
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/CLI_REFERENCE.md +15 -1
- package/package.json +1 -3
- package/skills_manifest.json +38 -2
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +5 -0
- package/templates/ai_bridges/copilot-instructions.md +2 -0
- package/templates/ai_bridges/cursor_synapse_qa.mdc +4 -3
- package/templates/ai_bridges/windsurfrules +1 -0
- package/tools/.infer_baseline.json +7 -1079
- package/tools/TOOLS_RUNBOOK.md +53 -13
- package/tools/__init__.py +1 -1
- package/tools/agent_canary.py +4 -4
- package/tools/analyze_project.py +2 -2
- package/tools/api_fuzzer.py +4 -4
- package/tools/auto_heal.py +4 -2
- package/tools/browser_helpers.js +2 -2
- package/tools/bump_version.py +30 -2
- package/tools/cache_resolver.py +6 -6
- package/tools/check_env.py +13 -7
- package/tools/cli.py +265 -197
- package/tools/count_tokens.py +7 -7
- package/tools/dashboard_generator.py +14 -14
- package/tools/db_sandbox.py +5 -5
- package/tools/debt_harvester.py +236 -0
- package/tools/distill_log.py +2 -2
- package/tools/flaky_detector.py +5 -5
- package/tools/generate_tc_from_story.py +2 -2
- package/tools/generate_test_reports.py +4 -4
- package/tools/git_pre_commit_guard.py +6 -6
- package/tools/infer_linter.py +12 -7
- package/tools/lint_ai_docs.py +2 -2
- package/tools/matrix_sharder.py +4 -4
- package/tools/mcp_excel_server.py +1 -1
- package/tools/mcp_server.py +3 -3
- package/tools/micro_mutator.py +6 -6
- package/tools/omni_transpiler.py +2 -2
- package/tools/output_formatter.py +37 -2
- package/tools/pinpoint_failure.py +4 -1
- package/tools/repo_map.py +2 -2
- package/tools/safety_firewall.py +2 -2
- package/tools/senior_code_review.py +341 -0
- package/tools/setup_project.py +9 -9
- package/tools/skillspector.py +2 -2
- package/tools/sync_agent_rules.py +14 -3
- package/tools/test_impact.py +4 -4
- package/tools/tms_sync_bridge.py +2 -2
- package/tools/update_sentinel.py +17 -17
- package/tools/verify_all.py +12 -12
- package/tools/visual_som_helper.py +8 -8
- package/tools/watch_mode.py +6 -7
- package/tools/wcag_audit.py +5 -1
- package/tools/welcome_page.py +11 -11
- package/benchmarks/qa_eval/README.md +0 -71
- package/benchmarks/qa_eval/build_dataset.py +0 -677
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +0 -22
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +0 -22
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +0 -22
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +0 -22
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +0 -22
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +0 -21
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +0 -21
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +0 -21
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +0 -21
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +0 -21
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +0 -23
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +0 -23
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +0 -23
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +0 -23
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +0 -23
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +0 -22
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +0 -22
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +0 -22
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +0 -22
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +0 -23
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +0 -22
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +0 -22
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +0 -22
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +0 -22
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +0 -22
- package/benchmarks/qa_eval/eval_runner.py +0 -418
- package/examples/cypress-ecommerce-suite/README.md +0 -45
- package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +0 -17
- package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +0 -29
- package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +0 -51
- package/examples/cypress-ecommerce-suite/cypress.config.js +0 -13
- package/examples/cypress-ecommerce-suite/package.json +0 -13
- package/examples/katalon-suite/Keywords/pages/BasePage.groovy +0 -19
- package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +0 -29
- package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +0 -45
- package/examples/katalon-suite/Profiles/default.glbl +0 -12
- package/examples/katalon-suite/README.md +0 -18
- package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +0 -12
- package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +0 -15
- package/examples/katalon-suite/katalon_suite.prj +0 -21
- package/examples/playwright-crm-suite/README.md +0 -40
- package/examples/playwright-crm-suite/package.json +0 -14
- package/examples/playwright-crm-suite/pages/BasePage.ts +0 -37
- package/examples/playwright-crm-suite/pages/CustomerPage.ts +0 -57
- package/examples/playwright-crm-suite/pages/LoginPage.ts +0 -32
- package/examples/playwright-crm-suite/playwright.config.ts +0 -27
- package/examples/playwright-crm-suite/tests/auth.spec.ts +0 -22
- package/examples/playwright-crm-suite/tests/customers.spec.ts +0 -33
- package/examples/plugins/sample-custom-plugin/README.md +0 -14
- package/examples/plugins/sample-custom-plugin/plugin.json +0 -16
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +0 -29
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +0 -8
- package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +0 -125
- package/examples/practices/README.md +0 -22
- package/examples/practices/requirements/crm/requirements_contracts.md +0 -292
- package/examples/practices/requirements/crm/requirements_customers.md +0 -462
- package/examples/practices/requirements/crm/requirements_login.md +0 -202
- package/examples/practices/requirements/crm/requirements_projects.md +0 -396
- package/examples/practices/requirements/crm/requirements_tasks.md +0 -343
- package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +0 -402
- package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +0 -52
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +0 -119
- package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +0 -9
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +0 -32
- package/examples/practices/testcases/api/book_management_api_test_cases.md +0 -144
- package/examples/practices/testcases/api/user_management_api_test_cases.md +0 -139
- package/examples/practices/testcases/crm/test_cases_crm_login.md +0 -148
- package/examples/practices/testcases/crm/test_cases_customers.md +0 -156
- package/examples/pytest-api-suite/README.md +0 -45
- package/examples/pytest-api-suite/conftest.py +0 -31
- package/examples/pytest-api-suite/pages/user_api_client.py +0 -36
- package/examples/pytest-api-suite/requirements.txt +0 -2
- package/examples/pytest-api-suite/tests/test_user_api.py +0 -44
- package/examples/selenium-suite/README.md +0 -18
- package/examples/selenium-suite/pages/base_page.py +0 -27
- package/examples/selenium-suite/pages/login_page.py +0 -29
- package/examples/selenium-suite/pages/order_page.py +0 -25
- package/examples/selenium-suite/pytest.ini +0 -6
- package/examples/selenium-suite/requirements.txt +0 -2
- package/examples/selenium-suite/tests/test_login.py +0 -10
- package/examples/selenium-suite/tests/test_order.py +0 -10
package/tools/count_tokens.py
CHANGED
|
@@ -140,7 +140,7 @@ def get_budget_config() -> Dict[str, int]:
|
|
|
140
140
|
"budget_7d": data.get("budget_7d", DEFAULT_7D_BUDGET),
|
|
141
141
|
}
|
|
142
142
|
except (OSError, json.JSONDecodeError):
|
|
143
|
-
|
|
143
|
+
custom_budgets = None
|
|
144
144
|
return {
|
|
145
145
|
"budget_5h": DEFAULT_5H_BUDGET,
|
|
146
146
|
"budget_7d": DEFAULT_7D_BUDGET,
|
|
@@ -208,7 +208,7 @@ class AntigravityAdapter:
|
|
|
208
208
|
}
|
|
209
209
|
conn.close()
|
|
210
210
|
except (sqlite3.Error, OSError):
|
|
211
|
-
|
|
211
|
+
conn_closed = True
|
|
212
212
|
|
|
213
213
|
conversations = []
|
|
214
214
|
for entry in os.listdir(brain_dir):
|
|
@@ -388,7 +388,7 @@ class AntigravityAdapter:
|
|
|
388
388
|
usage_hours["cache"] += cache_tok
|
|
389
389
|
usage_hours["output"] += out_tok
|
|
390
390
|
except (OSError, UnicodeDecodeError):
|
|
391
|
-
|
|
391
|
+
transcript_read = False
|
|
392
392
|
|
|
393
393
|
budgets = get_budget_config()
|
|
394
394
|
b_5h = budgets["budget_5h"]
|
|
@@ -665,7 +665,7 @@ def detect_platform_from_file(file_path: str) -> str:
|
|
|
665
665
|
if "claude" in line.lower():
|
|
666
666
|
return "claude"
|
|
667
667
|
except (OSError, UnicodeDecodeError):
|
|
668
|
-
|
|
668
|
+
model_detected = None
|
|
669
669
|
return "antigravity"
|
|
670
670
|
|
|
671
671
|
|
|
@@ -985,7 +985,7 @@ def export_opentelemetry_genai_spans(parsed: Dict[str, Any], session_id: str = "
|
|
|
985
985
|
- gen_ai.usage.cache_read_tokens
|
|
986
986
|
- tool.name / tool.system
|
|
987
987
|
"""
|
|
988
|
-
trace_id = hashlib.md5(session_id.encode('utf-8')).hexdigest()
|
|
988
|
+
trace_id = hashlib.md5(session_id.encode('utf-8'), usedforsecurity=False).hexdigest()
|
|
989
989
|
platform = parsed.get("platform", "antigravity")
|
|
990
990
|
model = parsed.get("detected_model", "gemini-flash")
|
|
991
991
|
|
|
@@ -1000,7 +1000,7 @@ def export_opentelemetry_genai_spans(parsed: Dict[str, Any], session_id: str = "
|
|
|
1000
1000
|
spans = []
|
|
1001
1001
|
turns = parsed.get("turns", [])
|
|
1002
1002
|
for idx, turn in enumerate(turns, 1):
|
|
1003
|
-
turn_span_id = hashlib.md5(f"{session_id}_turn_{idx}".encode('utf-8')).hexdigest()[:16]
|
|
1003
|
+
turn_span_id = hashlib.md5(f"{session_id}_turn_{idx}".encode('utf-8'), usedforsecurity=False).hexdigest()[:16]
|
|
1004
1004
|
turn_start_nano = int(time.time() * 1e9) - (len(turns) - idx) * int(1e9)
|
|
1005
1005
|
|
|
1006
1006
|
turn_span = {
|
|
@@ -1025,7 +1025,7 @@ def export_opentelemetry_genai_spans(parsed: Dict[str, Any], session_id: str = "
|
|
|
1025
1025
|
spans.append(turn_span)
|
|
1026
1026
|
|
|
1027
1027
|
for t_idx, tool_name in enumerate(turn.get("tools_called", []), 1):
|
|
1028
|
-
tool_span_id = hashlib.md5(f"{session_id}_turn_{idx}_tool_{t_idx}".encode('utf-8')).hexdigest()[:16]
|
|
1028
|
+
tool_span_id = hashlib.md5(f"{session_id}_turn_{idx}_tool_{t_idx}".encode('utf-8'), usedforsecurity=False).hexdigest()[:16]
|
|
1029
1029
|
tool_span = {
|
|
1030
1030
|
"traceId": trace_id,
|
|
1031
1031
|
"spanId": tool_span_id,
|
|
@@ -14,7 +14,7 @@ Supports Dual-Mode:
|
|
|
14
14
|
1. TARGET PROJECT MODE: Displays target project test suites, POM PageClasses,
|
|
15
15
|
automation tech stack (Playwright/Katalon/Pytest/Cypress), in-project 9 Gates DoD,
|
|
16
16
|
and active AI skill profile.
|
|
17
|
-
2. HUB ECOSYSTEM MODE: Displays central Synapse-Hub catalog (
|
|
17
|
+
2. HUB ECOSYSTEM MODE: Displays central Synapse-Hub catalog (42 skills),
|
|
18
18
|
self-healing benchmark (25 challenges), and platform governance.
|
|
19
19
|
|
|
20
20
|
Features:
|
|
@@ -183,8 +183,8 @@ def load_manifest(hub_root: str) -> Dict[str, Any]:
|
|
|
183
183
|
with open(path, "r", encoding="utf-8") as f:
|
|
184
184
|
return json.load(f)
|
|
185
185
|
except (OSError, json.JSONDecodeError):
|
|
186
|
-
|
|
187
|
-
return {"totalSkills":
|
|
186
|
+
manifest_data = None
|
|
187
|
+
return {"totalSkills": 42, "skills": [], "categories": {}, "profiles": {}}
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
def load_benchmark_report(hub_root: str) -> Dict[str, Any]:
|
|
@@ -209,7 +209,7 @@ def load_benchmark_report(hub_root: str) -> Dict[str, Any]:
|
|
|
209
209
|
"available": True
|
|
210
210
|
}
|
|
211
211
|
except (OSError, UnicodeDecodeError):
|
|
212
|
-
|
|
212
|
+
report_found = False
|
|
213
213
|
return {
|
|
214
214
|
"resolution_rate": "100.0%",
|
|
215
215
|
"mttr": "0.0s",
|
|
@@ -242,7 +242,7 @@ def load_health_report(target_dir: str) -> Dict[str, Any]:
|
|
|
242
242
|
"available": True
|
|
243
243
|
}
|
|
244
244
|
except (OSError, UnicodeDecodeError):
|
|
245
|
-
|
|
245
|
+
report_found = False
|
|
246
246
|
return {
|
|
247
247
|
"dod_status": "100%_PASS",
|
|
248
248
|
"classes_count": 0,
|
|
@@ -308,7 +308,7 @@ def load_latest_execution_results(target_dir: str) -> Dict[str, Any]:
|
|
|
308
308
|
"report_path": rel_path
|
|
309
309
|
}
|
|
310
310
|
except (ET.ParseError, OSError, ValueError, KeyError):
|
|
311
|
-
|
|
311
|
+
parsed_xml = False
|
|
312
312
|
|
|
313
313
|
# Check summaryTemp.txt if JUnit report not parsed
|
|
314
314
|
summary_files = list(target_path.glob("**/summaryTemp.txt"))
|
|
@@ -341,7 +341,7 @@ def load_latest_execution_results(target_dir: str) -> Dict[str, Any]:
|
|
|
341
341
|
"report_path": str(sf.relative_to(target_path))
|
|
342
342
|
}
|
|
343
343
|
except (OSError, ValueError):
|
|
344
|
-
|
|
344
|
+
parsed_summary = False
|
|
345
345
|
|
|
346
346
|
return {
|
|
347
347
|
"available": False,
|
|
@@ -401,7 +401,7 @@ def scan_target_project_assets(target_dir: str, stack_info: Dict[str, str]) -> D
|
|
|
401
401
|
if classes_list:
|
|
402
402
|
break
|
|
403
403
|
except (json.JSONDecodeError, OSError, KeyError):
|
|
404
|
-
|
|
404
|
+
json_parsed = False
|
|
405
405
|
|
|
406
406
|
# Priority B: Parse repo_map.txt
|
|
407
407
|
if not classes_list:
|
|
@@ -451,7 +451,7 @@ def scan_target_project_assets(target_dir: str, stack_info: Dict[str, str]) -> D
|
|
|
451
451
|
if classes_list:
|
|
452
452
|
break
|
|
453
453
|
except (OSError, UnicodeDecodeError):
|
|
454
|
-
|
|
454
|
+
continue
|
|
455
455
|
|
|
456
456
|
# Priority C: Scan filesystem directly if repo_map files were missing
|
|
457
457
|
if not classes_list:
|
|
@@ -488,7 +488,7 @@ def scan_target_project_assets(target_dir: str, stack_info: Dict[str, str]) -> D
|
|
|
488
488
|
"methods": m_list or ["(Defined in source)"]
|
|
489
489
|
})
|
|
490
490
|
except (OSError, UnicodeDecodeError):
|
|
491
|
-
|
|
491
|
+
report_found = False
|
|
492
492
|
|
|
493
493
|
# 2. Discover Test Cases
|
|
494
494
|
test_files = []
|
|
@@ -1766,7 +1766,7 @@ def generate_hub_html(
|
|
|
1766
1766
|
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
1767
1767
|
total_skills = manifest.get("totalSkills", len(manifest.get("skills", [])))
|
|
1768
1768
|
if total_skills == 0:
|
|
1769
|
-
total_skills =
|
|
1769
|
+
total_skills = 42
|
|
1770
1770
|
skills = manifest.get("skills", [])
|
|
1771
1771
|
|
|
1772
1772
|
gates = [
|
|
@@ -1885,7 +1885,7 @@ def generate_hub_html(
|
|
|
1885
1885
|
|
|
1886
1886
|
translations_vi = {
|
|
1887
1887
|
"header_title": "Bảng Điều Hành Chất Lượng Doanh Nghiệp",
|
|
1888
|
-
"header_subtitle": "Synapse-Hub v2.
|
|
1888
|
+
"header_subtitle": "Synapse-Hub v2.5.0 • Hệ Sinh Thái Kiểm Thử AI Agent Tự Chủ",
|
|
1889
1889
|
"nav_welcome": "Giới Thiệu",
|
|
1890
1890
|
"meta_mode": "🏷️ Chế độ: <strong>HỆ SINH THÁI QA HUB</strong>",
|
|
1891
1891
|
"meta_gen": f"🕒 Khởi tạo: <strong>{now_str}</strong>",
|
|
@@ -1963,7 +1963,7 @@ def generate_hub_html(
|
|
|
1963
1963
|
|
|
1964
1964
|
translations_en = {
|
|
1965
1965
|
"header_title": "Enterprise QA Executive Dashboard",
|
|
1966
|
-
"header_subtitle": "Synapse-Hub v2.
|
|
1966
|
+
"header_subtitle": "Synapse-Hub v2.5.0 • Autonomous AI Agent Testing Ecosystem",
|
|
1967
1967
|
"nav_welcome": "Welcome",
|
|
1968
1968
|
"meta_mode": "🏷️ Mode: <strong>QA HUB ECOSYSTEM</strong>",
|
|
1969
1969
|
"meta_gen": f"🕒 Generated: <strong>{now_str}</strong>",
|
|
@@ -2429,7 +2429,7 @@ def generate_hub_html(
|
|
|
2429
2429
|
</div>
|
|
2430
2430
|
{timeline_section_html}
|
|
2431
2431
|
|
|
2432
|
-
<!--
|
|
2432
|
+
<!-- 42 Specialized Skills Catalog -->
|
|
2433
2433
|
<div class="section-header">
|
|
2434
2434
|
<h2 data-i18n="sec_skills_title">{t['sec_skills_title']}</h2>
|
|
2435
2435
|
<span class="section-badge" data-i18n="sec_skills_badge">{t['sec_skills_badge']}</span>
|
package/tools/db_sandbox.py
CHANGED
|
@@ -143,8 +143,8 @@ class DBSandbox:
|
|
|
143
143
|
with open(meta_file, "r", encoding="utf-8") as f:
|
|
144
144
|
meta = json.load(f)
|
|
145
145
|
self.db_path = meta.get("source_db", "")
|
|
146
|
-
except
|
|
147
|
-
|
|
146
|
+
except (OSError, json.JSONDecodeError):
|
|
147
|
+
meta = {}
|
|
148
148
|
if not self.db_path:
|
|
149
149
|
self.db_path = os.path.join(self.snapshot_dir, "active_test.db")
|
|
150
150
|
|
|
@@ -212,8 +212,8 @@ class DBSandbox:
|
|
|
212
212
|
try:
|
|
213
213
|
cursor.execute(f"ROLLBACK TO SAVEPOINT {savepoint_name};")
|
|
214
214
|
conn.close()
|
|
215
|
-
except
|
|
216
|
-
|
|
215
|
+
except sqlite3.Error as rollback_err:
|
|
216
|
+
sys.stderr.write(f"Notice: Savepoint rollback notice: {rollback_err}\n")
|
|
217
217
|
return {
|
|
218
218
|
"status": "ERROR",
|
|
219
219
|
"error": str(e),
|
|
@@ -340,7 +340,7 @@ def _probe_http(target: str) -> Tuple[bool, str]:
|
|
|
340
340
|
such as 401/403 still proves the service is up)."""
|
|
341
341
|
try:
|
|
342
342
|
req = urllib.request.Request(target, method="GET")
|
|
343
|
-
with urllib.request.urlopen(req, timeout=2.0) as resp:
|
|
343
|
+
with urllib.request.urlopen(req, timeout=2.0) as resp: # nosec B310
|
|
344
344
|
return True, f"http {resp.getcode()}"
|
|
345
345
|
except urllib.error.HTTPError as exc:
|
|
346
346
|
if exc.code < 500:
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Thế Vị (@TeeVee998). All rights reserved.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
# Project: Synapse-Hub (https://github.com/TeeVee998/Synapse-Hub)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
tools/debt_harvester.py — Technical Debt & Shortcut Harvester
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
Scans repositories for deliberate simplification markers ('shortcut:' or 'ponytail:').
|
|
10
|
+
Compiles them into a structured Technical Debt Ledger so pragmatic compromises
|
|
11
|
+
and deferred upgrades are tracked and do not rot into forgotten technical debt.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
python3 tools/debt_harvester.py [target_dir]
|
|
15
|
+
python3 tools/debt_harvester.py [target_dir] --output DEBT_LEDGER.md
|
|
16
|
+
python3 tools/debt_harvester.py [target_dir] --json
|
|
17
|
+
python3 tools/debt_harvester.py [target_dir] --keyword "custom-tag:"
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import re
|
|
23
|
+
import json
|
|
24
|
+
import argparse
|
|
25
|
+
from typing import Dict, List, Any, Optional
|
|
26
|
+
|
|
27
|
+
# UTF-8 stdout encoding enforcement
|
|
28
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
29
|
+
try:
|
|
30
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
31
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# Default directories to ignore during scan
|
|
36
|
+
IGNORED_DIRS = {
|
|
37
|
+
".git", "node_modules", "dist", "build", ".next", "out",
|
|
38
|
+
"venv", ".venv", "env", ".env", "__pycache__", ".idea",
|
|
39
|
+
".vscode", "coverage", ".mypy_cache", ".ruff_cache",
|
|
40
|
+
".pytest_cache", ".sandbox_snapshots", "graphify-out",
|
|
41
|
+
"claude-ecosystem-repos"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Supported comment markers across major languages
|
|
45
|
+
# Supports: # (Python, Bash, YAML), // (JS, TS, Java, Groovy, C, C++),
|
|
46
|
+
# /* ... */, -- (SQL, Lua), <!-- (HTML, XML, Markdown)
|
|
47
|
+
DEBT_REGEX = re.compile(
|
|
48
|
+
r'(?:#|//|/\*|--|<!--)\s*(?:shortcut|ponytail):\s*(.+?)(?:\*/|-->)?$',
|
|
49
|
+
re.IGNORECASE
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ShortcutEntry:
|
|
54
|
+
def __init__(self, file_path: str, line_no: int, raw_comment: str):
|
|
55
|
+
self.file_path = file_path
|
|
56
|
+
self.line_no = line_no
|
|
57
|
+
self.raw_comment = raw_comment.strip()
|
|
58
|
+
self.description = ""
|
|
59
|
+
self.ceiling = ""
|
|
60
|
+
self.upgrade_trigger = ""
|
|
61
|
+
self.has_trigger = False
|
|
62
|
+
self._parse()
|
|
63
|
+
|
|
64
|
+
def _parse(self):
|
|
65
|
+
text = self.raw_comment
|
|
66
|
+
# Format patterns:
|
|
67
|
+
# 1. shortcut: <desc>, ceiling: <limit>, upgrade: <trigger>
|
|
68
|
+
# 2. shortcut: <desc>, <limit>, <trigger>
|
|
69
|
+
# 3. shortcut: <limit>, <when to upgrade>
|
|
70
|
+
|
|
71
|
+
parts = [p.strip() for p in text.split(',') if p.strip()]
|
|
72
|
+
|
|
73
|
+
# Check explicit labels
|
|
74
|
+
ceiling_match = re.search(r'(?:ceiling|limit):\s*([^,]+)', text, re.IGNORECASE)
|
|
75
|
+
upgrade_match = re.search(r'(?:upgrade|when|trigger):\s*([^,]+)', text, re.IGNORECASE)
|
|
76
|
+
|
|
77
|
+
if ceiling_match or upgrade_match:
|
|
78
|
+
self.ceiling = ceiling_match.group(1).strip() if ceiling_match else ""
|
|
79
|
+
self.upgrade_trigger = upgrade_match.group(1).strip() if upgrade_match else ""
|
|
80
|
+
# Description is what's left before ceiling/upgrade
|
|
81
|
+
desc_part = re.split(r'(?:ceiling|limit|upgrade|when|trigger):', text, flags=re.IGNORECASE)[0]
|
|
82
|
+
self.description = desc_part.strip().rstrip(',')
|
|
83
|
+
elif len(parts) >= 2:
|
|
84
|
+
self.description = parts[0]
|
|
85
|
+
self.ceiling = parts[1]
|
|
86
|
+
if len(parts) >= 3:
|
|
87
|
+
self.upgrade_trigger = parts[2]
|
|
88
|
+
else:
|
|
89
|
+
self.upgrade_trigger = parts[1]
|
|
90
|
+
elif len(parts) == 1:
|
|
91
|
+
self.description = parts[0]
|
|
92
|
+
self.ceiling = "Not specified"
|
|
93
|
+
self.upgrade_trigger = ""
|
|
94
|
+
|
|
95
|
+
if self.upgrade_trigger:
|
|
96
|
+
self.has_trigger = True
|
|
97
|
+
|
|
98
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
99
|
+
return {
|
|
100
|
+
"file": self.file_path,
|
|
101
|
+
"line": self.line_no,
|
|
102
|
+
"description": self.description or self.raw_comment,
|
|
103
|
+
"ceiling": self.ceiling or "Not specified",
|
|
104
|
+
"upgrade_trigger": self.upgrade_trigger or "No trigger specified",
|
|
105
|
+
"has_trigger": self.has_trigger,
|
|
106
|
+
"raw": self.raw_comment
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def scan_directory(target_dir: str, keyword: Optional[str] = None) -> List[ShortcutEntry]:
|
|
111
|
+
"""Scans all non-ignored source files in target_dir for shortcut/debt markers."""
|
|
112
|
+
target_dir = os.path.abspath(target_dir)
|
|
113
|
+
results: List[ShortcutEntry] = []
|
|
114
|
+
|
|
115
|
+
pattern = DEBT_REGEX
|
|
116
|
+
if keyword:
|
|
117
|
+
escaped = re.escape(keyword.rstrip(':'))
|
|
118
|
+
pattern = re.compile(
|
|
119
|
+
rf'(?:#|//|/\*|--|<!--)\s*{escaped}:\s*(.+?)(?:\*/|-->)?$',
|
|
120
|
+
re.IGNORECASE
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
for root, dirs, files in os.walk(target_dir):
|
|
124
|
+
# Exclude ignored directories in-place
|
|
125
|
+
dirs[:] = [d for d in dirs if d not in IGNORED_DIRS and not d.startswith('.')]
|
|
126
|
+
|
|
127
|
+
for f in sorted(files):
|
|
128
|
+
# Skip non-text or binary files
|
|
129
|
+
ext = os.path.splitext(f)[1].lower()
|
|
130
|
+
if ext in {".png", ".jpg", ".jpeg", ".svg", ".zip", ".tar", ".gz", ".lock", ".ico", ".pdf"}:
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
file_path = os.path.join(root, f)
|
|
134
|
+
rel_path = os.path.relpath(file_path, target_dir)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as fh:
|
|
138
|
+
for line_idx, line in enumerate(fh, start=1):
|
|
139
|
+
match = pattern.search(line)
|
|
140
|
+
if match:
|
|
141
|
+
comment_body = match.group(1).strip()
|
|
142
|
+
# Filter false positives like documentation describing the marker itself
|
|
143
|
+
if "shortcut: <" in comment_body or "shortcut: (" in comment_body:
|
|
144
|
+
continue
|
|
145
|
+
results.append(ShortcutEntry(rel_path, line_idx, comment_body))
|
|
146
|
+
except Exception:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def format_markdown(entries: List[ShortcutEntry], target_dir: str) -> str:
|
|
153
|
+
"""Renders shortcut debt entries into a structured Markdown ledger."""
|
|
154
|
+
total = len(entries)
|
|
155
|
+
no_trigger = sum(1 for e in entries if not e.has_trigger)
|
|
156
|
+
|
|
157
|
+
lines = [
|
|
158
|
+
"# Technical Debt & Shortcut Ledger",
|
|
159
|
+
"",
|
|
160
|
+
"> **Ledger Policy:** Deliberate shortcuts prevent over-engineering. Track limits and upgrade triggers so they never rot silently into permanent defects.",
|
|
161
|
+
"",
|
|
162
|
+
f"- **Repository:** `{os.path.basename(target_dir)}`",
|
|
163
|
+
f"- **Total Shortcuts:** `{total}`",
|
|
164
|
+
f"- **Missing Upgrade Triggers:** `{no_trigger}` " + ("⚠️ *(Action Required)*" if no_trigger > 0 else "✅ *(All tracked)*"),
|
|
165
|
+
"",
|
|
166
|
+
"---",
|
|
167
|
+
"",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
if not entries:
|
|
171
|
+
lines.append("🎉 **No shortcut debt found. Clean ledger.**")
|
|
172
|
+
return "\n".join(lines)
|
|
173
|
+
|
|
174
|
+
lines.append("| Location | Description | Ceiling (Limit) | Upgrade Trigger | Status |")
|
|
175
|
+
lines.append("| :--- | :--- | :--- | :--- | :--- |")
|
|
176
|
+
|
|
177
|
+
for e in entries:
|
|
178
|
+
status = "✅ Tracked" if e.has_trigger else "⚠️ `no-trigger`"
|
|
179
|
+
loc = f"`{e.file_path}:{e.line_no}`"
|
|
180
|
+
lines.append(f"| {loc} | {e.description} | {e.ceiling} | {e.upgrade_trigger or 'None'} | {status} |")
|
|
181
|
+
|
|
182
|
+
lines.append("")
|
|
183
|
+
lines.append("### Recommended Action")
|
|
184
|
+
if no_trigger > 0:
|
|
185
|
+
lines.append(f"1. Review the `{no_trigger}` shortcut(s) tagged `no-trigger` above and define concrete upgrade conditions.")
|
|
186
|
+
else:
|
|
187
|
+
lines.append("1. All tracked shortcuts have defined upgrade boundaries. Review them during sprint refactoring cycles.")
|
|
188
|
+
lines.append("2. Run `agent-qa debt` in CI to monitor debt accumulation.")
|
|
189
|
+
|
|
190
|
+
return "\n".join(lines)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def run_debt_harvest(target_dir: str, keyword: Optional[str] = None, output_file: Optional[str] = None, as_json: bool = False) -> int:
|
|
194
|
+
"""Executes the debt scan and prints/writes results."""
|
|
195
|
+
entries = scan_directory(target_dir, keyword)
|
|
196
|
+
total = len(entries)
|
|
197
|
+
no_trigger = sum(1 for e in entries if not e.has_trigger)
|
|
198
|
+
|
|
199
|
+
if as_json:
|
|
200
|
+
payload = {
|
|
201
|
+
"target": os.path.abspath(target_dir),
|
|
202
|
+
"total": total,
|
|
203
|
+
"no_trigger_count": no_trigger,
|
|
204
|
+
"items": [e.to_dict() for e in entries]
|
|
205
|
+
}
|
|
206
|
+
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
md_report = format_markdown(entries, target_dir)
|
|
210
|
+
|
|
211
|
+
if output_file:
|
|
212
|
+
out_path = os.path.abspath(output_file)
|
|
213
|
+
with open(out_path, "w", encoding="utf-8") as f:
|
|
214
|
+
f.write(md_report)
|
|
215
|
+
print(f"\n✅ Technical Debt Ledger saved to: {out_path} ({total} entries)")
|
|
216
|
+
else:
|
|
217
|
+
print("\n" + md_report)
|
|
218
|
+
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
parser = argparse.ArgumentParser(
|
|
224
|
+
description="Harvest shortcut: and deliberate debt comments into a tracked technical debt ledger."
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument("target", nargs="?", default=".", help="Directory to scan (default: current directory)")
|
|
227
|
+
parser.add_argument("--keyword", "-k", default=None, help="Custom marker keyword (default: shortcut or ponytail)")
|
|
228
|
+
parser.add_argument("--output", "-o", default=None, help="Save report to Markdown file")
|
|
229
|
+
parser.add_argument("--json", action="store_true", help="Output results in JSON format")
|
|
230
|
+
|
|
231
|
+
args = parser.parse_args()
|
|
232
|
+
sys.exit(run_debt_harvest(args.target, args.keyword, args.output, args.json))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
main()
|
package/tools/distill_log.py
CHANGED
|
@@ -149,8 +149,8 @@ def main():
|
|
|
149
149
|
os.makedirs(os.path.dirname(os.path.abspath(args.save_raw)), exist_ok=True)
|
|
150
150
|
with open(args.save_raw, "w", encoding="utf-8") as fp:
|
|
151
151
|
fp.write(log_text)
|
|
152
|
-
except
|
|
153
|
-
|
|
152
|
+
except OSError as err:
|
|
153
|
+
sys.stderr.write(f"Warning: Could not save raw log: {err}\n")
|
|
154
154
|
|
|
155
155
|
summary = distill(log_text)
|
|
156
156
|
print(summary)
|
package/tools/flaky_detector.py
CHANGED
|
@@ -15,10 +15,10 @@ Features:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
|
-
import re
|
|
19
18
|
import sys
|
|
20
19
|
import glob
|
|
21
20
|
import json
|
|
21
|
+
import sqlite3
|
|
22
22
|
import time
|
|
23
23
|
import shlex
|
|
24
24
|
import argparse
|
|
@@ -187,8 +187,8 @@ def benchmark_live_command(cmd: str, iterations: int = 5, test_id: Optional[str]
|
|
|
187
187
|
error_signature="NON_ZERO_EXIT" if not is_pass else "",
|
|
188
188
|
context=cmd
|
|
189
189
|
)
|
|
190
|
-
except
|
|
191
|
-
|
|
190
|
+
except sqlite3.Error as db_err:
|
|
191
|
+
sys.stderr.write(f"Notice: Run recording skipped: {db_err}\n")
|
|
192
192
|
|
|
193
193
|
status_str = f"{GREEN}PASS{RESET}" if is_pass else f"{RED}FAIL (Exit {code}){RESET}"
|
|
194
194
|
print(f"{status_str} ({ms} ms)")
|
|
@@ -362,8 +362,8 @@ def main():
|
|
|
362
362
|
duration_ms=0.0,
|
|
363
363
|
context=args.reports
|
|
364
364
|
)
|
|
365
|
-
except
|
|
366
|
-
|
|
365
|
+
except sqlite3.Error as db_err:
|
|
366
|
+
sys.stderr.write(f"Notice: Run recording skipped: {db_err}\n")
|
|
367
367
|
|
|
368
368
|
if st["quarantine"]:
|
|
369
369
|
quarantine_candidates.append(st)
|
|
@@ -43,8 +43,8 @@ def detect_project_package(repo_root="."):
|
|
|
43
43
|
m = re.search(r'import\s+([\w\.]+)\.pages\.', content)
|
|
44
44
|
if m:
|
|
45
45
|
return f"{m.group(1)}.pages"
|
|
46
|
-
except
|
|
47
|
-
|
|
46
|
+
except (OSError, UnicodeDecodeError):
|
|
47
|
+
content = ""
|
|
48
48
|
|
|
49
49
|
# 2. Check existing Keywords/ directory
|
|
50
50
|
keywords_dir = os.path.join(repo_root, "Keywords")
|
|
@@ -27,8 +27,8 @@ def find_latest_junit_report(search_dirs):
|
|
|
27
27
|
try:
|
|
28
28
|
mtime = os.path.getmtime(p)
|
|
29
29
|
candidates.append((mtime, p))
|
|
30
|
-
except OSError:
|
|
31
|
-
|
|
30
|
+
except OSError as err:
|
|
31
|
+
sys.stderr.write(f"Notice: getmtime skipped: {err}\n")
|
|
32
32
|
if not candidates:
|
|
33
33
|
return None
|
|
34
34
|
candidates.sort(key=lambda x: x[0], reverse=True)
|
|
@@ -96,8 +96,8 @@ def generate_reports(target_dir=None, project_name="Enterprise-Automation"):
|
|
|
96
96
|
if "Passed" in content:
|
|
97
97
|
print(f"[+] Found existing summary file: {existing}")
|
|
98
98
|
return 0
|
|
99
|
-
except OSError:
|
|
100
|
-
|
|
99
|
+
except OSError as err:
|
|
100
|
+
sys.stderr.write(f"Notice: Read summaryTemp skipped: {err}\n")
|
|
101
101
|
|
|
102
102
|
latest_xml = find_latest_junit_report([reports_dir, test_reports_dir, root_dir])
|
|
103
103
|
if latest_xml:
|
|
@@ -41,8 +41,8 @@ def check_bom(files):
|
|
|
41
41
|
with open(f, "rb") as fp:
|
|
42
42
|
if fp.read(3) == b"\xef\xbb\xbf":
|
|
43
43
|
bom_files.append(f)
|
|
44
|
-
except
|
|
45
|
-
|
|
44
|
+
except OSError as err:
|
|
45
|
+
sys.stderr.write(f"Notice: Could not read {f}: {err}\n")
|
|
46
46
|
return bom_files
|
|
47
47
|
|
|
48
48
|
def check_forbidden_paths(files):
|
|
@@ -62,8 +62,8 @@ def check_forbidden_paths(files):
|
|
|
62
62
|
for forbidden in FORBIDDEN_PATHS:
|
|
63
63
|
if forbidden in line:
|
|
64
64
|
violations.append((f, line_no, forbidden))
|
|
65
|
-
except
|
|
66
|
-
|
|
65
|
+
except OSError as err:
|
|
66
|
+
sys.stderr.write(f"Notice: Could not read {f}: {err}\n")
|
|
67
67
|
return violations
|
|
68
68
|
|
|
69
69
|
def check_scripts_clean_pom(files):
|
|
@@ -83,8 +83,8 @@ def check_scripts_clean_pom(files):
|
|
|
83
83
|
violations.append((f, line_no, "Raw findTestObject is forbidden in Scripts/"))
|
|
84
84
|
if "//" in line and ("//table" in line or "//input" in line or "//button" in line):
|
|
85
85
|
violations.append((f, line_no, "Raw XPath is forbidden in Scripts/"))
|
|
86
|
-
except
|
|
87
|
-
|
|
86
|
+
except OSError as err:
|
|
87
|
+
sys.stderr.write(f"Notice: Could not parse {f}: {err}\n")
|
|
88
88
|
return violations
|
|
89
89
|
|
|
90
90
|
def main():
|
package/tools/infer_linter.py
CHANGED
|
@@ -185,7 +185,7 @@ def load_config(repo_root=None, config_path=None):
|
|
|
185
185
|
with open(config_path, "r", encoding="utf-8") as f:
|
|
186
186
|
return json.load(f)
|
|
187
187
|
except (OSError, json.JSONDecodeError):
|
|
188
|
-
|
|
188
|
+
config_data = {}
|
|
189
189
|
|
|
190
190
|
root = repo_root or REPO_ROOT
|
|
191
191
|
candidates = [
|
|
@@ -198,7 +198,7 @@ def load_config(repo_root=None, config_path=None):
|
|
|
198
198
|
with open(c, "r", encoding="utf-8") as f:
|
|
199
199
|
return json.load(f)
|
|
200
200
|
except (OSError, json.JSONDecodeError):
|
|
201
|
-
|
|
201
|
+
continue
|
|
202
202
|
return {}
|
|
203
203
|
|
|
204
204
|
|
|
@@ -325,8 +325,8 @@ def classify_file_role(file_path: str) -> str:
|
|
|
325
325
|
if re.search(r'(^|_)basepage\b', base) or re.search(r'[a-z0-9_]page\.(ts|js|py|groovy|java)$', base):
|
|
326
326
|
return "PAGE_OBJECT"
|
|
327
327
|
|
|
328
|
-
# 3. Helpers & Fixtures / Tools & Utilities
|
|
329
|
-
if any(p in ("fixtures", "helpers", "utils", "support", "tools", "bin") for p in parts):
|
|
328
|
+
# 3. Helpers & Fixtures / Tools & Utilities / Benchmarks
|
|
329
|
+
if any(p in ("fixtures", "helpers", "utils", "support", "tools", "bin", "benchmarks", "benchmark") for p in parts):
|
|
330
330
|
return "HELPER"
|
|
331
331
|
if any(base.endswith(sfx) for sfx in ("helper.ts", "helper.js", "helper.py", "helper.groovy", "utils.ts", "utils.js", "utils.py", "fixture.ts", "fixture.js")):
|
|
332
332
|
return "HELPER"
|
|
@@ -613,7 +613,12 @@ def run_infer_linter(target_dir=None, output_path=None, strict: bool = False,
|
|
|
613
613
|
for ext in EXTENSIONS:
|
|
614
614
|
raw_files.extend(glob.glob(os.path.join(scan_dir, "**", ext), recursive=True))
|
|
615
615
|
# Filter out non-test files like tools, __init__.py, node_modules when scanning generic directory
|
|
616
|
-
ignored_parts = {
|
|
616
|
+
ignored_parts = {
|
|
617
|
+
"tools", "bin", "node_modules", ".git", "__pycache__", "venv", ".venv",
|
|
618
|
+
"claude-ecosystem-repos", "dist", "build", ".idea", ".vscode",
|
|
619
|
+
".mypy_cache", ".pytest_cache", ".ruff_cache", ".sandbox_snapshots",
|
|
620
|
+
"graphify-out"
|
|
621
|
+
} | extra_ignored
|
|
617
622
|
script_files = [
|
|
618
623
|
f for f in raw_files
|
|
619
624
|
if not any(part.lower() in ignored_parts for part in os.path.relpath(f, scan_dir).replace("\\", "/").split("/")[:-1])
|
|
@@ -768,8 +773,8 @@ def run_infer_linter(target_dir=None, output_path=None, strict: bool = False,
|
|
|
768
773
|
os.makedirs(os.path.dirname(target_out), exist_ok=True)
|
|
769
774
|
with open(target_out, "w", encoding="utf-8") as f:
|
|
770
775
|
json.dump(report_data, f, indent=2, ensure_ascii=False)
|
|
771
|
-
except OSError:
|
|
772
|
-
|
|
776
|
+
except OSError as err:
|
|
777
|
+
sys.stderr.write(f"Notice: Target report write failed: {err}\n")
|
|
773
778
|
|
|
774
779
|
if strict:
|
|
775
780
|
fake_pass_count = sum(1 for r in results for d in r["defects"] if d["type"] in ("FAKE_PASS_NO_ASSERTION", "NO_ASSERTION_IN_TEST"))
|
package/tools/lint_ai_docs.py
CHANGED
|
@@ -55,8 +55,8 @@ def extract_groovy_methods(file_path):
|
|
|
55
55
|
mname = m.group(1)
|
|
56
56
|
if mname not in {"if", "for", "while", "switch", "catch"}:
|
|
57
57
|
methods.add(mname)
|
|
58
|
-
except
|
|
59
|
-
|
|
58
|
+
except (OSError, UnicodeDecodeError) as err:
|
|
59
|
+
sys.stderr.write(f"Notice: Parse methods skipped for {file_path}: {err}\n")
|
|
60
60
|
return methods
|
|
61
61
|
|
|
62
62
|
def get_actual_entities(repo_root):
|
package/tools/matrix_sharder.py
CHANGED
|
@@ -59,8 +59,8 @@ def estimate_test_duration(file_path: str, full_path: str) -> float:
|
|
|
59
59
|
conn.close()
|
|
60
60
|
if row and row[0]:
|
|
61
61
|
return float(row[0])
|
|
62
|
-
except
|
|
63
|
-
|
|
62
|
+
except sqlite3.Error:
|
|
63
|
+
conn = None
|
|
64
64
|
|
|
65
65
|
# Heuristic fallback: ~0.5s per line of script
|
|
66
66
|
try:
|
|
@@ -68,8 +68,8 @@ def estimate_test_duration(file_path: str, full_path: str) -> float:
|
|
|
68
68
|
with open(full_path, "r", encoding="utf-8", errors="ignore") as fp:
|
|
69
69
|
lines = len(fp.readlines())
|
|
70
70
|
return max(5.0, round(lines * 0.25, 1))
|
|
71
|
-
except
|
|
72
|
-
|
|
71
|
+
except (OSError, UnicodeDecodeError):
|
|
72
|
+
lines = 0
|
|
73
73
|
|
|
74
74
|
return 10.0
|
|
75
75
|
|
|
@@ -23,7 +23,7 @@ if hasattr(sys.stdout, 'reconfigure'):
|
|
|
23
23
|
except Exception:
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
|
-
SERVER_VERSION = "2.
|
|
26
|
+
SERVER_VERSION = "2.5.0"
|
|
27
27
|
|
|
28
28
|
NS_MAIN = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}"
|
|
29
29
|
NS_REL = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}"
|