synapse-hub 2.4.3 → 2.6.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.
Files changed (184) hide show
  1. package/.agents/agents/failure-analyst.md +13 -0
  2. package/.agents/agents/orchestrator.md +5 -2
  3. package/.agents/agents/pom-crafter.md +13 -0
  4. package/.agents/agents/quality-gatekeeper.md +21 -2
  5. package/.agents/agents/test-runner.md +11 -0
  6. package/.agents/rules/context-engineering.md +22 -102
  7. package/.agents/rules/mobile_appium_rules.md +19 -115
  8. package/.agents/rules/performance_testing_rules.md +18 -62
  9. package/.agents/rules/skill-precedence.md +24 -24
  10. package/.agents/rules/systematic-debugging.md +10 -27
  11. package/.agents/skills/lean-code-ladder/SKILL.md +12 -1
  12. package/.agents/skills/lean-impact-scoreboard/SKILL.md +48 -0
  13. package/.agents/skills/lean-impact-scoreboard/examples/sample_benchmark_gain.md +20 -0
  14. package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +15 -0
  15. package/.agents/skills/repo-quality-audit/SKILL.md +68 -0
  16. package/.agents/skills/repo-quality-audit/examples/sample_audit_walkthrough.md +15 -0
  17. package/.agents/skills/repo-quality-audit/examples/sample_repo_audit.md +33 -0
  18. package/.agents/skills/senior-code-review/SKILL.md +90 -0
  19. package/.agents/skills/senior-code-review/examples/sample_review_walkthrough.md +29 -0
  20. package/.agents/skills/technical-debt-harvester/SKILL.md +70 -0
  21. package/.agents/skills/technical-debt-harvester/examples/sample_debt_ledger.md +25 -0
  22. package/.agents/skills/technical-debt-harvester/examples/sample_ledger_output.md +20 -0
  23. package/.agents/skills/token-and-context-optimizer/references/context_engineering_guide.md +49 -0
  24. package/.cursor/rules/synapse-qa.mdc +4 -3
  25. package/.cursorrules +1 -0
  26. package/.windsurfrules +1 -0
  27. package/AGENTS.md +57 -180
  28. package/CLAUDE.md +5 -0
  29. package/NOTICE +40 -4
  30. package/README.en.md +33 -13
  31. package/README.md +36 -16
  32. package/bin/agent-qa.js +3 -2
  33. package/docs/ARCHITECTURE.md +39 -11
  34. package/docs/CLI_REFERENCE.md +15 -1
  35. package/docs/adopted-ecosystem-tools.md +9 -0
  36. package/docs/research/agent_skill_training_analysis.md +1357 -0
  37. package/package.json +1 -3
  38. package/skills_manifest.json +39 -3
  39. package/synapse.config.json +3 -2
  40. package/templates/ai_bridges/CLAUDE.md +5 -0
  41. package/templates/ai_bridges/copilot-instructions.md +2 -0
  42. package/templates/ai_bridges/cursor_synapse_qa.mdc +4 -3
  43. package/templates/ai_bridges/windsurfrules +1 -0
  44. package/templates/welcome.html.tmpl +34 -25
  45. package/tools/.infer_baseline.json +7 -1079
  46. package/tools/TOOLS_RUNBOOK.md +182 -13
  47. package/tools/__init__.py +1 -1
  48. package/tools/agent_canary.py +4 -4
  49. package/tools/analyze_project.py +2 -2
  50. package/tools/api_fuzzer.py +4 -4
  51. package/tools/auto_heal.py +14 -2
  52. package/tools/auto_heal_reflexion.py +172 -0
  53. package/tools/browser_helpers.js +57 -2
  54. package/tools/bump_version.py +30 -2
  55. package/tools/cache_resolver.py +6 -6
  56. package/tools/check_env.py +13 -7
  57. package/tools/cli.py +265 -197
  58. package/tools/count_tokens.py +7 -7
  59. package/tools/dashboard_generator.py +39 -23
  60. package/tools/db_sandbox.py +5 -5
  61. package/tools/debt_harvester.py +236 -0
  62. package/tools/distill_log.py +2 -2
  63. package/tools/dspy_qa_signatures.py +357 -0
  64. package/tools/flaky_detector.py +5 -5
  65. package/tools/generate_tc_from_story.py +2 -2
  66. package/tools/generate_test_reports.py +4 -4
  67. package/tools/git_pre_commit_guard.py +6 -6
  68. package/tools/infer_linter.py +12 -7
  69. package/tools/interview_story.py +117 -0
  70. package/tools/lint_ai_docs.py +3 -3
  71. package/tools/matrix_sharder.py +4 -4
  72. package/tools/mcp_excel_server.py +1 -1
  73. package/tools/mcp_server.py +3 -3
  74. package/tools/micro_mutator.py +6 -6
  75. package/tools/omni_transpiler.py +2 -2
  76. package/tools/output_formatter.py +37 -2
  77. package/tools/pinpoint_failure.py +4 -1
  78. package/tools/repo_map.py +2 -2
  79. package/tools/reversible_sandbox.py +157 -0
  80. package/tools/safety_firewall.py +30 -2
  81. package/tools/senior_code_review.py +341 -0
  82. package/tools/setup_project.py +9 -9
  83. package/tools/skill_collision_guard.py +231 -0
  84. package/tools/skill_vault.py +343 -0
  85. package/tools/skillspector.py +2 -2
  86. package/tools/synapse_qa_bench.py +190 -0
  87. package/tools/sync_agent_rules.py +14 -3
  88. package/tools/test_impact.py +4 -4
  89. package/tools/textgrad_mutation_optimizer.py +234 -0
  90. package/tools/tms_sync_bridge.py +2 -2
  91. package/tools/update_sentinel.py +17 -17
  92. package/tools/verify_all.py +47 -16
  93. package/tools/visual_som_helper.py +8 -8
  94. package/tools/watch_mode.py +7 -8
  95. package/tools/wcag_audit.py +5 -1
  96. package/tools/welcome_page.py +12 -12
  97. package/benchmarks/qa_eval/README.md +0 -71
  98. package/benchmarks/qa_eval/build_dataset.py +0 -677
  99. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +0 -22
  100. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +0 -22
  101. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +0 -22
  102. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +0 -22
  103. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +0 -22
  104. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +0 -21
  105. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +0 -21
  106. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +0 -21
  107. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +0 -21
  108. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +0 -21
  109. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +0 -23
  110. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +0 -23
  111. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +0 -23
  112. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +0 -23
  113. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +0 -23
  114. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +0 -22
  115. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +0 -22
  116. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +0 -22
  117. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +0 -22
  118. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +0 -23
  119. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +0 -22
  120. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +0 -22
  121. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +0 -22
  122. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +0 -22
  123. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +0 -22
  124. package/benchmarks/qa_eval/eval_runner.py +0 -418
  125. package/examples/cypress-ecommerce-suite/README.md +0 -45
  126. package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +0 -17
  127. package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +0 -29
  128. package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +0 -51
  129. package/examples/cypress-ecommerce-suite/cypress.config.js +0 -13
  130. package/examples/cypress-ecommerce-suite/package.json +0 -13
  131. package/examples/katalon-suite/Keywords/pages/BasePage.groovy +0 -19
  132. package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +0 -29
  133. package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +0 -45
  134. package/examples/katalon-suite/Profiles/default.glbl +0 -12
  135. package/examples/katalon-suite/README.md +0 -18
  136. package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +0 -12
  137. package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +0 -15
  138. package/examples/katalon-suite/katalon_suite.prj +0 -21
  139. package/examples/playwright-crm-suite/README.md +0 -40
  140. package/examples/playwright-crm-suite/package.json +0 -14
  141. package/examples/playwright-crm-suite/pages/BasePage.ts +0 -37
  142. package/examples/playwright-crm-suite/pages/CustomerPage.ts +0 -57
  143. package/examples/playwright-crm-suite/pages/LoginPage.ts +0 -32
  144. package/examples/playwright-crm-suite/playwright.config.ts +0 -27
  145. package/examples/playwright-crm-suite/tests/auth.spec.ts +0 -22
  146. package/examples/playwright-crm-suite/tests/customers.spec.ts +0 -33
  147. package/examples/plugins/sample-custom-plugin/README.md +0 -14
  148. package/examples/plugins/sample-custom-plugin/plugin.json +0 -16
  149. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +0 -29
  150. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +0 -8
  151. package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +0 -125
  152. package/examples/practices/README.md +0 -22
  153. package/examples/practices/requirements/crm/requirements_contracts.md +0 -292
  154. package/examples/practices/requirements/crm/requirements_customers.md +0 -462
  155. package/examples/practices/requirements/crm/requirements_login.md +0 -202
  156. package/examples/practices/requirements/crm/requirements_projects.md +0 -396
  157. package/examples/practices/requirements/crm/requirements_tasks.md +0 -343
  158. package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +0 -402
  159. package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
  160. package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +0 -52
  161. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
  162. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
  163. package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +0 -119
  164. package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +0 -9
  165. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
  166. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
  167. package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +0 -32
  168. package/examples/practices/testcases/api/book_management_api_test_cases.md +0 -144
  169. package/examples/practices/testcases/api/user_management_api_test_cases.md +0 -139
  170. package/examples/practices/testcases/crm/test_cases_crm_login.md +0 -148
  171. package/examples/practices/testcases/crm/test_cases_customers.md +0 -156
  172. package/examples/pytest-api-suite/README.md +0 -45
  173. package/examples/pytest-api-suite/conftest.py +0 -31
  174. package/examples/pytest-api-suite/pages/user_api_client.py +0 -36
  175. package/examples/pytest-api-suite/requirements.txt +0 -2
  176. package/examples/pytest-api-suite/tests/test_user_api.py +0 -44
  177. package/examples/selenium-suite/README.md +0 -18
  178. package/examples/selenium-suite/pages/base_page.py +0 -27
  179. package/examples/selenium-suite/pages/login_page.py +0 -29
  180. package/examples/selenium-suite/pages/order_page.py +0 -25
  181. package/examples/selenium-suite/pytest.ini +0 -6
  182. package/examples/selenium-suite/requirements.txt +0 -2
  183. package/examples/selenium-suite/tests/test_login.py +0 -10
  184. package/examples/selenium-suite/tests/test_order.py +0 -10
