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.
- package/.agents/rules/git-push-protocol.md +1 -1
- package/.agents/rules/lessons-learned.md +3 -3
- package/.agents/rules/skill-precedence.md +1 -1
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +3 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +9 -7
- package/.agents/skills/security-dast-sentinel/SKILL.md +3 -0
- package/README.md +8 -4
- package/package.json +1 -1
- package/skills_manifest.json +1 -1
- package/tools/browser_helpers.js +2 -2
- package/tools/cli.py +1 -1
- package/tools/export_postman_collection.py +29 -7
- package/tools/generate_jmx_plan.py +42 -10
- package/tools/jtl_analyzer.py +1 -1
- package/tools/mcp_excel_server.ps1 +1 -1
- package/tools/mcp_server.py +1 -1
- package/tools/safety_firewall.py +15 -2
- package/tools/skillspector.py +8 -8
|
@@ -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
|
|
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
|
|
15
|
-
| **3** | **`ElementClickInterceptedException`** | Clicked while
|
|
16
|
-
| **4** | **Context window blowout** | Read 4,
|
|
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,
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
package/skills_manifest.json
CHANGED
|
@@ -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.
|
|
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": {
|
package/tools/browser_helpers.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
(function () {
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
if (window.__qa && window.__qa.__version === '2.2.
|
|
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.
|
|
50
|
+
__version: '2.2.4',
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* 1. Smart Action Batching
|
package/tools/cli.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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}}}}{
|
|
306
|
+
"raw": f"{{{{baseUrl}}}}{raw_path}",
|
|
287
307
|
"host": ["{{baseUrl}}"],
|
|
288
|
-
"path":
|
|
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
|
|
473
|
-
f'{indent} <DurationAssertion guiclass="DurationAssertionGui" testclass="DurationAssertion" testname="Assert
|
|
474
|
-
f'{indent} <stringProp name="DurationAssertion.duration">${{
|
|
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 (< ${{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">$..
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
|
997
|
-
spec_data =
|
|
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
|
package/tools/jtl_analyzer.py
CHANGED
|
@@ -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.
|
|
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))
|
package/tools/mcp_server.py
CHANGED
package/tools/safety_firewall.py
CHANGED
|
@@ -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
|
|
88
|
-
|
|
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"):
|
package/tools/skillspector.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|