synapse-hub 2.2.3 → 2.2.4

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.
@@ -51,7 +51,7 @@ git commit -m "<type>(<scope>): <summary>"
51
51
  # Examples:
52
52
  # git commit -m "feat(billing): add Clean POM for Invoice Reconciliation"
53
53
  # git commit -m "test(auth): add automated login boundary test cases"
54
- # git commit -m "fix(locators): heal stale table row selector in DataMiningPage"
54
+ # git commit -m "fix(locators): heal stale table row selector in OrderSummaryPage"
55
55
  ```
56
56
 
57
57
  ### Step 4: Direct Push to Project Remote
@@ -11,9 +11,9 @@ description: Critical anti-patterns, past mistakes, and strict avoidance rules (
11
11
  | # | Mistake / Anti-Pattern | Root Cause | Mandatory Avoidance Rule |
12
12
  |---|---|---|---|
13
13
  | **1** | **XPath strings parsed as methods** | Regex captured `String TXT = "...contains(...)"`. | In Groovy, `=` before `(` denotes a variable/constant, **never** a method. |
14
- | **2** | **GIS map polygon rejected** | Fixed `(100, 200)` coordinates caused overlap. | Always randomize coordinates via `TestHelper.getUniquePolygonCoordinates()`. |
15
- | **3** | **`ElementClickInterceptedException`** | Clicked while PrimeNG overlay backdrop was fading. | Always use `BasePage.clickJS()` or `window.__helper.clickSafe()` to bypass overlays. |
16
- | **4** | **Context window blowout** | Read 4,368-line `DataMiningPage.groovy` directly. | **Never read large files whole.** Query `tools/repo_map.txt` or read 20-50 line ranges. |
14
+ | **2** | **GIS map / geometric polygon collision** | Hardcoded fixed coordinates `(100, 200)` caused overlap. | Always randomize geometric coordinates via dynamic bounding box/offset randomizer. |
15
+ | **3** | **`ElementClickInterceptedException`** | Clicked while modal/spinner backdrop overlay was fading. | Wait for overlay detachment or bypass via JS dispatch (`clickJS` / `window.__helper.clickSafe()`). |
16
+ | **4** | **Context window blowout** | Read monolithic 4,000+ line Page Class directly. | **Never read large files whole.** Query `tools/repo_map.txt` or read 20-50 line ranges. |
17
17
  | **5** | **Flaky timing on CI/CD** | `Thread.sleep` failed under variable network lag. | Replace 100% with Smart Wait: Angular `isStable()` and CDP `Network Idle 0`. |
18
18
  | **6** | **DB polluted with test junk** | Created entities without executing deletion. | Every data creation test case **must** execute idempotent teardown cleanup. |
19
19
  | **7** | **Graphify/CLI ran on Skill Hub instead of target project** | Defaulted to `CWD = Agent Skill` without checking Active Workspaces. | **Agent Skill folder = read-only hub, NEVER a CLI target.** Always identify the real target project from `Active Workspaces` (the non-hub workspace). If multiple project workspaces exist, ask the user. All outputs (graphify-out/, reports) must land inside the target project folder. |
@@ -30,7 +30,7 @@ When executing tasks within automated testing repositories, Agent must strictly
30
30
  | **Test Script & POM Generation** | `clean-pom-crafter`<br/>`lam-script-generator` | `QA Automation Engineer` | Enforces Flat Architecture, zero XPaths in scripts, dynamic `uniqueCode`, and 2-phase search. Generic plugin templates violate Clean POM standards. |
31
31
  | **Locator Healing & DOM Diffing** | `self-healing-locator`<br/>`failure-autopsy-and-healing` | `Locator Healer Agent` | Uses token-efficient AOM diffing (Role > Label > TestId) and reads local JUnit XML/Screenshots. |
32
32
  | **Flaky Test Triage & Root Cause** | `failure-autopsy-and-healing` | `Flaky Test Analyzer` | Triage occurs via 0-token JUnit XML parsing, `Screenshots/FAILED_*.png` autopsy, and SQLite cosine cache (`memory_cache.db`). |
33
- | **Live Web & Locator Probing** | `playwright-live-interaction` | `Smart Locator Agent` | Direct CDP network idle 0, PrimeNG overlay bypass, and in-browser helper integration. |
33
+ | **Live Web & Locator Probing** | `playwright-live-interaction` | `Smart Locator Agent` | Direct CDP network idle 0, dynamic overlay bypass, and in-browser helper integration. |
34
34
  | **Code Refactoring & Tech Debt** | `code-refactoring-refactor-clean` | *(Consolidated)* | Single unified skill covering SOLID refactoring, code smells, and technical debt remediation. |
35
35
  | **Accessibility (A11y) Testing** | `wcag-audit-patterns`<br/>*(Guideline)* | `a11y-debugging`<br/>*(Tooling Only)* | `wcag-audit-patterns` governs WCAG 2.2 rules; `a11y-debugging` is used solely for low-level Chrome DevTools inspection. |
36
36
 
@@ -18,6 +18,9 @@ description: "Manage browser authentication sessions, storageState, JWT tokens,
18
18
  - Maintain distinct session fixtures for different privilege tiers (e.g. `admin_state.json`, `reviewer_state.json`, `viewer_state.json`).
19
19
  3. **Session Freshness & Auto-Refresh:**
20
20
  - Inspect token expiry timestamp (`exp` in JWT payload); auto-refresh via API endpoint if token lifetime has elapsed.
21
+ 4. **Security & Credential Hygiene:**
22
+ - Saved browser states contain live cookies, session tokens, and local storage values.
23
+ - **Mandatory:** Always add `.auth/` and `*storageState*.json` to your project's `.gitignore` to prevent committing live credentials to version control.
21
24
 
22
25
  ---
23
26
 
@@ -564,7 +564,7 @@ jobs:
564
564
 
565
565
  **Static Analysis Toolchain**
566
566
 
567
- ```python
567
+ ``` oml
568
568
  # pyproject.toml
569
569
  [tool.ruff]
570
570
  line-length = 100
@@ -614,7 +614,7 @@ fail_under = 80
614
614
 
615
615
  **Automated Refactoring Suggestions**
616
616
 
617
- ```python
617
+ ```yaml
618
618
  # Use Sourcery for automatic refactoring suggestions
619
619
  # sourcery.yaml
620
620
  rules:
@@ -622,7 +622,9 @@ rules:
622
622
  - id: merge-duplicate-blocks
623
623
  - id: use-named-expression
624
624
  - id: inline-immediately-returned-variable
625
+ `
625
626
 
627
+ `python
626
628
  # Example: Sourcery will suggest
627
629
  # BEFORE
628
630
  result = []
@@ -636,7 +638,7 @@ result = [item.name for item in items if item.is_active]
636
638
 
637
639
  **Code Quality Dashboard Configuration**
638
640
 
639
- ```yaml
641
+ ```properties
640
642
  # sonar-project.properties
641
643
  sonar.projectKey=my-project
642
644
  sonar.sources=src
@@ -658,7 +660,7 @@ sonar.security.rating=A
658
660
 
659
661
  **Security-Focused Refactoring**
660
662
 
661
- ```python
663
+ ```yaml
662
664
  # Use Semgrep for security-aware refactoring
663
665
  # .semgrep.yml
664
666
  rules:
@@ -815,12 +817,12 @@ Include specific optimizations:
815
817
  for item in items:
816
818
  for other in items:
817
819
  if item.id == other.id:
818
- # process
820
+ pass # process
819
821
 
820
822
  # After: O(n)
821
823
  item_map = {item.id: item for item in items}
822
824
  for item_id, item in item_map.items():
823
- # process
825
+ pass # process
824
826
  ```
825
827
 
826
828
  **Caching Strategy**
@@ -830,7 +832,7 @@ from functools import lru_cache
830
832
  @lru_cache(maxsize=128)
831
833
  def calculate_expensive_metric(data_id: str) -> float:
832
834
  # Expensive calculation cached
833
- return result
835
+ return 0.0
834
836
  ```
835
837
 
836
838
  ### 12. Code Quality Checklist
@@ -6,6 +6,9 @@ description: "Run dynamic security audits: OWASP Top 10, security headers, IDOR,
6
6
  # Security DAST Sentinel (Dynamic Application Security & OWASP Top 10)
7
7
 
8
8
  > **Purpose:** Automated Dynamic Application Security Testing (DAST) and defensive runtime verification across enterprise web applications and REST/GraphQL APIs. Bridges functional automated testing with OWASP Top 10 defensive compliance gates.
9
+ >
10
+ > [!IMPORTANT] Authorization & Scope of Engagement
11
+ > Dynamic security testing and fuzzing must **ONLY** be conducted against environments, domains, and applications for which explicit written authorization and testing agreements are established. Never direct automated security payloads or fuzzers against third-party production infrastructure.
9
12
 
10
13
  ---
11
14
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  > **Bộ não trí tuệ dùng chung và mạng lưới kỹ năng tự hành, tăng cường năng lực cho các trợ lý AI (Google Antigravity, Cursor, Windsurf, Claude Code) trên mọi hệ sinh thái kiểm thử tự động.**
6
6
  > **Tác giả:** Thế Vị ([@TeeVee998](https://github.com/TeeVee998))
7
- > **Phiên bản Hub:** `v2.2.3` | **Quy mô:** 28 Kỹ năng chuẩn hóa, 6 Vai trò AI Swarm, 7 Quy tắc cốt lõi, 20+ Công cụ tự động.
7
+ > **Phiên bản Hub:** `v2.2.4` | **Quy mô:** 28 Kỹ năng chuẩn hóa, 6 Vai trò AI Swarm, 7 Quy tắc cốt lõi, 20+ Công cụ tự động.
8
8
 
9
9
  ---
10
10
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  ```mermaid
16
16
  flowchart TD
17
- subgraph Hub["🧠 Synapse Hub (Intelligence Core v2.2.3)"]
17
+ subgraph Hub["🧠 Synapse Hub (Intelligence Core v2.2.4)"]
18
18
  Skills[".agents/skills/ (28 Skills)"]
19
19
  Agents[".agents/agents/ (6 Specialized Roles)"]
20
20
  Rules[".agents/rules/ (7 Core Rules)"]
@@ -66,6 +66,8 @@ flowchart TD
66
66
  | **Hiện tượng quên ngữ cảnh (Context Rot)** | Xảy ra nhanh chóng sau 40k – 60k tokens | **Miễn nhiễm nhờ 3 tầng Progressive Disclosure** |
67
67
  | **Thời gian tự phục hồi lỗi (MTTR)** | 5 – 15 phút mò mẫm thử nghiệm | **< 1 giây (Tra cứu kinh nghiệm có sẵn)** |
68
68
 
69
+ *(Lưu ý: 300–800 token là chi phí ngữ cảnh động của từng lượt sửa lỗi vi mô (micro-turn payload) nhờ repo_map và distill_log; chưa tính chi phí nạp hệ thống/rules ban đầu của IDE ~7.000 tokens).*
70
+
69
71
  ---
70
72
 
71
73
  ## 🚀 2. Hướng Dẫn Bắt Đầu & Tích Hợp (3 Cách Dùng)
@@ -301,7 +303,7 @@ Dự án được phân phối chính thức dưới giấy phép **[MIT License
301
303
 
302
304
  > **The shared intelligence core and autonomous skill hub empowering AI coding assistants (Google Antigravity / Cursor / Windsurf / Claude Code) across automated testing suites.**
303
305
  > **Author:** Thế Vị ([@TeeVee998](https://github.com/TeeVee998))
304
- > **Hub Version:** `v2.2.3` (Semantic Versioning) | **Integrity Index:** 28 Skills, 6 Agent Swarm Roles, 8 Core Rules, 50+ Specialized Python Tools, 9 Quality Gates.
306
+ > **Hub Version:** `v2.2.4` (Semantic Versioning) | **Integrity Index:** 28 Skills, 6 Agent Swarm Roles, 8 Core Rules, 50+ Specialized Python Tools, 9 Quality Gates.
305
307
 
306
308
  ---
307
309
 
@@ -311,7 +313,7 @@ Dự án được phân phối chính thức dưới giấy phép **[MIT License
311
313
 
312
314
  ```mermaid
313
315
  flowchart TD
314
- subgraph Hub["🧠 Synapse Hub (Intelligence Core v2.2.3)"]
316
+ subgraph Hub["🧠 Synapse Hub (Intelligence Core v2.2.4)"]
315
317
  Skills[".agents/skills/ (28 Skills)"]
316
318
  Agents[".agents/agents/ (6 Specialized Roles)"]
317
319
  Rules[".agents/rules/ (8 Core Rules)"]
@@ -363,6 +365,8 @@ flowchart TD
363
365
  | **Context Rot / Lost-in-the-Middle** | High degradation after 40k – 60k tokens | **Immune via 3-Tier Progressive Disclosure** |
364
366
  | **Mean Time-to-Heal (MTTR)** | 5 – 15 minutes of trial-and-error | **< 1 second (Direct cache resolution)** |
365
367
 
368
+ *(Note: 300–800 tokens represents the dynamic micro-turn execution payload achieved via repo_map and distill_log, excluding the one-time IDE system rules prefix overhead of ~7,000 tokens).*
369
+
366
370
  ---
367
371
 
368
372
  ## 🚀 2. Getting Started & Integration (3 Approaches)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synapse-hub",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "Enterprise AI Agent Intelligence Hub & Autonomous Skill Network",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "name": "Enterprise Agent Skills Hub",
4
- "version": "2.2.3",
4
+ "version": "2.2.4",
5
5
  "description": "Unified Catalog of 28 Specialized Skills for Autonomous AI Agents in Enterprise Software Quality Engineering, Performance, and Architecture.",
6
6
  "totalSkills": 28,
7
7
  "categories": {
@@ -13,7 +13,7 @@
13
13
  (function () {
14
14
  'use strict';
15
15
 
16
- if (window.__qa && window.__qa.__version === '2.2.3') {
16
+ if (window.__qa && window.__qa.__version === '2.2.4') {
17
17
  return window.__qa;
18
18
  }
19
19
 
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  const qa = {
50
- __version: '2.2.3',
50
+ __version: '2.2.4',
51
51
 
52
52
  /**
53
53
  * 1. Smart Action Batching
package/tools/cli.py CHANGED
@@ -29,7 +29,7 @@ sys.path.insert(0, SCRIPT_DIR)
29
29
 
30
30
  def cmd_version(args):
31
31
  manifest_path = os.path.join(REPO_ROOT, "skills_manifest.json")
32
- version = "2.2.3"
32
+ version = "2.2.4"
33
33
  if os.path.exists(manifest_path):
34
34
  try:
35
35
  import json
@@ -36,6 +36,7 @@ Usage:
36
36
  import os
37
37
  import sys
38
38
  import json
39
+ import re
39
40
  import uuid
40
41
  import csv
41
42
  import argparse
@@ -82,7 +83,7 @@ def build_test_script(
82
83
  "// Auto-generated Test Assertions by Synapse-Hub",
83
84
  f"// 1. Assert Status Code is {expected_status}",
84
85
  f"pm.test('Status code is {expected_status}', function () {{",
85
- f" pm.expect(pm.response.code).to.be.oneOf([{expected_status}, 200, 201]);",
86
+ f" pm.expect(pm.response.code).to.eql({expected_status});",
86
87
  "});",
87
88
  "",
88
89
  f"// 2. Assert SLA Latency (< {max_time_ms}ms)",
@@ -280,13 +281,34 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
280
281
  "options": {"raw": {"language": "json"}}
281
282
  }
282
283
 
283
- # URL decomposition
284
- path_segments = [seg for seg in path.strip("/").split("/") if seg]
284
+ # URL decomposition and path parameterization
285
+ raw_path = path
286
+ path_variables = []
287
+ normalized_segments = []
288
+ for seg in path.strip("/").split("/"):
289
+ if not seg:
290
+ continue
291
+ match = re.match(r"^\{([a-zA-Z0-9_-]+)\}$", seg)
292
+ if match:
293
+ param_name = match.group(1)
294
+ raw_path = raw_path.replace(f"{{{param_name}}}", f":{param_name}")
295
+ normalized_segments.append(f":{param_name}")
296
+ default_val = "{{lastCreatedId}}" if param_name.lower() in ("id", "entityid", "orderid") else f"{{{{{param_name}}}}}"
297
+ path_variables.append({
298
+ "key": param_name,
299
+ "value": default_val,
300
+ "description": f"URL Path variable {param_name}"
301
+ })
302
+ else:
303
+ normalized_segments.append(seg)
304
+
285
305
  url_obj = {
286
- "raw": f"{{{{baseUrl}}}}{path}",
306
+ "raw": f"{{{{baseUrl}}}}{raw_path}",
287
307
  "host": ["{{baseUrl}}"],
288
- "path": path_segments
308
+ "path": normalized_segments
289
309
  }
310
+ if path_variables:
311
+ url_obj["variable"] = path_variables
290
312
  if query_params:
291
313
  url_obj["query"] = query_params
292
314
 
@@ -451,10 +473,10 @@ def generate_postman_environment(name: str = "Enterprise-Staging-Env", base_url:
451
473
  "name": name,
452
474
  "values": [
453
475
  {"key": "baseUrl", "value": base_url, "enabled": True},
454
- {"key": "bearerToken", "value": "", "enabled": True},
476
+ {"key": "bearerToken", "value": "", "type": "secret", "enabled": True},
455
477
  {"key": "lastCreatedId", "value": "1", "enabled": True},
456
478
  {"key": "adminUser", "value": "qa_admin@enterprise.local", "enabled": True},
457
- {"key": "adminPass", "value": "SecretVault#2026", "enabled": True}
479
+ {"key": "adminPass", "value": "SecretVault#2026", "type": "secret", "enabled": True}
458
480
  ],
459
481
  "_postman_variable_scope": "environment"
460
482
  }
@@ -122,6 +122,16 @@ def build_jmx_xml(
122
122
  f' <stringProp name="Argument.value">{sla_p95}</stringProp>',
123
123
  ' <stringProp name="Argument.metadata">=</stringProp>',
124
124
  ' </elementProp>',
125
+ ' <elementProp name="MAX_LATENCY" elementType="Argument">',
126
+ ' <stringProp name="Argument.name">MAX_LATENCY</stringProp>',
127
+ f' <stringProp name="Argument.value">${{__P(max_latency, {max(3000, sla_p95 * 2)})}}</stringProp>',
128
+ ' <stringProp name="Argument.metadata">=</stringProp>',
129
+ ' </elementProp>',
130
+ ' <elementProp name="LAST_CREATED_ID" elementType="Argument">',
131
+ ' <stringProp name="Argument.name">LAST_CREATED_ID</stringProp>',
132
+ ' <stringProp name="Argument.value">1</stringProp>',
133
+ ' <stringProp name="Argument.metadata">=</stringProp>',
134
+ ' </elementProp>',
125
135
  ' <elementProp name="BEARER_TOKEN" elementType="Argument">',
126
136
  ' <stringProp name="Argument.name">BEARER_TOKEN</stringProp>',
127
137
  ' <stringProp name="Argument.value"></stringProp>',
@@ -469,9 +479,9 @@ def build_jmx_xml(
469
479
  f'{indent} <intProp name="Assertion.test_type">40</intProp>',
470
480
  f'{indent} </ResponseAssertion>',
471
481
  f'{indent} <hashTree/>',
472
- f'{indent} <!-- SLA Latency Assertion -->',
473
- f'{indent} <DurationAssertion guiclass="DurationAssertionGui" testclass="DurationAssertion" testname="Assert SLA Latency (&lt; ${{SLA_P95}}ms)" enabled="true">',
474
- f'{indent} <stringProp name="DurationAssertion.duration">${{SLA_P95}}</stringProp>',
482
+ f'{indent} <!-- SLA Ceiling Assertion (Percentile P95 is validated post-run by jtl_analyzer.py) -->',
483
+ f'{indent} <DurationAssertion guiclass="DurationAssertionGui" testclass="DurationAssertion" testname="Assert Max Latency Ceiling (&lt; ${{MAX_LATENCY}}ms)" enabled="true">',
484
+ f'{indent} <stringProp name="DurationAssertion.duration">${{MAX_LATENCY}}</stringProp>',
475
485
  f'{indent} </DurationAssertion>',
476
486
  f'{indent} <hashTree/>'
477
487
  ])
@@ -492,7 +502,7 @@ def build_jmx_xml(
492
502
  f'{indent} <!-- Deep Correlation: Random Array Item Selection (Match No: 0) -->',
493
503
  f'{indent} <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor - Random Item Selector" enabled="true">',
494
504
  f'{indent} <stringProp name="JSONPostProcessor.referenceNames">RANDOM_ITEM_ID</stringProp>',
495
- f'{indent} <stringProp name="JSONPostProcessor.jsonPathExprs">$..items[?(@.stock &gt; 0)].id, $..id, $..code</stringProp>',
505
+ f'{indent} <stringProp name="JSONPostProcessor.jsonPathExprs">$..id</stringProp>',
496
506
  f'{indent} <stringProp name="JSONPostProcessor.match_numbers">0</stringProp>',
497
507
  f'{indent} <stringProp name="JSONPostProcessor.defaultValues">${{RANDOM_ITEM_ID}}</stringProp>',
498
508
  f'{indent} </JSONPostProcessor>',
@@ -505,7 +515,7 @@ def build_jmx_xml(
505
515
  f'{indent} <!-- Auto-Extract Dynamic Bearer Token -->',
506
516
  f'{indent} <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor - Bearer Token" enabled="true">',
507
517
  f'{indent} <stringProp name="JSONPostProcessor.referenceNames">BEARER_TOKEN</stringProp>',
508
- f'{indent} <stringProp name="JSONPostProcessor.jsonPathExprs">$.token, $.access_token, $.data.token, $.data.access_token</stringProp>',
518
+ f'{indent} <stringProp name="JSONPostProcessor.jsonPathExprs">$..[\'token\',\'access_token\']</stringProp>',
509
519
  f'{indent} <stringProp name="JSONPostProcessor.match_numbers">1</stringProp>',
510
520
  f'{indent} <stringProp name="JSONPostProcessor.defaultValues">${{BEARER_TOKEN}}</stringProp>',
511
521
  f'{indent} </JSONPostProcessor>',
@@ -516,9 +526,9 @@ def build_jmx_xml(
516
526
  f'{indent} <!-- Auto-Extract Created Entity ID -->',
517
527
  f'{indent} <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor - Entity ID" enabled="true">',
518
528
  f'{indent} <stringProp name="JSONPostProcessor.referenceNames">LAST_CREATED_ID</stringProp>',
519
- f'{indent} <stringProp name="JSONPostProcessor.jsonPathExprs">$.id, $.code, $.data.id, $.data.code</stringProp>',
529
+ f'{indent} <stringProp name="JSONPostProcessor.jsonPathExprs">$..[\'id\',\'code\']</stringProp>',
520
530
  f'{indent} <stringProp name="JSONPostProcessor.match_numbers">1</stringProp>',
521
- f'{indent} <stringProp name="JSONPostProcessor.defaultValues"></stringProp>',
531
+ f'{indent} <stringProp name="JSONPostProcessor.defaultValues">${{LAST_CREATED_ID}}</stringProp>',
522
532
  f'{indent} </JSONPostProcessor>',
523
533
  f'{indent} <hashTree/>'
524
534
  ])
@@ -752,7 +762,10 @@ def parse_openapi_for_jmeter(spec_data: Dict[str, Any]) -> tuple:
752
762
 
753
763
  def replace_path_param(match):
754
764
  pname = match.group(1)
755
- if pname.lower() in ("id", "categoryid", "entityid", "userid"):
765
+ plow = pname.lower()
766
+ if plow in ("itemid", "productid", "item_id", "product_id"):
767
+ return "${RANDOM_ITEM_ID}"
768
+ if plow in ("id", "categoryid", "entityid", "userid", "orderid", "order_id"):
756
769
  return "${LAST_CREATED_ID}"
757
770
  return f"${{{pname}}}"
758
771
 
@@ -794,6 +807,25 @@ def parse_openapi_for_jmeter(spec_data: Dict[str, Any]) -> tuple:
794
807
  "expected_status": expected_status
795
808
  })
796
809
 
810
+ def sampler_sort_key(s: Dict[str, Any]) -> int:
811
+ p = s.get("path", "").lower()
812
+ m = s.get("method", "GET")
813
+ if "auth" in p or "login" in p or "token" in p:
814
+ return 1
815
+ if m == "POST" and "{" not in s.get("path", "") and "${" not in s.get("path", ""):
816
+ return 2
817
+ if m == "GET" and "{" not in s.get("path", "") and "${" not in s.get("path", ""):
818
+ return 3
819
+ if m == "GET":
820
+ return 4
821
+ if m in ("PUT", "PATCH"):
822
+ return 5
823
+ if m == "DELETE":
824
+ return 6
825
+ return 7
826
+
827
+ samplers.sort(key=sampler_sort_key)
828
+
797
829
  return plan_name, base_host, protocol, port, samplers
798
830
 
799
831
 
@@ -993,8 +1025,8 @@ def main():
993
1025
  plan_name, base_host, protocol, port, samplers = parse_openapi_for_jmeter(spec_data)
994
1026
  else:
995
1027
  # Fallback to built-in sample spec
996
- from export_postman_collection import get_sample_spec
997
- spec_data = get_sample_spec()
1028
+ from export_postman_collection import get_domain_spec
1029
+ spec_data = get_domain_spec("ecommerce")
998
1030
  plan_name, base_host, protocol, port, samplers = parse_openapi_for_jmeter(spec_data)
999
1031
 
1000
1032
  # Base URL override if provided
@@ -362,7 +362,7 @@ def main():
362
362
  parser.add_argument("--sla-p95", type=float, default=None, help="P95 Latency threshold in ms (fails if exceeded).")
363
363
  parser.add_argument("--sla-p99", type=float, default=None, help="P99 Latency threshold in ms (fails if exceeded).")
364
364
  parser.add_argument("--sla-avg", type=float, default=None, help="Average Latency threshold in ms (fails if exceeded).")
365
- parser.add_argument("--max-error-rate", type=float, default=None, help="Maximum allowed error rate percentage (e.g. 1.0 = 1%).")
365
+ parser.add_argument("--max-error-rate", type=float, default=None, help="Maximum allowed error rate percentage (e.g. 1.0 = 1%%).")
366
366
  parser.add_argument("--markdown", "-m", help="Output path for Markdown summary report.")
367
367
  parser.add_argument("--json", "-j", help="Output path for JSON summary metrics.")
368
368
  parser.add_argument("--telemetry", help="Path to server telemetry CSV (timestamp, cpu_pct, memory_pct, db_pool_pct) for Full-Stack APM correlation.")
@@ -160,7 +160,7 @@ while (($line = $inputReader.ReadLine()) -ne $null) {
160
160
  result = @{
161
161
  protocolVersion = "2024-11-05"
162
162
  capabilities = @{ tools = @{} }
163
- serverInfo = @{ name = "excel-qa-mcp-server"; version = "2.2.3" }
163
+ serverInfo = @{ name = "excel-qa-mcp-server"; version = "2.2.4" }
164
164
  }
165
165
  }
166
166
  [System.Console]::Out.WriteLine(($resp | ConvertTo-Json -Compress -Depth 10))
@@ -43,7 +43,7 @@ import traffic_session_converter
43
43
  import visual_som_helper
44
44
 
45
45
  SERVER_NAME = "enterprise-agent-qa"
46
- SERVER_VERSION = "2.2.3"
46
+ SERVER_VERSION = "2.2.4"
47
47
 
48
48
  MCP_TOOLS = [
49
49
  {
@@ -41,6 +41,13 @@ DANGEROUS_ACTION_KEYWORDS = [
41
41
  r"\breset\b",
42
42
  r"\btransfer\b",
43
43
  r"\bformat\b",
44
+ # Multilingual & Vietnamese destructive keywords
45
+ r"x[oóòỏõọ][aáàảãạ]",
46
+ r"x[oóòỏõọ]a\s+t[aáàảãạ]t\s+c[aảáàãạ]",
47
+ r"h[uủúùũụ]y",
48
+ r"thanh\s+to[aáàảãạ]n",
49
+ r"r[uúùủũụ]t\s+ti[eềéèẻẽẹ]n",
50
+ r"n[aạáàảã]p\s+ti[eềéèẻẽẹ]n",
44
51
  ]
45
52
 
46
53
  DANGEROUS_SELECTOR_PATTERNS = [
@@ -50,6 +57,8 @@ DANGEROUS_SELECTOR_PATTERNS = [
50
57
  r"\.p-button-danger\b",
51
58
  r"\[aria-label\*=['\"]?delete['\"]?\]",
52
59
  r"button[#\.\-_a-zA-Z0-9]*[_\-]*(?:del|delete|purge|remove|wipe|drop)",
60
+ r":(?:has-text|text)\s*\(\s*['\"][^'\"]*(?:delete|remove|wipe|purge|pay|x[oóòỏõọ][aáàảãạ]|h[uủúùũụ]y|thanh\s*to[aáàảãạ]n)[^'\"]*['\"]\s*\)",
61
+ r"text=['\"][^'\"]*(?:delete|remove|wipe|purge|pay|x[oóòỏõọ][aáàảãạ]|h[uủúùũụ]y|thanh\s*to[aáàảãạ]n)",
53
62
  ]
54
63
 
55
64
  COMPILED_TEXT_REGEX = re.compile("|".join(DANGEROUS_ACTION_KEYWORDS), re.IGNORECASE)
@@ -84,8 +93,12 @@ def is_action_dangerous(action_type: str, text: str = "", selector: str = "") ->
84
93
  return True, f"Element text contains dangerous keyword: '{match}'"
85
94
 
86
95
  # Check selector
87
- if sel_clean and COMPILED_SEL_REGEX.search(sel_clean):
88
- return True, f"Element selector matches destructive pattern: '{sel_clean}'"
96
+ if sel_clean:
97
+ if COMPILED_SEL_REGEX.search(sel_clean):
98
+ return True, f"Element selector matches destructive pattern: '{sel_clean}'"
99
+ if COMPILED_TEXT_REGEX.search(sel_clean):
100
+ match = COMPILED_TEXT_REGEX.search(sel_clean).group(0)
101
+ return True, f"Element selector contains dangerous keyword: '{match}' in '{sel_clean}'"
89
102
 
90
103
  # Specific dangerous action types
91
104
  if action_type.lower() in ("drop_database", "wipe_environment", "execute_payment"):
@@ -165,7 +165,7 @@ PROHIBITION_PREFIX = re.compile(
165
165
  )
166
166
 
167
167
  ALERT_BLOCK_PREFIX = re.compile(
168
- r"^\s*>\s*\[!(?:WARNING|CAUTION|DANGER|ALERT|IMPORTANT|NOTE|TIP)\]",
168
+ r"^\s*>\s*\[!(?:WARNING|CAUTION|DANGER|ALERT)\]",
169
169
  re.IGNORECASE | re.MULTILINE,
170
170
  )
171
171
 
@@ -189,23 +189,20 @@ def is_in_prohibition_context(content: str, start: int, end: int) -> bool:
189
189
  if PROHIBITION_PREFIX.search(current_line):
190
190
  return True
191
191
 
192
- # 2. Markdown Caution/Warning Callout Block
193
- if any(ALERT_BLOCK_PREFIX.search(line) for line in lines[-5:]):
192
+ # 2. Markdown Caution/Warning Callout Block with explicit negative imperative
193
+ has_alert = any(ALERT_BLOCK_PREFIX.search(line) for line in lines[-5:])
194
+ if has_alert and any(re.search(r'\b(never|do not|don[\'’]t|must not|forbidden|prohibited|avoid)\b', line, re.IGNORECASE) for line in lines[-5:]):
194
195
  return True
195
196
 
196
197
  # 3. Defensive Guideline headings ("Anti-Pattern Blacklist", "Forbidden actions")
197
198
  preceding_lower = preceding_text.lower()
198
199
  defensive_markers = (
199
- "anti-pattern",
200
+ "anti-pattern blacklist",
200
201
  "mistake / anti-pattern",
201
202
  "strictly forbidden",
202
203
  "never use",
203
204
  "mandatory avoidance",
204
205
  "forbidden actions",
205
- "prohibited",
206
- "blacklist",
207
- "threat model",
208
- "vulnerability check",
209
206
  "zero-token error blacklist",
210
207
  )
211
208
  if any(marker in preceding_lower for marker in defensive_markers):
@@ -927,6 +924,9 @@ def scan_target(target_path: str) -> ScanReport:
927
924
  pass
928
925
 
929
926
  path_obj = Path(target_path).resolve()
927
+ if not path_obj.exists():
928
+ raise FileNotFoundError(f"Target path does not exist: {target_path}")
929
+
930
930
  all_findings: List[Finding] = []
931
931
  scanned_count = 0
932
932