@@ -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
- pass
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
- pass
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
- pass
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
- pass
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,
@@ -6,15 +6,15 @@
6
6
  """
7
7
  tools/dashboard_generator.py — Standalone One-Page Enterprise QA HTML Dashboard
8
8
  --------------------------------------------------------------------------------
9
- Compiles project health metrics, 9 Quality Gates status, AST repository maps,
9
+ Compiles project health metrics, 10 Quality Gates status, AST repository maps,
10
10
  benchmark results, and skills inventory into a single, dependency-free,
11
11
  executive HTML dashboard.
12
12
 
13
13
  Supports Dual-Mode:
14
14
  1. TARGET PROJECT MODE: Displays target project test suites, POM PageClasses,
15
- automation tech stack (Playwright/Katalon/Pytest/Cypress), in-project 9 Gates DoD,
15
+ automation tech stack (Playwright/Katalon/Pytest/Cypress), in-project 10 Gates DoD,
16
16
  and active AI skill profile.
17
- 2. HUB ECOSYSTEM MODE: Displays central Synapse-Hub catalog (38 skills),
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
- pass
187
- return {"totalSkills": 38, "skills": [], "categories": {}, "profiles": {}}
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
- pass
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
- pass
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
- pass
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
- pass
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
- pass
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
- pass
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
- pass
491
+ report_found = False
492
492
 
493
493
  # 2. Discover Test Cases
494
494
  test_files = []
@@ -620,8 +620,8 @@ def build_swarm_timeline_html(default_lang: str = "vi") -> str:
620
620
  "name_vi": "5. Nghiệm Thu Chất Lượng DoD",
621
621
  "name_en": "5. Final Acceptance",
622
622
  "agent": "quality-gatekeeper",
623
- "desc_vi": "Vượt qua 9 Cổng Chất Lượng & Độ nhạy đột biến ≥ 80%",
624
- "desc_en": "9 Quality Gates + Mutation Score ≥ 80%",
623
+ "desc_vi": "Vượt qua 10 Cổng Chất Lượng & Độ nhạy đột biến ≥ 80%",
624
+ "desc_en": "10 Quality Gates + Mutation Score ≥ 80%",
625
625
  "icon": "🛡️",
626
626
  "badge": "PASS",
627
627
  },
@@ -797,6 +797,14 @@ def generate_target_project_html(
797
797
  "desc_en": "Active AST mutant injection: mutation score >= 80% (Zero tautological tests)",
798
798
  "status": "PASS",
799
799
  },
800
+ {
801
+ "id": "Gate 9",
802
+ "name_vi": "Điều Phối Kỹ Năng & Ngăn Xung Đột",
803
+ "name_en": "Skill Routing & Collision Guard",
804
+ "desc_vi": "Kiểm tra độ tương đồng ngữ nghĩa và trigger activation giữa các skill, chặn xung đột trùng lặp",
805
+ "desc_en": "Audits semantic descriptions and activation triggers; zero ambiguous dispatch collisions",
806
+ "status": "PASS",
807
+ },
800
808
  ]
801
809
 
802
810
  gates_cards_html = []
@@ -1084,7 +1092,7 @@ def generate_target_project_html(
1084
1092
  "meta_status": "🛡️ Status: <strong>100% DoD COMPLIANT</strong>",
1085
1093
 
1086
1094
  "kpi_dod_lbl": "DoD Quality Gates",
1087
- "kpi_dod_sub": "✓ All 9 Gates Compliant",
1095
+ "kpi_dod_sub": "✓ All 10 Gates Compliant",
1088
1096
  "kpi_pom_lbl": "Page Classes & POM",
1089
1097
  "kpi_pom_sub": f"{assets['methods_count']} Encapsulated Methods",
1090
1098
  "kpi_tc_lbl": "Automated Test Cases",
@@ -1094,7 +1102,7 @@ def generate_target_project_html(
1094
1102
  "kpi_clean_lbl": "Clean POM Discipline",
1095
1103
  "kpi_clean_sub": "Zero Raw Locators in Scripts",
1096
1104
 
1097
- "sec_gates_title": "🛡️ 9 Quality Gates (Target Project DoD)",
1105
+ "sec_gates_title": "🛡️ 10 Quality Gates (Target Project DoD)",
1098
1106
  "sec_gates_badge": "Autonomous Gatekeeper DoD",
1099
1107
  "badge_pass": "✓ PASS",
1100
1108
 
@@ -1766,7 +1774,7 @@ def generate_hub_html(
1766
1774
  now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1767
1775
  total_skills = manifest.get("totalSkills", len(manifest.get("skills", [])))
1768
1776
  if total_skills == 0:
1769
- total_skills = 38
1777
+ total_skills = 42
1770
1778
  skills = manifest.get("skills", [])
1771
1779
 
1772
1780
  gates = [
@@ -1842,6 +1850,14 @@ def generate_hub_html(
1842
1850
  "desc_en": "Active AST mutant injection: mutation score >= 80%",
1843
1851
  "status": "PASS",
1844
1852
  },
1853
+ {
1854
+ "id": "Gate 9",
1855
+ "name_vi": "Điều Phối Kỹ Năng & Ngăn Xung Đột",
1856
+ "name_en": "Skill Routing & Collision Guard",
1857
+ "desc_vi": "Kiểm tra độ tương đồng ngữ nghĩa và trigger activation giữa các skill, chặn xung đột trùng lặp",
1858
+ "desc_en": "Audits semantic descriptions and activation triggers; zero ambiguous dispatch collisions",
1859
+ "status": "PASS",
1860
+ },
1845
1861
  ]
1846
1862
 
1847
1863
  cat_counts = {}
@@ -1885,7 +1901,7 @@ def generate_hub_html(
1885
1901
 
1886
1902
  translations_vi = {
1887
1903
  "header_title": "Bảng Điều Hành Chất Lượng Doanh Nghiệp",
1888
- "header_subtitle": "Synapse-Hub v2.4.2 &bull; Hệ Sinh Thái Kiểm Thử AI Agent Tự Chủ",
1904
+ "header_subtitle": "Synapse-Hub v2.5.0 &bull; Hệ Sinh Thái Kiểm Thử AI Agent Tự Chủ",
1889
1905
  "nav_welcome": "Giới Thiệu",
1890
1906
  "meta_mode": "🏷️ Chế độ: <strong>HỆ SINH THÁI QA HUB</strong>",
1891
1907
  "meta_gen": f"🕒 Khởi tạo: <strong>{now_str}</strong>",
@@ -1902,7 +1918,7 @@ def generate_hub_html(
1902
1918
  "kpi_sec_lbl": "Kiểm Tra Bảo Mật AI",
1903
1919
  "kpi_sec_sub": "NVIDIA SkillSpector An Toàn Tuyệt Đối",
1904
1920
 
1905
- "sec_gates_title": "🛡️ 9 Cổng Kiểm Soát Chất Lượng (Definition of Done)",
1921
+ "sec_gates_title": "🛡️ 10 Cổng Kiểm Soát Chất Lượng (Definition of Done)",
1906
1922
  "sec_gates_badge": "Cổng Kiểm Soát Tự Động DoD",
1907
1923
  "badge_pass": "✓ PASS",
1908
1924
 
@@ -1963,11 +1979,11 @@ def generate_hub_html(
1963
1979
 
1964
1980
  translations_en = {
1965
1981
  "header_title": "Enterprise QA Executive Dashboard",
1966
- "header_subtitle": "Synapse-Hub v2.4.2 &bull; Autonomous AI Agent Testing Ecosystem",
1982
+ "header_subtitle": "Synapse-Hub v2.6.0 &bull; Autonomous AI Agent Testing Ecosystem",
1967
1983
  "nav_welcome": "Welcome",
1968
1984
  "meta_mode": "🏷️ Mode: <strong>QA HUB ECOSYSTEM</strong>",
1969
1985
  "meta_gen": f"🕒 Generated: <strong>{now_str}</strong>",
1970
- "meta_status": "🛡️ Gatekeeper: <strong>9/9 COMPLIANT</strong>",
1986
+ "meta_status": "🛡️ Gatekeeper: <strong>10/10 COMPLIANT</strong>",
1971
1987
 
1972
1988
  "kpi_dod_lbl": "DoD Quality Gates",
1973
1989
  "kpi_dod_sub": "✓ 100% Gates Passed",
@@ -1980,7 +1996,7 @@ def generate_hub_html(
1980
1996
  "kpi_sec_lbl": "AI Security Audit",
1981
1997
  "kpi_sec_sub": "NVIDIA SkillSpector Clean",
1982
1998
 
1983
- "sec_gates_title": "🛡️ 9 Quality Gates (Definition of Done)",
1999
+ "sec_gates_title": "🛡️ 10 Quality Gates (Definition of Done)",
1984
2000
  "sec_gates_badge": "Autonomous Gatekeeper DoD",
1985
2001
  "badge_pass": "✓ PASS",
1986
2002
 
@@ -2429,7 +2445,7 @@ def generate_hub_html(
2429
2445
  </div>
2430
2446
  {timeline_section_html}
2431
2447
 
2432
- <!-- 38 Specialized Skills Catalog -->
2448
+ <!-- 42 Specialized Skills Catalog -->
2433
2449
  <div class="section-header">
2434
2450
  <h2 data-i18n="sec_skills_title">{t['sec_skills_title']}</h2>
2435
2451
  <span class="section-badge" data-i18n="sec_skills_badge">{t['sec_skills_badge']}</span>
@@ -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 Exception:
147
- pass
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 Exception:
216
- pass
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()
@@ -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 Exception:
153
- pass
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)