synapse-hub 3.0.0 → 3.0.1
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/rules/communication-style.md +4 -0
- package/.agents/rules/context-engineering.md +1 -0
- package/.agents/rules/git-push-protocol.md +17 -0
- package/.agents/skills/agentless-qa-repair/examples/three_phase_repair_workflow.md +40 -0
- package/.agents/skills/antigravity-test-sentinel/examples/sprint_dod_enforcement.md +47 -0
- package/.agents/skills/auth-session-and-cookie-manager/examples/multi_role_auth_matrix.md +70 -0
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_playwright_pom.ts +52 -0
- package/.agents/skills/failure-autopsy-and-healing/examples/closed_loop_healing_walkthrough.md +48 -0
- package/.agents/skills/lean-impact-scoreboard/examples/sample_scoreboard.md +1 -1
- package/.agents/skills/mipro-prompt-compiler/examples/dspy_assertion_optimizer_case.md +46 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +1 -1
- package/.agents/skills/qa-aci-interface/examples/surgical_code_window_walkthrough.md +40 -0
- package/.agents/skills/review-spec/examples/pocock_grilling_decision_rubric.md +40 -0
- package/.agents/skills/skillspector-security-guard/examples/audit_threat_vectors_report.json +35 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +1 -1
- package/.agents/skills/tot-exploratory-testing/examples/payment_gateway_tot_tree.json +62 -0
- package/.agents/skills/verification-before-completion/SKILL.md +2 -2
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +2 -2
- package/.agents/skills/web-agent-gym-benchmarking/examples/enterprise_tasks_suite.json +38 -0
- package/.agents/skills/web-vitals-and-performance-profiler/examples/playwright_cwv_profiler_fixture.ts +61 -0
- package/AGENTS.md +6 -4
- package/CLAUDE.md +1 -0
- package/README.en.md +8 -8
- package/README.md +3 -3
- package/action.yml +1 -1
- package/bin/agent-qa.js +2 -2
- package/docs/ARCHITECTURE.en.md +192 -0
- package/docs/ARCHITECTURE.md +127 -126
- package/docs/CI_CD_INTEGRATION.en.md +96 -0
- package/docs/CI_CD_INTEGRATION.md +24 -22
- package/docs/CLI_REFERENCE.en.md +174 -0
- package/docs/CLI_REFERENCE.md +120 -115
- package/docs/CONTRIBUTING.en.md +49 -0
- package/docs/CONTRIBUTING.md +25 -67
- package/docs/QUALITY_GATES.en.md +115 -0
- package/docs/QUALITY_GATES.md +82 -80
- package/docs/research/agent_skill_training_analysis.md +1 -1
- package/package.json +1 -1
- package/skills_manifest.json +1 -1
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +1 -0
- package/tools/__init__.py +1 -1
- package/tools/auto_heal_reflexion.py +8 -0
- package/tools/browser_helpers.js +2 -2
- package/tools/bump_version.py +15 -14
- package/tools/check_version_sync.py +24 -7
- package/tools/cli.py +3 -3
- package/tools/contract_validator.py +141 -0
- package/tools/dashboard_generator.py +11 -11
- package/tools/db_sandbox.py +22 -9
- package/tools/generate_allure_report.py +1 -1
- package/tools/infer_linter.py +135 -21
- package/tools/mcp_excel_server.py +1 -1
- package/tools/mcp_server.py +7 -4
- package/tools/micro_mutator.py +21 -3
- package/tools/reversible_sandbox.py +2 -2
- package/tools/senior_code_review.py +2 -1
- package/tools/skill_vault.py +82 -3
- package/tools/skillspector.py +5 -1
- package/tools/sync_agent_rules.py +1 -0
- package/tools/test_impact.py +2 -1
- package/tools/update_sentinel.py +3 -3
- package/tools/verify_all.py +2 -2
- package/tools/visual_diff_canvas.py +3 -2
- package/tools/welcome_page.py +1 -1
package/tools/mcp_server.py
CHANGED
|
@@ -46,7 +46,7 @@ import gherkin_suite
|
|
|
46
46
|
import api_fuzzer
|
|
47
47
|
|
|
48
48
|
SERVER_NAME = "enterprise-agent-qa"
|
|
49
|
-
SERVER_VERSION = "3.0.
|
|
49
|
+
SERVER_VERSION = "3.0.1"
|
|
50
50
|
|
|
51
51
|
MCP_TOOLS = [
|
|
52
52
|
{
|
|
@@ -473,13 +473,15 @@ def _handle_pull_alm_story(arguments: Dict[str, Any]) -> str:
|
|
|
473
473
|
|
|
474
474
|
def _handle_report_alm_defect(arguments: Dict[str, Any]) -> str:
|
|
475
475
|
bridge = tms_sync_bridge.TMSBridge()
|
|
476
|
+
raw_screenshot = arguments.get("screenshot_path", "")
|
|
477
|
+
safe_screenshot = _validate_safe_path(raw_screenshot) if raw_screenshot else ""
|
|
476
478
|
ticket = bridge.report_defect(
|
|
477
479
|
project_key=arguments["project_key"],
|
|
478
480
|
test_case_id=arguments["test_case_id"],
|
|
479
481
|
summary=arguments["summary"],
|
|
480
482
|
error_message=arguments["error_message"],
|
|
481
483
|
stack_trace=arguments.get("stack_trace", ""),
|
|
482
|
-
screenshot_path=
|
|
484
|
+
screenshot_path=safe_screenshot
|
|
483
485
|
)
|
|
484
486
|
return json.dumps(ticket, indent=2)
|
|
485
487
|
|
|
@@ -517,7 +519,8 @@ def _handle_convert_traffic_session(arguments: Dict[str, Any]) -> str:
|
|
|
517
519
|
|
|
518
520
|
def _handle_resolve_visual_som_coordinates(arguments: Dict[str, Any]) -> str:
|
|
519
521
|
helper = visual_som_helper.VisualSoMHelper()
|
|
520
|
-
|
|
522
|
+
raw_img = arguments.get("image_path", "screenshot.png")
|
|
523
|
+
img = _validate_safe_path(raw_img)
|
|
521
524
|
lbl = arguments.get("target_label")
|
|
522
525
|
mid = arguments.get("mark_id")
|
|
523
526
|
marked_img, marks_map = helper.generate_som_overlay(img, [])
|
|
@@ -526,7 +529,7 @@ def _handle_resolve_visual_som_coordinates(arguments: Dict[str, Any]) -> str:
|
|
|
526
529
|
|
|
527
530
|
|
|
528
531
|
def _handle_compile_gherkin_suite(arguments: Dict[str, Any]) -> str:
|
|
529
|
-
target = arguments["target"]
|
|
532
|
+
target = _validate_safe_path(arguments["target"])
|
|
530
533
|
res = gherkin_suite.compile_feature(target) if os.path.isfile(target) else gherkin_suite.run(target)
|
|
531
534
|
if isinstance(res, int):
|
|
532
535
|
return json.dumps({"exit_code": res, "detail": "see reports/gherkin_suite_report.json"}, indent=2)
|
package/tools/micro_mutator.py
CHANGED
|
@@ -22,6 +22,7 @@ import os
|
|
|
22
22
|
import re
|
|
23
23
|
import sys
|
|
24
24
|
import json
|
|
25
|
+
import ast
|
|
25
26
|
import shlex
|
|
26
27
|
import html as html_lib
|
|
27
28
|
import argparse
|
|
@@ -59,11 +60,12 @@ MUTATION_RULES = [
|
|
|
59
60
|
(r"\breturn\s+true\b", "return false", "Return Value Inversion (return true -> return false)"),
|
|
60
61
|
(r"\breturn\s+false\b", "return true", "Return Value Inversion (return false -> return true)"),
|
|
61
62
|
(r"\breturn\s+0\b", "return -1", "Return Value Shift (return 0 -> return -1)"),
|
|
63
|
+
(r"\s\?\?\s", " || ", "Nullish Coalescing Inversion (?? -> ||)"),
|
|
62
64
|
]
|
|
63
65
|
|
|
64
66
|
# Action statement pattern for Statement Deletion Mutants
|
|
65
67
|
ACTION_STATEMENT_PATTERN = re.compile(
|
|
66
|
-
r'\.(click|submit|save|update|delete|send|fill|press|type|execute)\s*\(',
|
|
68
|
+
r'\.(click|submit|save|update|delete|send|fill|press|type|execute|check|uncheck|hover|dragTo|selectOption|clear|focus|setInputFiles)\s*\(',
|
|
67
69
|
re.IGNORECASE
|
|
68
70
|
)
|
|
69
71
|
|
|
@@ -74,7 +76,12 @@ ASSERTION_PATTERN = re.compile(
|
|
|
74
76
|
|
|
75
77
|
# Tautological / fake assertions that do not test application logic
|
|
76
78
|
TAUTOLOGY_PATTERN = re.compile(
|
|
77
|
-
r'(?:assert\s+(?:true|1\s*==\s*1)\b
|
|
79
|
+
r'(?:assert\s+(?:true|1\s*==\s*1)\b'
|
|
80
|
+
r'|\bassert\s+([a-zA-Z0-9_\.]+(?:\([^)]*\))?)\s*==\s*\1\b'
|
|
81
|
+
r'|\bassert\s+(?:len\s*\([^)]*\)|[a-zA-Z0-9_\.]+\.(?:length|size\(\)))\s*(?:>=\s*0|>\s*-(?:1|0))\b'
|
|
82
|
+
r'|assertEquals\s*\(\s*([a-zA-Z_]\w*)\s*,\s*\1\s*\)'
|
|
83
|
+
r'|expect\s*\(\s*([a-zA-Z_]\w*)\s*\)\.toBe\s*\(\s*\1\s*\)'
|
|
84
|
+
r'|assertTrue\s*\(\s*true\s*\))',
|
|
78
85
|
re.IGNORECASE
|
|
79
86
|
)
|
|
80
87
|
|
|
@@ -301,6 +308,10 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
|
|
|
301
308
|
|
|
302
309
|
# Case 1: Mutation directly in an assertion statement
|
|
303
310
|
if ASSERTION_PATTERN.search(snippet):
|
|
311
|
+
if TAUTOLOGY_PATTERN.search(snippet):
|
|
312
|
+
m["status"] = "SURVIVED"
|
|
313
|
+
m["reason"] = "Mutant in tautological/dummy assertion - cannot kill mutant without genuine assertion logic"
|
|
314
|
+
continue
|
|
304
315
|
m["status"] = "KILLED"
|
|
305
316
|
m["reason"] = "Direct assertion mutation caught by verification condition"
|
|
306
317
|
killed_count += 1
|
|
@@ -324,6 +335,13 @@ def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tup
|
|
|
324
335
|
tgt = asgn.group(1)
|
|
325
336
|
if any(re.search(rf'\b{re.escape(t)}\b', l_code) for t in dataflow_tokens):
|
|
326
337
|
dataflow_tokens.add(tgt)
|
|
338
|
+
destruct = re.match(r'^\s*(?:(?:const|let|var|val)\s+)?[\{\(]([^\}\)]+)[\}\)]\s*=', l_code)
|
|
339
|
+
if destruct:
|
|
340
|
+
vars_in_destruct = [v.strip().split(':')[0].strip() for v in destruct.group(1).split(',') if v.strip()]
|
|
341
|
+
if any(re.search(rf'\b{re.escape(t)}\b', l_code) for t in dataflow_tokens):
|
|
342
|
+
for v in vars_in_destruct:
|
|
343
|
+
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', v):
|
|
344
|
+
dataflow_tokens.add(v)
|
|
327
345
|
|
|
328
346
|
# Check if any assertion references the variables mutated in this logic branch or downstream dataflow
|
|
329
347
|
has_variable_link = any(any(re.search(rf'\b{re.escape(tok)}\b', a_text) for tok in dataflow_tokens) for _, a_text in downstream)
|
|
@@ -633,7 +651,7 @@ def main():
|
|
|
633
651
|
print(format_junit(report))
|
|
634
652
|
else:
|
|
635
653
|
mode_tag = "LIVE DYNAMIC RUNNER" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC"
|
|
636
|
-
mode_desc = f"LIVE DYNAMIC RUNNER (cmd: '{args.test_cmd}')" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC (fast
|
|
654
|
+
mode_desc = f"LIVE DYNAMIC RUNNER (cmd: '{args.test_cmd}')" if report.get("mode") == "DYNAMIC" else "STATIC HEURISTIC (fast heuristic assertion sensitivity scan; pass --test-cmd for dynamic run)"
|
|
637
655
|
print("\n======================================================================")
|
|
638
656
|
print(f" ASSERTION SENSITIVITY & SYNTACTIC MUTATION SENTINEL [{mode_tag}] ")
|
|
639
657
|
print("======================================================================")
|
|
@@ -60,7 +60,7 @@ class ExecutionSandbox:
|
|
|
60
60
|
"""Executes a forward shell command and immediately records its reversible undo counterpart."""
|
|
61
61
|
try:
|
|
62
62
|
args = shlex.split(forward_cmd) if isinstance(forward_cmd, str) else forward_cmd
|
|
63
|
-
res = subprocess.run(args, text=True, capture_output=True)
|
|
63
|
+
res = subprocess.run(args, text=True, capture_output=True, timeout=60.0)
|
|
64
64
|
if res.returncode == 0:
|
|
65
65
|
self.record_mutation(action_type, target_resource, undo_cmd, {"forward_cmd": forward_cmd})
|
|
66
66
|
return True
|
|
@@ -97,7 +97,7 @@ class ExecutionSandbox:
|
|
|
97
97
|
elif cmd:
|
|
98
98
|
try:
|
|
99
99
|
args = shlex.split(cmd) if isinstance(cmd, str) else cmd
|
|
100
|
-
res = subprocess.run(args, text=True, capture_output=True)
|
|
100
|
+
res = subprocess.run(args, text=True, capture_output=True, timeout=60.0)
|
|
101
101
|
success = (res.returncode == 0)
|
|
102
102
|
if not success:
|
|
103
103
|
err_msg = res.stderr.strip()
|
package/tools/skill_vault.py
CHANGED
|
@@ -50,11 +50,25 @@ QA_SYNONYMS: Dict[str, Set[str]] = {
|
|
|
50
50
|
"dropdown": {"select", "combobox", "option", "menu", "picker", "choice"},
|
|
51
51
|
"invoice": {"billing", "order", "receipt", "payment", "charge", "reconcile"},
|
|
52
52
|
"cart": {"checkout", "basket", "purchase", "item", "order"},
|
|
53
|
-
"upload": {"attachment", "file", "import", "choose-file", "document"},
|
|
53
|
+
"upload": {"attachment", "file", "import", "choose-file", "document", "dropzone"},
|
|
54
|
+
"export": {"export", "download", "csv", "excel", "pdf", "report"},
|
|
55
|
+
"toast": {"toast", "notification", "snackbar", "alert", "message", "banner", "popup"},
|
|
56
|
+
"spinner": {"spinner", "loader", "loading", "shimmer", "skeleton", "progress", "wait"},
|
|
57
|
+
"drawer": {"drawer", "sidebar", "panel", "modal", "aside", "sheet"},
|
|
58
|
+
"tab": {"tab", "tabs", "tabpanel", "nav", "segment"},
|
|
54
59
|
"wait": {"detach", "detached", "sleep", "loading", "spinner", "ready", "networkidle", "smartwait"},
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
|
|
63
|
+
def split_identifier(identifier: str) -> List[str]:
|
|
64
|
+
"""Splits camelCase, PascalCase, and snake_case tokens into lowercase sub-tokens."""
|
|
65
|
+
tokens = re.sub(r'([A-Z]+)', r' \1', identifier).split()
|
|
66
|
+
sub_tokens = []
|
|
67
|
+
for t in tokens:
|
|
68
|
+
sub_tokens.extend(re.split(r'[_\\-]+', t))
|
|
69
|
+
return [t.lower() for t in sub_tokens if len(t) >= 2]
|
|
70
|
+
|
|
71
|
+
|
|
58
72
|
def _expand_tokens_with_synonyms(tokens: List[str]) -> Set[str]:
|
|
59
73
|
"""Expands query tokens using the QA domain synonym clusters."""
|
|
60
74
|
expanded = set(tokens)
|
|
@@ -185,7 +199,13 @@ class SkillVault:
|
|
|
185
199
|
Queries verified primitives using intelligent fuzzy semantic matching:
|
|
186
200
|
combines exact tokens, synonym expansion, n-gram substring match, and SequenceMatcher similarity.
|
|
187
201
|
"""
|
|
188
|
-
|
|
202
|
+
words = re.findall(r'[a-zA-Z0-9_-]+', query)
|
|
203
|
+
raw_tokens = []
|
|
204
|
+
for w in words:
|
|
205
|
+
sub = split_identifier(w)
|
|
206
|
+
raw_tokens.extend(sub if sub else [w.lower()])
|
|
207
|
+
raw_tokens = list(dict.fromkeys(raw_tokens))
|
|
208
|
+
|
|
189
209
|
if not raw_tokens and not tag:
|
|
190
210
|
return self.list_primitives(limit=top_k)
|
|
191
211
|
|
|
@@ -203,7 +223,12 @@ class SkillVault:
|
|
|
203
223
|
scored = []
|
|
204
224
|
for r in rows:
|
|
205
225
|
corpus_text = f"{r['page_class']} {r['method_signature']} {r['semantic_docstring'] or ''} {r['tags'] or ''}".lower()
|
|
206
|
-
|
|
226
|
+
corpus_words = re.findall(r'[a-zA-Z0-9_-]+', corpus_text)
|
|
227
|
+
corpus_tokens = []
|
|
228
|
+
for cw in corpus_words:
|
|
229
|
+
sub = split_identifier(cw)
|
|
230
|
+
corpus_tokens.extend(sub if sub else [cw.lower()])
|
|
231
|
+
corpus_tokens = list(dict.fromkeys(corpus_tokens))
|
|
207
232
|
|
|
208
233
|
# Calculate individual query token matches
|
|
209
234
|
token_scores = [_calculate_token_match_score(qt, corpus_tokens) for qt in raw_tokens]
|
|
@@ -229,6 +254,54 @@ class SkillVault:
|
|
|
229
254
|
scored.sort(key=lambda x: (x["relevance_score"], x["mutation_score"]), reverse=True)
|
|
230
255
|
return scored[:top_k]
|
|
231
256
|
|
|
257
|
+
def harvest_page_classes(self, dir_path: str) -> int:
|
|
258
|
+
"""
|
|
259
|
+
Scans a directory for Page Classes across TS, JS, Python, and Groovy,
|
|
260
|
+
extracts method signatures, and registers them into SkillVault.
|
|
261
|
+
"""
|
|
262
|
+
if not os.path.isdir(dir_path):
|
|
263
|
+
return 0
|
|
264
|
+
count = 0
|
|
265
|
+
for root, _, files in os.walk(dir_path):
|
|
266
|
+
for f in files:
|
|
267
|
+
ext = os.path.splitext(f)[1].lower()
|
|
268
|
+
if ext not in (".py", ".ts", ".js", ".groovy", ".java"):
|
|
269
|
+
continue
|
|
270
|
+
file_path = os.path.join(root, f)
|
|
271
|
+
try:
|
|
272
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as fp:
|
|
273
|
+
content = fp.read()
|
|
274
|
+
except OSError:
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
page_class = os.path.splitext(f)[0]
|
|
278
|
+
method_matches = re.finditer(r'(?:def\s+([a-zA-Z_]\w*)\s*\(([^)]*)\)|(?:async\s+)?([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(?::\s*[^\{]+)?\s*\{)', content)
|
|
279
|
+
for mm in method_matches:
|
|
280
|
+
m_name = mm.group(1) or mm.group(3)
|
|
281
|
+
m_args = mm.group(2) if mm.group(1) else mm.group(4)
|
|
282
|
+
if not m_name or m_name.startswith("_") or m_name in ("constructor", "constructor__"):
|
|
283
|
+
continue
|
|
284
|
+
skill_id = f"SKILL_{page_class.upper()}_{m_name.upper()}"
|
|
285
|
+
sig = f"{m_name}({m_args.strip() if m_args else ''})"
|
|
286
|
+
doc_m = re.search(rf'{re.escape(m_name)}[^\n]*\n\s*[\'\""""/]+([^\'\""""\n*]+)', content)
|
|
287
|
+
doc = doc_m.group(1).strip() if doc_m else f"Page method for {m_name} on {page_class}"
|
|
288
|
+
tags = ",".join(split_identifier(page_class) + split_identifier(m_name))
|
|
289
|
+
|
|
290
|
+
prim = PageClassPrimitive(
|
|
291
|
+
skill_id=skill_id,
|
|
292
|
+
page_class=page_class,
|
|
293
|
+
method_signature=sig,
|
|
294
|
+
method_code=f"// Harvested from {f}\n{sig}",
|
|
295
|
+
locator_tier=1,
|
|
296
|
+
semantic_docstring=doc,
|
|
297
|
+
verified_on_url=f"local://{f}",
|
|
298
|
+
mutation_score=85.0,
|
|
299
|
+
tags=tags
|
|
300
|
+
)
|
|
301
|
+
if self.register_primitive(prim):
|
|
302
|
+
count += 1
|
|
303
|
+
return count
|
|
304
|
+
|
|
232
305
|
def list_primitives(self, limit: int = 50) -> List[Dict[str, Any]]:
|
|
233
306
|
with self._get_connection() as conn:
|
|
234
307
|
rows = conn.execute("SELECT * FROM page_primitives ORDER BY created_at DESC LIMIT ?", (limit,)).fetchall()
|
|
@@ -239,12 +312,18 @@ def main():
|
|
|
239
312
|
parser = argparse.ArgumentParser(description="SkillVault Lifelong POM Library")
|
|
240
313
|
parser.add_argument("--query", default=None, help="Query verified primitives by keyword (fuzzy semantic search)")
|
|
241
314
|
parser.add_argument("--tag", default=None, help="Filter primitives by domain tag (e.g. auth, table, invoice)")
|
|
315
|
+
parser.add_argument("--harvest", default=None, help="Directory path to harvest Page Classes from")
|
|
242
316
|
parser.add_argument("--list", action="store_true", help="List all stored primitives")
|
|
243
317
|
parser.add_argument("--seed-samples", action="store_true", help="Seed initial verified POM primitives")
|
|
244
318
|
args = parser.parse_args()
|
|
245
319
|
|
|
246
320
|
vault = SkillVault()
|
|
247
321
|
|
|
322
|
+
if args.harvest:
|
|
323
|
+
num = vault.harvest_page_classes(args.harvest)
|
|
324
|
+
print(f"✅ Harvested {num} Page Class primitives from '{args.harvest}'.")
|
|
325
|
+
return 0
|
|
326
|
+
|
|
248
327
|
if args.seed_samples:
|
|
249
328
|
samples = [
|
|
250
329
|
PageClassPrimitive(
|
package/tools/skillspector.py
CHANGED
|
@@ -956,7 +956,11 @@ def scan_target(target_path: str) -> ScanReport:
|
|
|
956
956
|
all_findings: List[Finding] = []
|
|
957
957
|
scanned_count = 0
|
|
958
958
|
|
|
959
|
-
SKIP_DIRS = {
|
|
959
|
+
SKIP_DIRS = {
|
|
960
|
+
".git", ".venv", "venv", "node_modules", "__pycache__", ".pytest_cache",
|
|
961
|
+
"dist", "build", "claude-ecosystem-repos", "graphify-out", ".synapse",
|
|
962
|
+
".mypy_cache", ".ruff_cache", ".sandbox_snapshots", "reports"
|
|
963
|
+
}
|
|
960
964
|
SKIP_FILES = {"skillspector.py"}
|
|
961
965
|
SCANNABLE_EXTS = {".md", ".py", ".json", ".yaml", ".yml", ".sh", ".ps1", ".txt", ".ts", ".js"}
|
|
962
966
|
|
|
@@ -92,6 +92,7 @@ In accordance with the MIT License, the original copyright notice crediting **Th
|
|
|
92
92
|
5. **Pragmatic Communication Protocol:**
|
|
93
93
|
- Conversational responses, explanation, and telemetry MUST be in Vietnamese.
|
|
94
94
|
- All codebase assets, test scripts, Page Classes, docstrings, and commits MUST remain strictly in English.
|
|
95
|
+
- English-First Document Ingestion: When reading bilingual docs (e.g. ARCHITECTURE.en.md vs ARCHITECTURE.md), always read the English version (*.en.md) to save 50–65% BPE tokens.
|
|
95
96
|
|
|
96
97
|
6. **Lean Senior Dev Protocol (The 7-Rung Ladder & YAGNI):**
|
|
97
98
|
- The best code is the code never written. Solve the whole problem with the least new code.
|
package/tools/test_impact.py
CHANGED
|
@@ -175,7 +175,8 @@ def get_git_modified_files(repo_root=None):
|
|
|
175
175
|
stdout=subprocess.PIPE,
|
|
176
176
|
stderr=subprocess.PIPE,
|
|
177
177
|
text=True,
|
|
178
|
-
check=True
|
|
178
|
+
check=True,
|
|
179
|
+
timeout=30.0
|
|
179
180
|
)
|
|
180
181
|
files = [line.strip() for line in res.stdout.splitlines() if line.strip()]
|
|
181
182
|
return files
|
package/tools/update_sentinel.py
CHANGED
|
@@ -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", "3.0.
|
|
68
|
+
return data.get("version", "3.0.1")
|
|
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", "3.0.
|
|
77
|
+
return data.get("version", "3.0.1")
|
|
78
78
|
except (OSError, json.JSONDecodeError):
|
|
79
79
|
data = {}
|
|
80
80
|
|
|
81
|
-
return "3.0.
|
|
81
|
+
return "3.0.1"
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def fetch_remote_version():
|
package/tools/verify_all.py
CHANGED
|
@@ -296,7 +296,7 @@ def gate_3_clean_pom(repo_root, mode="AUTO"):
|
|
|
296
296
|
print("[N/A] Hub Mode: Not Applicable (Clean POM rules apply to target test suites in Spokes).")
|
|
297
297
|
return True
|
|
298
298
|
|
|
299
|
-
test_dirs = ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
|
|
299
|
+
test_dirs = ["tests"] if effective_mode == "HUB" else ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
|
|
300
300
|
test_files = []
|
|
301
301
|
for td in test_dirs:
|
|
302
302
|
d = os.path.join(repo_root, td)
|
|
@@ -708,7 +708,7 @@ def gate_7_infer_linter(repo_root, mode="AUTO"):
|
|
|
708
708
|
except (ImportError, OSError) as err:
|
|
709
709
|
print(f"[WARN] Failed to run check_version_sync: {err}")
|
|
710
710
|
|
|
711
|
-
test_dirs = ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
|
|
711
|
+
test_dirs = ["tests"] if effective_mode == "HUB" else ["Scripts", "tests", "test", "e2e", "specs", "cypress"]
|
|
712
712
|
has_tests = False
|
|
713
713
|
target_scan_dir = None
|
|
714
714
|
for td in test_dirs:
|
|
@@ -55,11 +55,12 @@ def compare_canvas_images(
|
|
|
55
55
|
"""
|
|
56
56
|
with Image.open(baseline_path) as img_base, Image.open(current_path) as img_curr:
|
|
57
57
|
# Normalize dimensions and color mode
|
|
58
|
+
curr_image: Image.Image = img_curr
|
|
58
59
|
if img_base.size != img_curr.size:
|
|
59
|
-
|
|
60
|
+
curr_image = img_curr.resize(img_base.size, Image.Resampling.LANCZOS)
|
|
60
61
|
|
|
61
62
|
base_rgb = img_base.convert("RGB")
|
|
62
|
-
curr_rgb =
|
|
63
|
+
curr_rgb = curr_image.convert("RGB")
|
|
63
64
|
|
|
64
65
|
# C-level accelerated pixel difference via Pillow vectorization
|
|
65
66
|
diff = ImageChops.difference(base_rgb, curr_rgb)
|
package/tools/welcome_page.py
CHANGED
|
@@ -47,7 +47,7 @@ def get_hub_metadata() -> Dict[str, Any]:
|
|
|
47
47
|
"""Retrieves version and catalog count from skills_manifest.json."""
|
|
48
48
|
manifest_path = os.path.join(HUB_ROOT, "skills_manifest.json")
|
|
49
49
|
meta = {
|
|
50
|
-
"version": "3.0.
|
|
50
|
+
"version": "3.0.1",
|
|
51
51
|
"totalSkills": 42,
|
|
52
52
|
"author": "Thế Vị (@TeeVee998)",
|
|
53
53
|
"repo": "https://github.com/TeeVee998/Synapse-Hub"
|