synapse-hub 2.7.0 → 3.0.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 (108) hide show
  1. package/.agents/agents/live-explorer.md +2 -0
  2. package/.agents/agents/pom-crafter.md +2 -0
  3. package/.agents/agents/test-runner.md +3 -0
  4. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  5. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  6. package/.agents/skills/review-spec/SKILL.md +1 -1
  7. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  8. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  9. package/CLAUDE.md +1 -1
  10. package/README.en.md +70 -291
  11. package/README.md +68 -303
  12. package/docs/ARCHITECTURE.md +48 -2
  13. package/docs/CONTRIBUTING.md +1 -1
  14. package/docs/QUALITY_GATES.md +113 -0
  15. package/package.json +1 -1
  16. package/scripts/package_dist.py +2 -1
  17. package/skills_manifest.json +6 -6
  18. package/synapse.config.json +1 -1
  19. package/templates/ai_bridges/CLAUDE.md +1 -1
  20. package/templates/ai_bridges/copilot-instructions.md +2 -2
  21. package/tools/__init__.py +18 -1
  22. package/tools/_openapi_parser.py +1 -1
  23. package/tools/agent_canary.py +42 -30
  24. package/tools/agentless_patcher.py +2 -2
  25. package/tools/analyze_project.py +3 -5
  26. package/tools/api_drift_sentinel.py +4 -4
  27. package/tools/api_fuzzer.py +8 -8
  28. package/tools/auto_heal.py +4 -5
  29. package/tools/auto_heal_reflexion.py +2 -2
  30. package/tools/autogen_qa_broker.py +2 -2
  31. package/tools/browser_helpers.js +2 -2
  32. package/tools/browsergym_eval.py +2 -3
  33. package/tools/bug_reporter.py +11 -13
  34. package/tools/bump_version.py +75 -5
  35. package/tools/cache_resolver.py +78 -83
  36. package/tools/check_env.py +9 -1
  37. package/tools/check_version_sync.py +40 -3
  38. package/tools/ci_healing_hook.py +4 -6
  39. package/tools/cli.py +5 -7
  40. package/tools/config_decoupler.py +2 -3
  41. package/tools/core/__init__.py +52 -0
  42. package/tools/count_tokens.py +22 -20
  43. package/tools/dashboard_generator.py +627 -998
  44. package/tools/db_sandbox.py +65 -53
  45. package/tools/debt_harvester.py +3 -3
  46. package/tools/distill_log.py +3 -3
  47. package/tools/dspy_qa_signatures.py +3 -4
  48. package/tools/export_postman_collection.py +8 -8
  49. package/tools/flaky_detector.py +2 -2
  50. package/tools/fuzz_payloads.py +5 -5
  51. package/tools/generate_allure_report.py +1 -1
  52. package/tools/generate_jmx_plan.py +4 -4
  53. package/tools/gherkin_suite.py +1 -1
  54. package/tools/git_pre_commit_guard.py +2 -2
  55. package/tools/goldfish_review.py +2 -2
  56. package/tools/heal/__init__.py +28 -0
  57. package/tools/heal_pr_automator.py +6 -6
  58. package/tools/heuristic_locator_matcher.py +2 -2
  59. package/tools/infer_linter.py +8 -8
  60. package/tools/interview_story.py +0 -1
  61. package/tools/jtl_analyzer.py +11 -11
  62. package/tools/lint/__init__.py +30 -0
  63. package/tools/lint_ai_docs.py +42 -0
  64. package/tools/lookup_tc.py +1 -1
  65. package/tools/matrix_sharder.py +15 -13
  66. package/tools/mcp/__init__.py +16 -0
  67. package/tools/mcp_excel_server.py +2 -2
  68. package/tools/mcp_server.py +3 -4
  69. package/tools/md_to_xlsx.py +26 -26
  70. package/tools/micro_mutator.py +11 -12
  71. package/tools/network_mock_shield.py +27 -27
  72. package/tools/newman_analyzer.py +2 -3
  73. package/tools/omni_emitter.py +5 -8
  74. package/tools/omni_parser.py +5 -5
  75. package/tools/omni_transpiler.py +5 -5
  76. package/tools/output_formatter.py +19 -0
  77. package/tools/perf/__init__.py +24 -0
  78. package/tools/pinpoint_failure.py +1 -1
  79. package/tools/project_state.py +0 -2
  80. package/tools/qa_event_stream.py +1 -1
  81. package/tools/repo_map.py +8 -8
  82. package/tools/report/__init__.py +32 -0
  83. package/tools/reversible_sandbox.py +1 -2
  84. package/tools/safety_firewall.py +2 -2
  85. package/tools/senior_code_review.py +2 -2
  86. package/tools/setup_project.py +10 -12
  87. package/tools/skill_collision_guard.py +3 -3
  88. package/tools/skill_vault.py +12 -3
  89. package/tools/skillspector.py +5 -7
  90. package/tools/smart_visual_masker.py +4 -6
  91. package/tools/spec_drift_sentinel.py +1 -2
  92. package/tools/swe_qa_aci.py +2 -2
  93. package/tools/synapse_qa_bench.py +3 -3
  94. package/tools/sync_agent_rules.py +17 -3
  95. package/tools/telemetry_correlator.py +3 -2
  96. package/tools/test_memory_os.py +15 -5
  97. package/tools/textgrad_mutation_optimizer.py +3 -3
  98. package/tools/tms_sync_bridge.py +5 -5
  99. package/tools/tot_exploratory_tester.py +5 -6
  100. package/tools/traffic_session_converter.py +9 -9
  101. package/tools/universal_ir.py +2 -3
  102. package/tools/update_sentinel.py +6 -9
  103. package/tools/verify_all.py +10 -0
  104. package/tools/visual_diff_canvas.py +1 -2
  105. package/tools/visual_som_helper.py +15 -13
  106. package/tools/watch_mode.py +1 -1
  107. package/tools/wcag_audit.py +22 -1
  108. package/tools/welcome_page.py +5 -6
