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
@@ -13,7 +13,7 @@
13
13
  (function () {
14
14
  'use strict';
15
15
 
16
- if (window.__qa && window.__qa.__version === '2.4.3') {
16
+ if (window.__qa && window.__qa.__version === '2.5.0') {
17
17
  return window.__qa;
18
18
  }
19
19
 
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  const qa = {
50
- __version: '2.4.3',
50
+ __version: '2.5.0',
51
51
 
52
52
  /**
53
53
  * 1. Smart Action Batching
@@ -682,6 +682,61 @@
682
682
 
683
683
  check();
684
684
  });
685
+ },
686
+
687
+ /**
688
+ * 9. DFSDT Backtracking Locator Resolver (ToolBench / Master Locator Hierarchy Pattern)
689
+ * Executes Depth-First Search across 6 architectural locator tiers when a selector fails or goes stale.
690
+ * Evaluates DOM presence, visibility, and clickability before returning the resilient candidate.
691
+ * @param {Object|string} spec { testId, role, text, formControl, id, css } or broken selector string
692
+ * @returns {Object} { resolved: boolean, tier: number, selector: string, element: HTMLElement|null }
693
+ */
694
+ dfsdtResolve: function (spec) {
695
+ if (typeof document === 'undefined') {
696
+ return { resolved: false, tier: 0, selector: null, element: null };
697
+ }
698
+ const candidates = [];
699
+ if (typeof spec === 'string') {
700
+ const clean = spec.replace(/['"\\\[\]]/g, '').trim();
701
+ candidates.push({ tier: 1, sel: `[data-testid="${clean}"]` });
702
+ candidates.push({ tier: 2, sel: `[role="button"][name="${clean}"], [role="textbox"][name="${clean}"]` });
703
+ candidates.push({ tier: 2, sel: `button:has-text("${clean}"), a:has-text("${clean}")` });
704
+ candidates.push({ tier: 3, sel: `[formcontrolname="${clean}"]` });
705
+ candidates.push({ tier: 3, sel: `[name="${clean}"]` });
706
+ candidates.push({ tier: 4, sel: `#${clean}` });
707
+ } else if (spec && typeof spec === 'object') {
708
+ if (spec.testId) candidates.push({ tier: 1, sel: `[data-testid="${spec.testId}"]` });
709
+ if (spec.role && spec.name) candidates.push({ tier: 1, sel: `[role="${spec.role}"][name="${spec.name}"]` });
710
+ if (spec.text) candidates.push({ tier: 2, sel: `button:has-text("${spec.text}"), a:has-text("${spec.text}"), span:has-text("${spec.text}")` });
711
+ if (spec.formControl) candidates.push({ tier: 3, sel: `[formcontrolname="${spec.formControl}"]` });
712
+ if (spec.name) candidates.push({ tier: 3, sel: `[name="${spec.name}"]` });
713
+ if (spec.id) candidates.push({ tier: 4, sel: `#${spec.id}` });
714
+ if (spec.css) candidates.push({ tier: 5, sel: spec.css });
715
+ }
716
+
717
+ // DFSDT Backtracking evaluation
718
+ for (const cand of candidates) {
719
+ try {
720
+ const els = Array.from(document.querySelectorAll(cand.sel));
721
+ for (const el of els) {
722
+ const rect = el.getBoundingClientRect();
723
+ const isVisible = !!(rect.width > 0 && rect.height > 0 && window.getComputedStyle(el).display !== 'none' && window.getComputedStyle(el).visibility !== 'hidden');
724
+ if (isVisible) {
725
+ return {
726
+ resolved: true,
727
+ tier: cand.tier,
728
+ selector: cand.sel,
729
+ element: el
730
+ };
731
+ }
732
+ }
733
+ } catch (e) {
734
+ // Backtrack to next candidate tier
735
+ continue;
736
+ }
737
+ }
738
+
739
+ return { resolved: false, tier: 0, selector: null, element: null };
685
740
  }
686
741
  };
687
742
 
@@ -142,7 +142,18 @@ def git_commit_and_tag(version: str, push: bool = False):
142
142
 
143
143
  if push:
144
144
  print(f"🚀 Pushing commit and tags to remote origin...")
145
- subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
145
+ res = subprocess.run(["git", "branch", "--show-current"], cwd=REPO_ROOT, stdout=subprocess.PIPE, text=True)
146
+ cur_branch = res.stdout.strip()
147
+ if cur_branch == "develop":
148
+ try:
149
+ subprocess.run(["git", "checkout", "main"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
150
+ subprocess.run(["git", "merge", "develop", "--ff-only"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
151
+ subprocess.run(["git", "checkout", "develop"], cwd=REPO_ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
152
+ subprocess.run(["git", "push", "origin", "develop", "main", "--tags"], cwd=REPO_ROOT, check=True)
153
+ except Exception:
154
+ subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
155
+ else:
156
+ subprocess.run(["git", "push", "origin", "HEAD", "--tags"], cwd=REPO_ROOT, check=True)
146
157
  print(f"✅ Remote origin synchronized.")
147
158
 
148
159
  def main():
@@ -174,7 +185,7 @@ def main():
174
185
  update_json_root_version("plugin.json", new_ver, args.dry_run)
175
186
 
176
187
  # 2. pyproject.toml
177
- update_file("pyproject.toml", r'(version\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
188
+ update_file("pyproject.toml", r'(?m)^(\s*version\s*=\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
178
189
 
179
190
  # 3. skills_manifest.json
180
191
  update_json_root_version("skills_manifest.json", new_ver, args.dry_run)
@@ -222,6 +233,23 @@ def main():
222
233
  update_file("tools/update_sentinel.py", r'(\.get\("version",\s*")[^"]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
223
234
  update_file("tools/update_sentinel.py", r'(return\s*")[0-9]+\.[0-9]+\.[0-9]+(")', rf"\g<1>{new_ver}\g<2>", args.dry_run)
224
235
 
236
+ # 14. CHANGELOG.md
237
+ cl_path = os.path.join(REPO_ROOT, "CHANGELOG.md")
238
+ if os.path.exists(cl_path) and not args.dry_run:
239
+ try:
240
+ with open(cl_path, "r", encoding="utf-8") as f:
241
+ cl_text = f.read()
242
+ if f"## [{new_ver}]" not in cl_text:
243
+ from datetime import date
244
+ today_str = date.today().isoformat()
245
+ new_block = f"---\n\n## [{new_ver}] - {today_str}\n\n### Changed\n- Maintenance & version release v{new_ver}.\n\n"
246
+ cl_text = re.sub(r'(?m)^---\s*\n\s*##\s*\[', new_block + "## [", cl_text, count=1)
247
+ with open(cl_path, "w", encoding="utf-8") as f:
248
+ f.write(cl_text)
249
+ print(f" [UPDATED] CHANGELOG.md (v{new_ver} entry added)")
250
+ except Exception as e:
251
+ print(f" [WARN] CHANGELOG.md update notice: {e}")
252
+
225
253
  if args.dry_run:
226
254
  print("\n[INFO] Dry-run completed. No files modified on disk.")
227
255
  return
@@ -285,8 +285,8 @@ def get_db(db_path=None, timeout=30.0):
285
285
  conn.execute("PRAGMA journal_mode=WAL;")
286
286
  conn.execute("PRAGMA synchronous=NORMAL;")
287
287
  conn.execute("PRAGMA busy_timeout=30000;")
288
- except Exception:
289
- pass
288
+ except sqlite3.Error as e:
289
+ sys.stderr.write(f"Notice: SQLite PRAGMA configuration skipped: {e}\n")
290
290
  return conn
291
291
 
292
292
 
@@ -312,8 +312,8 @@ def init_db(seed=True, target_db=None):
312
312
  """)
313
313
  try:
314
314
  cursor.execute("ALTER TABLE healing_cache ADD COLUMN code_patch TEXT DEFAULT ''")
315
- except Exception:
316
- pass
315
+ except sqlite3.OperationalError:
316
+ column_exists = True
317
317
 
318
318
  # 2. Negative Memory Table (Blacklist of Failed Fix Attempts)
319
319
  cursor.execute("""
@@ -372,8 +372,8 @@ def init_db(seed=True, target_db=None):
372
372
  """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
373
373
  conn.commit()
374
374
  local_conn.close()
375
- except Exception:
376
- pass
375
+ except (sqlite3.Error, OSError) as e:
376
+ sys.stderr.write(f"Notice: Local cache migration skipped: {e}\n")
377
377
 
378
378
  if seed:
379
379
  for s in SEED_SOLUTIONS:
@@ -13,6 +13,7 @@ Autonomously detects Target Project or QA Hub (CLI, CWD, ENV).
13
13
  import os
14
14
  import sys
15
15
  import shutil
16
+ import shlex
16
17
  import platform
17
18
  import subprocess
18
19
  import argparse
@@ -89,8 +90,13 @@ class DiagnosticRunner:
89
90
 
90
91
  def run_command(self, cmd, timeout: float = 15.0):
91
92
  try:
92
- res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=isinstance(cmd, str), timeout=timeout)
93
- return res.returncode == 0, res.stdout.strip()
93
+ if isinstance(cmd, str):
94
+ cmd_args = shlex.split(cmd, posix=(platform.system() != "Windows"))
95
+ else:
96
+ cmd_args = list(cmd)
97
+ res = subprocess.run(cmd_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=False, timeout=timeout)
98
+ out = res.stdout.strip() if res.stdout.strip() else res.stderr.strip()
99
+ return res.returncode == 0, out
94
100
  except Exception as e:
95
101
  return False, str(e)
96
102
 
@@ -225,8 +231,8 @@ class DiagnosticRunner:
225
231
  is_user_home = False
226
232
  try:
227
233
  is_user_home = self.target_dir.resolve() == Path.home().resolve()
228
- except Exception:
229
- pass
234
+ except OSError:
235
+ is_user_home = False
230
236
 
231
237
  has_project_marker = any([
232
238
  (self.target_dir / marker).exists() for marker in [
@@ -303,8 +309,8 @@ class DiagnosticRunner:
303
309
  k, v = line.strip().split("=", 1)
304
310
  if k.strip() not in os.environ:
305
311
  os.environ[k.strip()] = v.strip().strip("'\"")
306
- except Exception:
307
- pass
312
+ except OSError as err:
313
+ sys.stderr.write(f"Notice: .env read failed: {err}\n")
308
314
 
309
315
  base = os.environ.get("BASE_URL", "").rstrip("/")
310
316
  if not base:
@@ -328,7 +334,7 @@ class DiagnosticRunner:
328
334
  ctx.verify_mode = ssl.CERT_NONE
329
335
 
330
336
  req = urllib.request.Request(test_url, headers={'User-Agent': 'Mozilla/5.0'})
331
- with urllib.request.urlopen(req, timeout=8, context=ctx) as resp:
337
+ with urllib.request.urlopen(req, timeout=8, context=ctx) as resp: # nosec B310
332
338
  code = resp.getcode()
333
339
  if code in (200, 301, 302):
334
340
  self.log_check("Network / Server Base URL", "PASS", f"Connection successful to {test_url} (HTTP {code})")