@@ -19,7 +19,7 @@ import os
19
19
  import sys
20
20
  import json
21
21
  import argparse
22
- from typing import List, Dict, Any, Tuple
22
+ from typing import List, Dict, Any, Optional
23
23
 
24
24
  # Enforce UTF-8 stdout encoding for Windows console compatibility
25
25
  if hasattr(sys.stdout, 'reconfigure'):
@@ -96,7 +96,7 @@ BENCHMARK_CASES = [
96
96
  class SynapseQABench:
97
97
  """Evaluates agent tool-calling capabilities against standardized QA benchmark cases."""
98
98
 
99
- def __init__(self, cases: List[Dict[str, Any]] = None):
99
+ def __init__(self, cases: Optional[List[Dict[str, Any]]] = None):
100
100
  self.cases = cases or BENCHMARK_CASES
101
101
 
102
102
  def run_benchmark(self, tool_resolver_fn=None) -> Dict[str, Any]:
@@ -112,7 +112,7 @@ class SynapseQABench:
112
112
  details = []
113
113
 
114
114
  for case in self.cases:
115
- expected_tool = case["expected_tool"]
115
+ expected_tool = str(case["expected_tool"])
116
116
  # Baseline keyword resolver simulation
117
117
  selected_tool = expected_tool if tool_resolver_fn is None else tool_resolver_fn(case["intent"])
118
118
 
@@ -47,9 +47,10 @@ HEADER_HASH_TEMPLATE = """# ====================================================
47
47
 
48
48
 
49
49
  def generate_claude_md():
50
+ """Generates the unified CLAUDE.md file compiling agent rules and standards."""
50
51
  return f"""# CLAUDE.md — Synapse-Hub Guidelines for Claude Code
51
52
 
52
- > **Author & Creator:** Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
53
+ > **Author & Creator:** Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
53
54
  > **License:** MIT License (Mandatory Attribution Preservation)
54
55
 
55
56
  {HEADER_TEMPLATE}
@@ -100,6 +101,7 @@ In accordance with the MIT License, the original copyright notice crediting **Th
100
101
 
101
102
 
102
103
  def generate_cursorrules():
104
+ """Generates the .cursorrules file compiling agent rules for Cursor AI."""
103
105
  return f"""# .cursorrules — Synapse-Hub Guidelines for Cursor AI
104
106
  # Author & Creator: Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
105
107
  # License: MIT License (Mandatory Attribution Preservation)
@@ -127,6 +129,7 @@ def generate_cursorrules():
127
129
 
128
130
 
129
131
  def generate_windsurfrules():
132
+ """Generates the .windsurfrules configuration for Windsurf AI."""
130
133
  return f"""# .windsurfrules — Synapse-Hub Guidelines for Windsurf AI
131
134
  # Author & Creator: Thế Vị (@TeeVee998) <https://github.com/TeeVee998/Synapse-Hub>
132
135
  # License: MIT License (Mandatory Attribution Preservation)
@@ -153,6 +156,7 @@ def generate_windsurfrules():
153
156
 
154
157
 
155
158
  def generate_cursor_mdc():
159
+ """Generates the .cursor/rules/synapse-qa.mdc file for Cursor IDE."""
156
160
  return f"""---
157
161
  description: Synapse-Hub Enterprise QA Automation & Testing Rules
158
162
  globs: **/*
@@ -177,12 +181,13 @@ Attribution notices must be preserved.
177
181
 
178
182
 
179
183
  def generate_copilot_instructions():
184
+ """Generates the .github/copilot-instructions.md file for GitHub Copilot."""
180
185
  return f"""# GitHub Copilot Custom Instructions for Synapse-Hub
181
186
 
182
187
  {HEADER_TEMPLATE}
183
188
 
184
- > **Project:** Synapse-Hub Enterprise QA Automation
185
- > **Author:** Thế Vị (@TeeVee998) — MIT License
189
+ > **Project:** Synapse-Hub Enterprise QA Automation
190
+ > **Author:** Thế Vị (@TeeVee998) — MIT License
186
191
 
187
192
  ## Coding Invariants
188
193
  - Enforce Clean Page Object Model (POM) with Flat Architecture.
@@ -210,6 +215,14 @@ TARGET_GENERATORS = [
210
215
 
211
216
 
212
217
  def sync_all(check_only=False):
218
+ """Synchronizes AGENTS.md rules across all IDE and assistant bridge files.
219
+
220
+ Args:
221
+ check_only: If True, only checks for rule drift without modifying files.
222
+
223
+ Returns:
224
+ 0 if clean or synchronized, 1 if drift was detected in check_only mode.
225
+ """
213
226
  drift_detected = False
214
227
  for rel_path, gen_fn in TARGET_GENERATORS:
215
228
  full_path = os.path.join(REPO_ROOT, rel_path)
@@ -249,6 +262,7 @@ def sync_all(check_only=False):
249
262
 
250
263
 
251
264
  def main():
265
+ """CLI entrypoint for synchronizing AI agent rules."""
252
266
  parser = argparse.ArgumentParser(description="Synchronize AGENTS.md to all AI IDE bridges")
253
267
  parser.add_argument("--check", action="store_true", help="Check for drift without modifying files")
254
268
  args = parser.parse_args()
@@ -36,7 +36,7 @@ import csv
36
36
  import json
37
37
  import math
38
38
  import argparse
39
- from typing import Dict, List, Any, Optional, Tuple
39
+ from typing import Dict, List, Any
40
40
 
41
41
  # Enforce UTF-8 stdout encoding for Windows/macOS/Linux console compatibility
42
42
  if hasattr(sys.stdout, "reconfigure"):
@@ -286,7 +286,8 @@ def main():
286
286
  args = parser.parse_args()
287
287
 
288
288
  if args.sample or not args.jtl or not args.server_metrics:
289
- import tempfile, time
289
+ import tempfile
290
+ import time
290
291
  temp_dir = tempfile.gettempdir()
291
292
  temp_jtl = os.path.join(temp_dir, "sample_corr_jmeter.jtl")
292
293
  temp_srv = os.path.join(temp_dir, "sample_corr_server.csv")
@@ -27,6 +27,7 @@ import re
27
27
  import json
28
28
  import sqlite3
29
29
  import time
30
+ import uuid
30
31
  import difflib
31
32
  import argparse
32
33
  from datetime import datetime, timezone, timedelta
@@ -48,6 +49,15 @@ SECRET_PATTERNS = [
48
49
  ]
49
50
 
50
51
 
52
+ class ManagedConnection(sqlite3.Connection):
53
+ """SQLite connection that automatically commits/rollbacks and closes on context exit."""
54
+ def __exit__(self, exc_type, exc_val, exc_tb):
55
+ try:
56
+ super().__exit__(exc_type, exc_val, exc_tb)
57
+ finally:
58
+ self.close()
59
+
60
+
51
61
  class TestMemoryOS:
52
62
  """3-Tier Hierarchical QA Memory OS."""
53
63
  __test__ = False
@@ -62,8 +72,8 @@ class TestMemoryOS:
62
72
  os.makedirs(os.path.dirname(os.path.abspath(self.db_path)), exist_ok=True)
63
73
  self._init_db()
64
74
 
65
- def _get_connection(self) -> sqlite3.Connection:
66
- conn = sqlite3.connect(self.db_path)
75
+ def _get_connection(self) -> ManagedConnection:
76
+ conn = sqlite3.connect(self.db_path, factory=ManagedConnection)
67
77
  conn.row_factory = sqlite3.Row
68
78
  return conn
69
79
 
@@ -165,7 +175,7 @@ class TestMemoryOS:
165
175
  INSERT INTO recall_memory (session_id, event_type, actor, payload_json, timestamp)
166
176
  VALUES (?, ?, ?, ?, ?)
167
177
  """, (session_id, event_type, actor, payload_str, now))
168
- return cursor.lastrowid
178
+ return cursor.lastrowid or 0
169
179
 
170
180
  def recall_log(self, session_id: str, limit: int = 50) -> List[Dict[str, Any]]:
171
181
  """Retrieves recent sequential steps for a session."""
@@ -199,7 +209,7 @@ class TestMemoryOS:
199
209
  """Inserts long-term searchable knowledge into archival memory."""
200
210
  payload_str = json.dumps(payload) if not isinstance(payload, str) else payload
201
211
  now = datetime.now(timezone.utc).isoformat()
202
- mem_id = f"mem_arch_{int(time.time() * 1000) % 10000000}"
212
+ mem_id = f"mem_arch_{int(time.time() * 1000) % 10000000}_{uuid.uuid4().hex[:6]}"
203
213
 
204
214
  # Flatten text content for semantic/fuzzy search
205
215
  text_content = f"{key} {aut_version} {tags} {payload_str}".lower()
@@ -336,7 +346,7 @@ def main():
336
346
  subparsers = parser.add_subparsers(dest="subcommand", help="Subcommand to execute")
337
347
 
338
348
  # init
339
- init_parser = subparsers.add_parser("init", help="Initialize memory OS database")
349
+ subparsers.add_parser("init", help="Initialize memory OS database")
340
350
 
341
351
  # update-core
342
352
  uc_parser = subparsers.add_parser("update-core", help="Update core memory block")
@@ -20,7 +20,7 @@ import sys
20
20
  import re
21
21
  import json
22
22
  import argparse
23
- from typing import List, Dict, Any, Tuple, Optional
23
+ from typing import List, Dict, Any, Tuple
24
24
 
25
25
  # Enforce UTF-8 stdout encoding for Windows console compatibility
26
26
  if hasattr(sys.stdout, 'reconfigure'):
@@ -33,10 +33,10 @@ if hasattr(sys.stdout, 'reconfigure'):
33
33
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
34
34
  sys.path.insert(0, SCRIPT_DIR)
35
35
 
36
- import micro_mutator
36
+ import micro_mutator # noqa: E402
37
37
 
38
38
  try:
39
- import textgrad
39
+ import textgrad # noqa: F401, E402
40
40
  TEXTGRAD_AVAILABLE = True
41
41
  except ImportError:
42
42
  TEXTGRAD_AVAILABLE = False
@@ -78,7 +78,7 @@ class TMSBridge:
78
78
  "issue_type": "Story",
79
79
  "status": "Ready for QA",
80
80
  "assignee": self.user_email,
81
- "description": f"As an Enterprise Accountant, I want to reconcile invoices matching ERP records to ensure financial accuracy.",
81
+ "description": "As an Enterprise Accountant, I want to reconcile invoices matching ERP records to ensure financial accuracy.",
82
82
  "acceptance_criteria": [
83
83
  "Given the accountant is logged in with FINANCE_ROLE",
84
84
  "When navigating to '/billing/invoices' and filtering by date '2026-10-01'",
@@ -161,8 +161,8 @@ class TMSBridge:
161
161
  bug_key = f"{proj_clean}-BUG-{int(datetime.now().timestamp()) % 10000}"
162
162
 
163
163
  repro = reproduction_steps or [
164
- f"1. Launch headless browser session via Playwright Live.",
165
- f"2. Navigate to target application flow under test.",
164
+ "1. Launch headless browser session via Playwright Live.",
165
+ "2. Navigate to target application flow under test.",
166
166
  f"3. Execute test action sequence for '{test_case_id}'.",
167
167
  f"4. Observe failure: {error_message}"
168
168
  ]
@@ -299,9 +299,9 @@ def main():
299
299
  if args.json:
300
300
  print(json.dumps(story, indent=2))
301
301
  else:
302
- print(f"\n======================================================================")
302
+ print("\n======================================================================")
303
303
  print(f" ENTERPRISE ALM STORY PULL: [{story.get('issue_key')}]")
304
- print(f"======================================================================")
304
+ print("======================================================================")
305
305
  print(f" Summary : {story.get('summary')}")
306
306
  print(f" Status : {story.get('status')} | Assignee: {story.get('assignee')}")
307
307
  print(f" Source : {story.get('source')}\n")
@@ -21,9 +21,8 @@ import os
21
21
  import sys
22
22
  import re
23
23
  import json
24
- import time
25
24
  import argparse
26
- from typing import Dict, Any, List, Optional, Tuple
25
+ from typing import Dict, Any, List, Optional, cast
27
26
 
28
27
  # Enforce UTF-8 stdout encoding
29
28
  if hasattr(sys.stdout, 'reconfigure'):
@@ -43,7 +42,7 @@ class TotExploratoryTester:
43
42
  def expand_story(self, story_content: str, max_depth: int = 3, beam_width: int = 3) -> Dict[str, Any]:
44
43
  """Decomposes story into a 3-branch Tree of Thoughts with risk scores."""
45
44
  # Extract title and criteria
46
- lines = [l.strip() for l in story_content.splitlines() if l.strip()]
45
+ lines = [line.strip() for line in story_content.splitlines() if line.strip()]
47
46
  title = lines[0] if lines else "Untitled User Story"
48
47
  if title.startswith("#"):
49
48
  title = title.lstrip("#").strip()
@@ -93,7 +92,7 @@ class TotExploratoryTester:
93
92
  ]
94
93
 
95
94
  # Beam search selection: sort by risk score descending and take beam_width
96
- sorted_branches = sorted(branches, key=lambda b: b["risk_score"], reverse=True)
95
+ sorted_branches = sorted(branches, key=lambda b: cast(float, b["risk_score"]), reverse=True)
97
96
  selected_branches = sorted_branches[:beam_width]
98
97
 
99
98
  return {
@@ -169,7 +168,7 @@ class TotExploratoryTester:
169
168
  "evidence": evidence
170
169
  })
171
170
 
172
- scored_hypotheses.sort(key=lambda h: h["confidence_score"], reverse=True)
171
+ scored_hypotheses.sort(key=lambda h: cast(float, h["confidence_score"]), reverse=True)
173
172
  winner = scored_hypotheses[0]
174
173
 
175
174
  recommendations = {
@@ -184,7 +183,7 @@ class TotExploratoryTester:
184
183
  "status": "PASS",
185
184
  "primary_root_cause": winner["hypothesis"],
186
185
  "confidence": winner["confidence_score"],
187
- "recommended_fix": recommendations.get(winner["hypothesis"], "Review stacktrace with team"),
186
+ "recommended_fix": recommendations.get(str(winner["hypothesis"]), "Review stacktrace with team"),
188
187
  "ranked_hypotheses": scored_hypotheses
189
188
  }
190
189
 
@@ -23,7 +23,7 @@ import re
23
23
  import sys
24
24
  import json
25
25
  import argparse
26
- from typing import Dict, Any, List, Optional
26
+ from typing import Dict, Any, List
27
27
  from urllib.parse import urlparse
28
28
 
29
29
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -153,14 +153,14 @@ class TrafficConverter:
153
153
  lines = [
154
154
  "import { test, expect } from '@playwright/test';",
155
155
  "",
156
- f"/**",
157
- f" * Automated Replay of Production Traffic Session",
158
- f" * Generated by traffic_session_converter.py (agent-skills Shift-Right Engine)",
156
+ "/**",
157
+ " * Automated Replay of Production Traffic Session",
158
+ " * Generated by traffic_session_converter.py (agent-skills Shift-Right Engine)",
159
159
  f" * Total Actions: {len(requests)} verified network interactions",
160
- f" */",
160
+ " */",
161
161
  f"test.describe('{test_name}', () => {{",
162
- f" test('replays production user journey with PII masking', async ({{ page, request }}) => {{",
163
- f" // 1. Base navigation",
162
+ " test('replays production user journey with PII masking', async ({ page, request }) => {",
163
+ " // 1. Base navigation",
164
164
  f" await page.goto('{base_url}');",
165
165
  ""
166
166
  ]
@@ -185,9 +185,9 @@ class TrafficConverter:
185
185
  formatted_body = json.dumps(body_str)
186
186
 
187
187
  lines.append(f" const resp{step_num} = await request.{method.lower()}('{url}', {{")
188
- lines.append(f" headers: {{ 'Content-Type': 'application/json' }},")
188
+ lines.append(" headers: { 'Content-Type': 'application/json' },")
189
189
  lines.append(f" data: {formatted_body}")
190
- lines.append(f" }});")
190
+ lines.append(" });")
191
191
  lines.append(f" expect(resp{step_num}.status()).toBe({status});")
192
192
  else:
193
193
  lines.append(f" const resp{step_num} = await request.get('{url}');")
@@ -9,8 +9,9 @@ whether the target is Playwright, Selenium, Cypress, Katalon, or Appium.
9
9
 
10
10
  from enum import Enum
11
11
  from typing import Dict, List, Optional, Any
12
- from dataclasses import dataclass, field, asdict
12
+ from dataclasses import dataclass, field
13
13
  import json
14
+ import re
14
15
 
15
16
 
16
17
  class ActionType(str, Enum):
@@ -345,8 +346,6 @@ class ProjectIR:
345
346
  return cls.from_dict(data)
346
347
 
347
348
 
348
- import re
349
-
350
349
  SHARED_VAR_PATTERNS = [
351
350
  (r"\b(?:created_id|order_id|user_id|item_id|customer_id|invoice_id)\b", DataDependencyType.ID),
352
351
  (r"\b(?:auth_token|bearer_token|access_token|jwt_token|token)\b", DataDependencyType.AUTH_TOKEN),
@@ -65,7 +65,7 @@ def get_local_version():
65
65
  try:
66
66
  with open(pkg_file, "r", encoding="utf-8") as f:
67
67
  data = json.load(f)
68
- return data.get("version", "2.7.0")
68
+ return data.get("version", "3.0.0")
69
69
  except (OSError, json.JSONDecodeError):
70
70
  data = {}
71
71
 
@@ -74,11 +74,11 @@ def get_local_version():
74
74
  try:
75
75
  with open(manifest_file, "r", encoding="utf-8") as f:
76
76
  data = json.load(f)
77
- return data.get("version", "2.7.0")
77
+ return data.get("version", "3.0.0")
78
78
  except (OSError, json.JSONDecodeError):
79
79
  data = {}
80
80
 
81
- return "2.7.0"
81
+ return "3.0.0"
82
82
 
83
83
 
84
84
  def fetch_remote_version():
@@ -259,7 +259,6 @@ def perform_update():
259
259
  git_dir = os.path.join(HUB_ROOT, ".git")
260
260
  is_git_repo = os.path.exists(git_dir)
261
261
 
262
- updated = False
263
262
 
264
263
  if is_git_repo:
265
264
  print(f"\n{BOLD}[1/3] Pulling latest updates via Git...{RESET}")
@@ -281,7 +280,6 @@ def perform_update():
281
280
  print(f" {RED}⚠️ Provenance Warning: {msg}{RESET}")
282
281
  else:
283
282
  print(f" {GREEN}[OK] {msg}{RESET}")
284
- updated = True
285
283
  # If this is the first pull, show the welcome landing page
286
284
  try:
287
285
  import welcome_page
@@ -306,9 +304,8 @@ def perform_update():
306
304
  )
307
305
  if res.returncode == 0:
308
306
  print(f" {GREEN}[OK] Global NPM package updated to latest!{RESET}")
309
- updated = True
310
307
  except (subprocess.SubprocessError, OSError):
311
- updated = False
308
+ _ = False
312
309
 
313
310
  # 2. Check Global Plugin
314
311
  if os.path.exists(GLOBAL_PLUGIN_DIR) and GLOBAL_PLUGIN_DIR.lower() != HUB_ROOT.lower():
@@ -319,7 +316,7 @@ def perform_update():
319
316
  subprocess.run(["git", "pull", "--ff-only"], cwd=GLOBAL_PLUGIN_DIR, stdout=subprocess.DEVNULL, timeout=30.0)
320
317
  print(f" {GREEN}[OK] Updated Global Plugin repository: {GLOBAL_PLUGIN_DIR}{RESET}")
321
318
  except (subprocess.SubprocessError, OSError):
322
- plugin_synced = False
319
+ _ = False
323
320
  else:
324
321
  # Refresh metadata files in global plugin
325
322
  try:
@@ -331,7 +328,7 @@ def perform_update():
331
328
  shutil.copy2(src_f, dst_f)
332
329
  print(f" {GREEN}[OK] Refreshed metadata & LICENSE in: {GLOBAL_PLUGIN_DIR}{RESET}")
333
330
  except OSError:
334
- copy_done = False
331
+ _ = False
335
332
 
336
333
  # 3. Re-initialize SQLite Memory Cache
337
334
  print(f"\n{BOLD}[3/3] Synchronizing Central Agent Memory Cache...{RESET}")
@@ -136,6 +136,7 @@ def resolve_target_repo(cli_target=None, cli_repo=None):
136
136
 
137
137
 
138
138
  def gate_0_author_integrity(repo_root, mode="AUTO"):
139
+ """Gate 0: Verifies author attribution and MIT license provenance across source manifests."""
139
140
  print("\n[GATE 0] Verifying Author Attribution & MIT License Integrity...")
140
141
  try:
141
142
  from author_integrity import (
@@ -194,6 +195,7 @@ MOJIBAKE_INDICATORS = (
194
195
 
195
196
 
196
197
  def gate_1_bom_check(repo_root, mode="AUTO"):
198
+ """Gate 1: Verifies 100% UTF-8 encoding with zero Byte Order Mark (BOM) bytes."""
197
199
  print("\n[GATE 1] Scanning UTF-8 No BOM and Encoding Integrity across repository...")
198
200
  ignored_dirs = {".git", ".gradle", "node_modules", "allure-results", "graphify-out", "__pycache__", "build", "dist", "claude-ecosystem-repos", "external_repos", "teamwork"}
199
201
  ignored_files = {"verify_all.py", "test_tools.py"}
@@ -241,6 +243,7 @@ def gate_1_bom_check(repo_root, mode="AUTO"):
241
243
 
242
244
 
243
245
  def gate_2_path_portability(repo_root, mode="AUTO"):
246
+ """Gate 2: Scans for and rejects machine-specific absolute filepaths in code."""
244
247
  print("\n[GATE 2] Checking Path Portability (Zero absolute machine paths in repo sources)...")
245
248
  ignored_dirs = {
246
249
  ".git", ".gradle", "node_modules", "allure-results", "Screenshots",
@@ -286,6 +289,7 @@ def gate_2_path_portability(repo_root, mode="AUTO"):
286
289
 
287
290
 
288
291
  def gate_3_clean_pom(repo_root, mode="AUTO"):
292
+ """Gate 3: Enforces Clean Page Object Model by rejecting raw selectors in test scripts."""
289
293
  print("\n[GATE 3] Checking Clean POM & Flat Architecture in Test Scripts...")
290
294
  effective_mode = detect_repo_mode(repo_root) if mode == "AUTO" else mode
291
295
  if effective_mode == "HUB":
@@ -346,6 +350,7 @@ def gate_3_clean_pom(repo_root, mode="AUTO"):
346
350
 
347
351
 
348
352
  def gate_4_ai_docs_lint(repo_root, mode="AUTO"):
353
+ """Gate 4: Reconciles PROJECT_AI_GUIDE.md against physical AST to prevent hallucinated classes."""
349
354
  print("\n[GATE 4] Running AI Documentation & Artifact Linter...")
350
355
  linter_script = os.path.join(SCRIPT_DIR, "lint_ai_docs.py")
351
356
  if not os.path.exists(linter_script):
@@ -368,6 +373,7 @@ def gate_4_ai_docs_lint(repo_root, mode="AUTO"):
368
373
 
369
374
 
370
375
  def gate_5_cleanup_blacklist(repo_root, mode="AUTO"):
376
+ """Gate 5: Scans for prohibited temporary files, heap dumps, and local cache artifacts."""
371
377
  print("\n[GATE 5] Checking Temporary File Blacklist...")
372
378
  blacklist_patterns = [
373
379
  os.path.join(repo_root, "Reports", "batch*.properties"),
@@ -392,6 +398,7 @@ def gate_5_cleanup_blacklist(repo_root, mode="AUTO"):
392
398
 
393
399
 
394
400
  def gate_6_credential_security(repo_root, mode="AUTO"):
401
+ """Gate 6: Scans for plaintext passwords, API tokens, and private keys across codebase."""
395
402
  print("\n[GATE 6] Checking Credential Security across repository...")
396
403
  violations = []
397
404
 
@@ -658,6 +665,7 @@ def check_python_ast_integrity(repo_root):
658
665
 
659
666
 
660
667
  def gate_7_infer_linter(repo_root, mode="AUTO"):
668
+ """Gate 7: Runs Meta Infer-style static defect sentinel catching fake passes, 0 assert, and sleeps."""
661
669
  print("\n[GATE 7] Running Meta Infer-Style Static Defect & Fake-Pass Sentinel...")
662
670
  effective_mode = detect_repo_mode(repo_root) if mode == "AUTO" else mode
663
671
 
@@ -737,6 +745,7 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
737
745
 
738
746
 
739
747
  def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
748
+ """Gate 8: Runs syntactic mutation testing and verifies assertion sensitivity score >= 80%."""
740
749
  eval_mode_desc = f"DYNAMIC EXECUTION MODE ({test_cmd})" if test_cmd else "HEURISTIC STATIC MODE"
741
750
  print(f"\n[GATE 8] Running Assertion Sensitivity & Syntactic Mutation Sentinel [{eval_mode_desc}] (Min Score >= 80%)...")
742
751
  scripts_dir = os.path.join(repo_root, "Scripts")
@@ -797,6 +806,7 @@ def gate_8_micro_mutator(repo_root, mode="AUTO", test_cmd=""):
797
806
 
798
807
 
799
808
  def gate_9_skill_collision(repo_root, mode="AUTO"):
809
+ """Gate 9: Scans skill descriptions to ensure semantic trigger overlap remains < 0.75."""
800
810
  print("\n[GATE 9] Running Tier-2 Skill Routing & Collision Guard (Addy Osmani Pattern)...")
801
811
  guard_script = os.path.join(SCRIPT_DIR, "skill_collision_guard.py")
802
812
  if not os.path.exists(guard_script):
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- from __future__ import annotations
4
3
  """
5
4
  Enterprise Canvas Visual Diff & Regression Checker
6
5
  ===================================================
@@ -17,11 +16,11 @@ Usage:
17
16
  python3 tools/visual_diff_canvas.py compare baseline.png current.png --diff diff.png --threshold 0.02
18
17
  python3 tools/visual_diff_canvas.py check-blank current.png
19
18
  """
19
+ from __future__ import annotations
20
20
 
21
21
  import os
22
22
  import sys
23
23
  import argparse
24
- from pathlib import Path
25
24
 
26
25
  try:
27
26
  from PIL import Image, ImageChops, ImageStat
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- from __future__ import annotations
3
2
  """
4
3
  Enterprise Vision-Grounded Fallback & Set-of-Marks (SoM) Engine
5
4
  --------------------------------------------------------------
@@ -20,27 +19,25 @@ Features:
20
19
  4. Compact Vision Prompt Formatter:
21
20
  - Formats token-efficient briefs for Multimodal Vision Models (Gemini / Claude / GPT-4o).
22
21
  """
22
+ from __future__ import annotations
23
23
 
24
24
  import os
25
25
  import sys
26
26
  import json
27
27
  import sqlite3
28
28
  import argparse
29
- from typing import Dict, Any, List, Optional, Tuple, Union
29
+ from typing import Dict, Any, List, Optional, Tuple
30
30
  try:
31
31
  from PIL import Image, ImageDraw, ImageFont, ImageFilter
32
32
  HAS_PIL = True
33
33
  except ImportError:
34
- Image = None
35
- ImageDraw = None
36
- ImageFont = None
37
- ImageFilter = None
34
+ Image = None # type: ignore[assignment]
35
+ ImageDraw = None # type: ignore[assignment]
36
+ ImageFont = None # type: ignore[assignment]
37
+ ImageFilter = None # type: ignore[assignment]
38
38
  HAS_PIL = False
39
39
 
40
- if HAS_PIL and Image is not None:
41
- ImageSourceType = Union[Image.Image, str]
42
- else:
43
- ImageSourceType = Any
40
+ ImageSourceType = Any
44
41
 
45
42
  # Enforce UTF-8 stdout encoding for Windows console compatibility
46
43
  if hasattr(sys.stdout, 'reconfigure'):
@@ -74,12 +71,12 @@ def compute_visual_hash(image_source: ImageSourceType, bbox: Optional[List[int]]
74
71
  if isinstance(image_source, str):
75
72
  if not os.path.exists(image_source):
76
73
  raise FileNotFoundError(f"Image not found: {image_source}")
77
- img = Image.open(image_source)
74
+ img: Any = Image.open(image_source)
78
75
  else:
79
76
  img = image_source
80
77
 
81
78
  if bbox and len(bbox) == 4:
82
- img = img.crop(bbox)
79
+ img = img.crop(tuple(bbox))
83
80
 
84
81
  # Resize to (hash_size + 1, hash_size) in grayscale
85
82
  resized = img.convert("L").resize((hash_size + 1, hash_size), Image.Resampling.LANCZOS)
@@ -266,6 +263,8 @@ class VisualSoMHelper:
266
263
  edges = img.filter(ImageFilter.FIND_EDGES)
267
264
  edges = edges.point(lambda p: 255 if p > edge_threshold else 0)
268
265
  pixels = edges.load()
266
+ if pixels is None:
267
+ return []
269
268
 
270
269
  # Identify active cells with high edge density
271
270
  active_cells = set()
@@ -274,7 +273,10 @@ class VisualSoMHelper:
274
273
  count = 0
275
274
  for cy in range(y, min(h, y + stride)):
276
275
  for cx in range(x, min(w, x + stride)):
277
- if pixels[cx, cy] > 0:
276
+ px_val = pixels[cx, cy]
277
+ if isinstance(px_val, (int, float)) and px_val > 0:
278
+ count += 1
279
+ elif isinstance(px_val, tuple) and px_val and px_val[0] > 0:
278
280
  count += 1
279
281
  if count >= min_pixels_per_cell:
280
282
  active_cells.add((x // stride, y // stride))
@@ -156,7 +156,7 @@ def run_incremental_audit(file_path: str, target_dir: str) -> Dict[str, Any]:
156
156
  """Performs fast sub-100ms audit on a single changed file."""
157
157
  start_t = time.perf_counter()
158
158
  rel_path = os.path.relpath(file_path, target_dir)
159
- results = {
159
+ results: Dict[str, Any] = {
160
160
  "file": rel_path,
161
161
  "bom_pass": True,
162
162
  "path_pass": True,
@@ -126,6 +126,18 @@ class WCAGAuditor(HTMLParser):
126
126
 
127
127
 
128
128
  def run_audit(target, is_url=False):
129
+ """Executes a WCAG 2.2 accessibility audit against an HTML file or live web URL.
130
+
131
+ Args:
132
+ target: Filepath or URL pointing to HTML content.
133
+ is_url: True if target should be fetched over HTTP/HTTPS.
134
+
135
+ Returns:
136
+ The populated WCAGAuditor instance containing violations and stats.
137
+
138
+ Raises:
139
+ ValueError: If an insecure or invalid URL scheme is passed.
140
+ """
129
141
  auditor = WCAGAuditor()
130
142
  if is_url:
131
143
  parsed = urllib.parse.urlparse(target)
@@ -143,6 +155,15 @@ def run_audit(target, is_url=False):
143
155
 
144
156
 
145
157
  def format_markdown_report(target, auditor):
158
+ """Generates an executive WCAG 2.2 audit markdown report.
159
+
160
+ Args:
161
+ target: Target URL or file audited.
162
+ auditor: Populated WCAGAuditor instance.
163
+
164
+ Returns:
165
+ Markdown string containing compliance score, grade, and violation details.
166
+ """
146
167
  violations = auditor.violations
147
168
  stats = {
148
169
  "elements_scanned": auditor.elements_scanned,
@@ -158,7 +179,7 @@ def format_markdown_report(target, auditor):
158
179
  grade = "PASS" if score >= 80 else "NEEDS REMEDIATION"
159
180
 
160
181
  out = [
161
- f"# WCAG 2.2 Accessibility Audit Report",
182
+ "# WCAG 2.2 Accessibility Audit Report",
162
183
  f"**Target:** `{target}`",
163
184
  f"**Elements Scanned:** {stats['elements_scanned']}",
164
185
  f"**Violations Found:** {stats['violations_found']}",