- 💼 ECC Pro
+ ECC Pro
نجی ریپوز · GitHub App · $19/نشست/ماہ
|
- ❤️ اسپانسر
+ اسپانسر
OSS کو فنڈ کریں · $5/ماہ سے
|
- 💬 کمیونٹی
+ کمیونٹی
Discussions · Q&A · Show & Tell
|
- 🤖 GitHub App
+ GitHub App
انسٹال · PR آڈٹس · مفت ٹیئر
|
diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md
index f37808d7..241f675c 100644
--- a/docs/zh-CN/AGENTS.md
+++ b/docs/zh-CN/AGENTS.md
@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — 智能体指令
-这是一个**生产就绪的 AI 编码插件**,提供 63 个专业代理、251 项技能、79 条命令以及自动化钩子工作流,用于软件开发。
+这是一个**生产就绪的 AI 编码插件**,提供 64 个专业代理、255 项技能、79 条命令以及自动化钩子工作流,用于软件开发。
**版本:** 2.0.0-rc.1
@@ -146,8 +146,8 @@
## 项目结构
```
-agents/ — 63 个专业子代理
-skills/ — 251 个工作流技能和领域知识
+agents/ — 64 个专业子代理
+skills/ — 255 个工作流技能和领域知识
commands/ — 79 个斜杠命令
hooks/ — 基于触发的自动化
rules/ — 始终遵循的指导方针(通用 + 每种语言)
diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md
index 0014cf38..9cb11465 100644
--- a/docs/zh-CN/README.md
+++ b/docs/zh-CN/README.md
@@ -224,7 +224,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
/plugin list ecc@ecc
```
-**搞定!** 你现在可以使用 63 个智能体、251 项技能和 79 个命令了。
+**搞定!** 你现在可以使用 64 个智能体、255 项技能和 79 个命令了。
***
@@ -1136,9 +1136,9 @@ opencode
| 功能特性 | Claude Code | OpenCode | 状态 |
|---------|---------------|----------|--------|
-| 智能体 | PASS: 63 个 | PASS: 12 个 | **Claude Code 领先** |
+| 智能体 | PASS: 64 个 | PASS: 12 个 | **Claude Code 领先** |
| 命令 | PASS: 79 个 | PASS: 35 个 | **Claude Code 领先** |
-| 技能 | PASS: 251 项 | PASS: 37 项 | **Claude Code 领先** |
+| 技能 | PASS: 255 项 | PASS: 37 项 | **Claude Code 领先** |
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
@@ -1244,9 +1244,9 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|---------|-----------------------|------------|-----------|----------|
-| **智能体** | 63 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
+| **智能体** | 64 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
| **命令** | 79 | 共享 | 基于指令 | 35 |
-| **技能** | 251 | 共享 | 10 (原生格式) | 37 |
+| **技能** | 255 | 共享 | 10 (原生格式) | 37 |
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
diff --git a/scripts/hooks/plugin-hook-bootstrap.js b/scripts/hooks/plugin-hook-bootstrap.js
index c965d403..cc6724ee 100644
--- a/scripts/hooks/plugin-hook-bootstrap.js
+++ b/scripts/hooks/plugin-hook-bootstrap.js
@@ -171,7 +171,14 @@ function main() {
process.exit(Number.isInteger(result.status) ? result.status : 0);
}
-if (require.main === module) {
+// Run when invoked as a hook entry. Production hooks load this via
+// `node -e "...; process.argv.splice(1,0,s); require(s)"`; on Node 21+ that
+// leaves require.main undefined (not this module), which previously skipped
+// main() and made every plugin hook a silent no-op. Guard on both the
+// direct-entry case and that eval-bootstrap case. When imported for its
+// exports (tests), require.main is a real, different module, so main() stays
+// dormant.
+if (require.main === module || require.main === undefined) {
main();
}
diff --git a/skills/codehealth-mcp/SKILL.md b/skills/codehealth-mcp/SKILL.md
index 1f41e585..08d8ee47 100644
--- a/skills/codehealth-mcp/SKILL.md
+++ b/skills/codehealth-mcp/SKILL.md
@@ -8,7 +8,7 @@ origin: community
Structural maintainability feedback for AI-assisted coding. Complements style/lint skills (`coding-standards`, `plankton-code-quality`) with **design-level** health scores and regression gates.
-**Upstream:** [codescene-oss/codescene-mcp-server](https://github.com/codescene-oss/codescene-mcp-server)
+**Upstream:** [codescene-oss/codescene-mcp-server](https://github.com/codescene-oss/codescene-mcp-server)
**Package:** `@codescene/codehealth-mcp` (stdio via npx)
## Security and boundaries
diff --git a/skills/fastapi-patterns/SKILL.md b/skills/fastapi-patterns/SKILL.md
index 3e7ae88f..34ae89a7 100644
--- a/skills/fastapi-patterns/SKILL.md
+++ b/skills/fastapi-patterns/SKILL.md
@@ -510,4 +510,4 @@ async def list_items(db: AsyncSession = Depends(get_db)):
- Wrap database mutation boundaries gracefully within transactions inside your service layer, catching structural database errors directly.
- Parse JWT parameters defensively, expecting potential string/integer cast mismatches from modern payload variations.
- Enforce deterministic sorting (e.g., `.order_by(Model.id)`) on all offset/limit paginated endpoints to avoid data skips.
-- Isolate authorization checks from core authentication dependencies to provide precise REST status signals (`401` vs `403`).
\ No newline at end of file
+- Isolate authorization checks from core authentication dependencies to provide precise REST status signals (`401` vs `403`).
diff --git a/skills/frontend-a11y/SKILL.md b/skills/frontend-a11y/SKILL.md
index 08763c1c..efdf492a 100644
--- a/skills/frontend-a11y/SKILL.md
+++ b/skills/frontend-a11y/SKILL.md
@@ -203,8 +203,7 @@ Use ARIA only when native HTML semantics are insufficient. Wrong ARIA is worse t
This action cannot be undone.
```
diff --git a/skills/homelab-pihole-dns/SKILL.md b/skills/homelab-pihole-dns/SKILL.md
index 0048749c..621cf59e 100644
--- a/skills/homelab-pihole-dns/SKILL.md
+++ b/skills/homelab-pihole-dns/SKILL.md
@@ -96,11 +96,11 @@ less pi-hole-install.sh # review before proceeding
bash pi-hole-install.sh
# Follow the interactive installer:
-# 1. Select network interface (eth0 for wired — recommended)
-# 2. Select upstream DNS (Cloudflare or leave default — can change later)
-# 3. Confirm static IP
-# 4. Install the web admin interface (recommended)
-# 5. Note the admin password shown at the end
+# 1. Select network interface (eth0 for wired — recommended)
+# 2. Select upstream DNS (Cloudflare or leave default — can change later)
+# 3. Confirm static IP
+# 4. Install the web admin interface (recommended)
+# 5. Note the admin password shown at the end
```
## Pointing Your Network at Pi-hole
@@ -183,9 +183,9 @@ sudo systemctl start cloudflared
sudo systemctl enable cloudflared
# Now point Pi-hole at the local DoH proxy:
-# Pi-hole admin → Settings → DNS → Custom upstream DNS
-# Set to: 127.0.0.1#5053
-# Uncheck all other upstream resolvers
+# Pi-hole admin → Settings → DNS → Custom upstream DNS
+# Set to: 127.0.0.1#5053
+# Uncheck all other upstream resolvers
```
## Local DNS Records
diff --git a/skills/homelab-wireguard-vpn/SKILL.md b/skills/homelab-wireguard-vpn/SKILL.md
index 65b58fb7..39a53ff6 100644
--- a/skills/homelab-wireguard-vpn/SKILL.md
+++ b/skills/homelab-wireguard-vpn/SKILL.md
@@ -218,8 +218,8 @@ stays reachable after an IP change.
restart: unless-stopped
# ddns.env (chmod 600, not committed to git):
-# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
-# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
+# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
+# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
# Option 2: DuckDNS (free, simple)
Sign up at duckdns.org → get a token and subdomain (myhome.duckdns.org)
diff --git a/skills/inherit-legacy-style/SKILL.md b/skills/inherit-legacy-style/SKILL.md
index bc3973c5..dda2e0ce 100644
--- a/skills/inherit-legacy-style/SKILL.md
+++ b/skills/inherit-legacy-style/SKILL.md
@@ -71,9 +71,9 @@ Before interrupting the user, evaluate signal strength:
For each strong-signal conflict, present exactly ONE question with 4 options:
-> 📄 Evidence: `pathA` uses style X, `pathB` uses style Y
-> ⚠️ Risk: mixing both fractures the project style
-> ❓ Choose: `1` follow X `2` follow Y `3` this is evolution, update rules `4` I have a new rule
+> Evidence: `pathA` uses style X, `pathB` uses style Y
+> WARNING: Risk: mixing both fractures the project style
+> Choose: `1` follow X `2` follow Y `3` this is evolution, update rules `4` I have a new rule
Suspend until the user answers, then proceed to the next conflict. Never stack questions.
@@ -89,7 +89,7 @@ Ask the user for enforcement strength (use `AskUserQuestion`):
| Option | Mechanism |
|---|---|
| **1** Soft hook (recommended) | Write `@.ai-style-rules.md` reference into project `CLAUDE.md` |
-| **2** Hard hook | Soft hook + `PreToolUse[Write|Edit|MultiEdit]` Hook in `settings.json` |
+| **2** Hard hook | Soft hook + `PreToolUse[Write\|Edit\|MultiEdit]` Hook in `settings.json` |
| **3** No hook | Keep the rules file; user references manually |
### Branch B — Incremental Sniff
@@ -120,12 +120,12 @@ This skill auto-detects whether it's a first-time or incremental run via `.ai-st
## Anti-Patterns
-- ❌ Do NOT skip the scale measurement step — sampling a 30-file project "starves" it; full-scanning a 5,000-file repo blows up
-- ❌ Do NOT stack multiple conflict questions at once — grilling is strictly one-at-a-time
-- ❌ Do NOT overwrite old rules in incremental mode — always append evolution logs
-- ❌ Do NOT default to "hard hook" without asking — enforcement strength is the user's call
-- ❌ Do NOT judge syntax or tech-stack quality — this skill aligns meta-architecture only
-- ❌ Do NOT copy bugs from exemplar files — reuse structure, flag defects
+- FAIL: Do NOT skip the scale measurement step — sampling a 30-file project "starves" it; full-scanning a 5,000-file repo blows up
+- FAIL: Do NOT stack multiple conflict questions at once — grilling is strictly one-at-a-time
+- FAIL: Do NOT overwrite old rules in incremental mode — always append evolution logs
+- FAIL: Do NOT default to "hard hook" without asking — enforcement strength is the user's call
+- FAIL: Do NOT judge syntax or tech-stack quality — this skill aligns meta-architecture only
+- FAIL: Do NOT copy bugs from exemplar files — reuse structure, flag defects
## Best Practices
diff --git a/skills/motion-patterns/SKILL.md b/skills/motion-patterns/SKILL.md
index 16425d18..a883ea45 100644
--- a/skills/motion-patterns/SKILL.md
+++ b/skills/motion-patterns/SKILL.md
@@ -337,8 +337,7 @@ export function ExpandingCard({ title, body }: { title: string; body: string })
duration: motionTokens.duration.normal,
ease: motionTokens.easing.smooth,
}}
->
- {children}
+> {children}
```
diff --git a/skills/quarkus-patterns/SKILL.md b/skills/quarkus-patterns/SKILL.md
index 3de2cc71..c1526c48 100644
--- a/skills/quarkus-patterns/SKILL.md
+++ b/skills/quarkus-patterns/SKILL.md
@@ -70,7 +70,7 @@ public class OrderProcessingService {
```java
@ApplicationScoped
public class ProcessingService {
-
+
public void processDocument(Document doc) {
LogContext logContext = CustomLog.getCurrentContext();
try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {
@@ -78,12 +78,12 @@ public class ProcessingService {
logContext.put("documentId", doc.getId().toString());
logContext.put("documentType", doc.getType());
logContext.put("userId", SecurityContext.getUserId());
-
+
log.info("Starting document processing");
-
+
// All logs within this scope inherit the context
processInternal(doc);
-
+
log.info("Document processing completed");
} catch (Exception e) {
log.error("Document processing failed", e);
@@ -103,7 +103,7 @@ public class ProcessingService {
true
-
+
@@ -120,7 +120,7 @@ public class ProcessingService {
public class EventService {
private final EventRepository eventRepository;
private final ObjectMapper objectMapper;
-
+
public void createSuccessEvent(Object payload, String eventType) {
Objects.requireNonNull(payload, "Payload cannot be null");
Event event = new Event();
@@ -128,11 +128,11 @@ public class EventService {
event.setStatus(EventStatus.SUCCESS);
event.setPayload(serializePayload(payload));
event.setTimestamp(Instant.now());
-
+
eventRepository.persist(event);
log.info("Success event created: {}", eventType);
}
-
+
public void createErrorEvent(Object payload, String eventType, String errorMessage) {
Objects.requireNonNull(payload, "Payload cannot be null");
if (errorMessage == null || errorMessage.isBlank()) {
@@ -144,11 +144,11 @@ public class EventService {
event.setErrorMessage(errorMessage);
event.setPayload(serializePayload(payload));
event.setTimestamp(Instant.now());
-
+
eventRepository.persist(event);
log.error("Error event created: {} - {}", eventType, errorMessage);
}
-
+
private String serializePayload(Object payload) {
try {
return objectMapper.writeValueAsString(payload);
@@ -167,10 +167,10 @@ public class EventService {
@RequiredArgsConstructor
public class BusinessRulesPublisher {
private final ProducerTemplate producerTemplate;
-
+
public void publishSync(BusinessRulesPayload payload) {
producerTemplate.sendBody(
- "direct:business-rules-publisher",
+ "direct:business-rules-publisher",
payload
);
}
@@ -182,23 +182,23 @@ public class BusinessRulesPublisher {
```java
@ApplicationScoped
public class BusinessRulesRoute extends RouteBuilder {
-
+
@ConfigProperty(name = "camel.rabbitmq.queue.business-rules")
String businessRulesQueue;
-
+
@ConfigProperty(name = "rabbitmq.host")
String rabbitHost;
-
+
@ConfigProperty(name = "rabbitmq.port")
Integer rabbitPort;
-
+
@Override
public void configure() {
from("direct:business-rules-publisher")
.routeId("business-rules-publisher")
.log("Publishing message to RabbitMQ: ${body}")
.marshal().json(JsonLibrary.Jackson)
- .toF("spring-rabbitmq:%s?hostname=%s&portNumber=%d",
+ .toF("spring-rabbitmq:%s?hostname=%s&portNumber=%d",
businessRulesQueue, rabbitHost, rabbitPort);
}
}
@@ -209,7 +209,7 @@ public class BusinessRulesRoute extends RouteBuilder {
```java
@ApplicationScoped
public class DocumentProcessingRoute extends RouteBuilder {
-
+
@Override
public void configure() {
// Error handling
@@ -217,7 +217,7 @@ public class DocumentProcessingRoute extends RouteBuilder {
.handled(true)
.to("direct:validation-error-handler")
.log("Validation error: ${exception.message}");
-
+
// Main processing route
from("direct:process-document")
.routeId("document-processing")
@@ -232,7 +232,7 @@ public class DocumentProcessingRoute extends RouteBuilder {
.otherwise()
.to("direct:process-generic")
.end();
-
+
from("direct:validation-error-handler")
.bean(EventService.class, "createErrorEvent")
.log("Validation error handled");
@@ -245,24 +245,24 @@ public class DocumentProcessingRoute extends RouteBuilder {
```java
@ApplicationScoped
public class FileMonitoringRoute extends RouteBuilder {
-
+
@ConfigProperty(name = "file.input.directory")
String inputDirectory;
-
+
@ConfigProperty(name = "file.processed.directory")
String processedDirectory;
-
+
@ConfigProperty(name = "file.error.directory")
String errorDirectory;
-
+
@Override
public void configure() {
- from("file:" + inputDirectory + "?move=" + processedDirectory +
+ from("file:" + inputDirectory + "?move=" + processedDirectory +
"&moveFailed=" + errorDirectory + "&delay=5000")
.routeId("file-monitor")
.log("Processing file: ${header.CamelFileName}")
.to("direct:process-file");
-
+
from("direct:process-file")
.bean(OrderProcessingService.class, "processFile")
.log("File processing completed");
@@ -275,13 +275,13 @@ public class FileMonitoringRoute extends RouteBuilder {
```java
@ApplicationScoped
public class InvoiceRoute extends RouteBuilder {
-
+
@Override
public void configure() {
from("direct:invoice-validation")
.bean(InvoiceFlowValidator.class, "validateFlowWithConfig")
.log("Validation result: ${body}");
-
+
from("direct:persist-and-publish")
.bean(DocumentJobService.class, "createDocumentAndJobEntities")
.bean(BusinessRulesPublisher.class, "publishAsync")
@@ -334,7 +334,7 @@ public class DocumentResource {
```java
@ApplicationScoped
public class DocumentRepository implements PanacheRepository {
-
+
public List findByStatus(DocumentStatus status, int page, int size) {
return find("status = ?1 order by createdAt desc", status)
.page(page, size)
@@ -344,7 +344,7 @@ public class DocumentRepository implements PanacheRepository {
public Optional findByReferenceNumber(String referenceNumber) {
return find("referenceNumber", referenceNumber).firstResultOptional();
}
-
+
public long countByStatusAndDate(DocumentStatus status, LocalDate date) {
return count("status = ?1 and createdAt >= ?2", status, date.atStartOfDay());
}
@@ -367,11 +367,11 @@ public class DocumentService {
document.setDescription(request.description());
document.setStatus(DocumentStatus.PENDING);
document.setCreatedAt(Instant.now());
-
+
repo.persist(document);
-
+
eventService.createSuccessEvent(document, "DOCUMENT_CREATED");
-
+
return document;
}
@@ -398,7 +398,7 @@ public record CreateDocumentRequest(
public record DocumentResponse(Long id, String referenceNumber, DocumentStatus status) {
public static DocumentResponse from(Document document) {
- return new DocumentResponse(document.getId(), document.getReferenceNumber(),
+ return new DocumentResponse(document.getId(), document.getReferenceNumber(),
document.getStatus());
}
}
@@ -414,7 +414,7 @@ public class ValidationExceptionMapper implements ExceptionMapper cv.getPropertyPath() + ": " + cv.getMessage())
.collect(Collectors.joining(", "));
-
+
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "validation_error", "message", message))
.build();
@@ -444,30 +444,30 @@ public class GenericExceptionMapper implements ExceptionMapper {
public class FileStorageService {
private final S3Client s3Client;
private final ExecutorService executorService;
-
+
@ConfigProperty(name = "storage.bucket-name")
String bucketName;
-
+
public CompletableFuture uploadOriginalFile(
- InputStream inputStream,
- long size,
+ InputStream inputStream,
+ long size,
LogContext logContext,
InvoiceFormat format) {
-
+
return CompletableFuture.supplyAsync(() -> {
try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {
String path = generateStoragePath(format);
-
+
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucketName)
.key(path)
.contentLength(size)
.build();
-
+
s3Client.putObject(request, RequestBody.fromInputStream(inputStream, size));
-
+
log.info("File uploaded to S3: {}", path);
-
+
return new StoredDocumentInfo(path, size, Instant.now());
} catch (Exception e) {
log.error("Failed to upload file to S3", e);
@@ -513,7 +513,7 @@ public class DocumentCacheService {
hibernate-orm:
database:
generation: drop-and-create
-
+
rabbitmq:
host: localhost
port: 5672
@@ -539,7 +539,7 @@ public class DocumentCacheService {
hibernate-orm:
database:
generation: validate
-
+
rabbitmq:
host: ${RABBITMQ_HOST}
port: ${RABBITMQ_PORT}
@@ -632,7 +632,7 @@ public class CamelHealthCheck implements HealthCheck {
io.quarkus
quarkus-config-yaml
-
+
org.apache.camel.quarkus
@@ -646,7 +646,7 @@ public class CamelHealthCheck implements HealthCheck {
org.apache.camel.quarkus
camel-quarkus-bean
-
+
org.projectlombok
@@ -654,7 +654,7 @@ public class CamelHealthCheck implements HealthCheck {
${lombok.version}
provided
-
+
io.quarkiverse.logging.logback
diff --git a/skills/quarkus-security/SKILL.md b/skills/quarkus-security/SKILL.md
index 4a9af479..0222646b 100644
--- a/skills/quarkus-security/SKILL.md
+++ b/skills/quarkus-security/SKILL.md
@@ -28,7 +28,7 @@ Best practices for securing Quarkus applications with authentication, authorizat
@Path("/api/protected")
@Authenticated
public class ProtectedResource {
-
+
@Inject
JsonWebToken jwt;
@@ -65,20 +65,20 @@ quarkus.oidc.credentials.secret=${OIDC_SECRET}
@Provider
@Priority(Priorities.AUTHENTICATION)
public class CustomAuthFilter implements ContainerRequestFilter {
-
+
@Inject
SecurityIdentity identity;
@Override
public void filter(ContainerRequestContext requestContext) {
String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
-
+
// Reject immediately if header is absent or malformed
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
return;
}
-
+
String token = authHeader.substring(7);
if (!validateToken(token)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
@@ -100,7 +100,7 @@ public class CustomAuthFilter implements ContainerRequestFilter {
@Path("/api/admin")
@RolesAllowed("ADMIN")
public class AdminResource {
-
+
@GET
@Path("/users")
public List listUsers() {
@@ -118,7 +118,7 @@ public class AdminResource {
@Path("/api/users")
public class UserResource {
-
+
@Inject
SecurityIdentity securityIdentity;
@@ -127,7 +127,7 @@ public class UserResource {
@RolesAllowed("USER")
public Response getUser(@PathParam("id") Long id) {
// Check ownership
- if (!securityIdentity.hasRole("ADMIN") &&
+ if (!securityIdentity.hasRole("ADMIN") &&
!isOwner(id, securityIdentity.getPrincipal().getName())) {
return Response.status(Response.Status.FORBIDDEN).build();
}
@@ -145,7 +145,7 @@ public class UserResource {
```java
@ApplicationScoped
public class SecurityService {
-
+
@Inject
SecurityIdentity securityIdentity;
@@ -153,7 +153,7 @@ public class SecurityService {
if (securityIdentity.isAnonymous()) {
return false;
}
-
+
if (securityIdentity.hasRole("ADMIN")) {
return true;
}
@@ -229,7 +229,7 @@ List users = User.list("email = ?1 and active = ?2", email, true);
Optional user = User.find("username", username).firstResultOptional();
// GOOD: Named parameters
-List users = User.list("email = :email and age > :minAge",
+List users = User.list("email = :email and age > :minAge",
Parameters.with("email", email).and("minAge", 18));
```
@@ -256,7 +256,7 @@ public class User extends PanacheEntity {
```java
@ApplicationScoped
public class PasswordService {
-
+
public String hash(String plainPassword) {
return BcryptUtil.bcryptHash(plainPassword);
}
@@ -324,7 +324,7 @@ quarkus.vault.authentication.kubernetes.role=my-role
```java
@ApplicationScoped
public class SecretService {
-
+
@ConfigProperty(name = "api-key")
String apiKey; // Fetched from Vault
@@ -351,7 +351,7 @@ public class RateLimitFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
String clientId = getClientIdentifier();
- RateLimiter limiter = limiters.computeIfAbsent(clientId,
+ RateLimiter limiter = limiters.computeIfAbsent(clientId,
k -> RateLimiter.create(100.0)); // 100 requests per second
if (!limiter.tryAcquire()) {
@@ -377,25 +377,25 @@ public class RateLimitFilter implements ContainerRequestFilter {
```java
@Provider
public class SecurityHeadersFilter implements ContainerResponseFilter {
-
+
@Override
public void filter(ContainerRequestContext request, ContainerResponseContext response) {
MultivaluedMap headers = response.getHeaders();
-
+
// Prevent clickjacking
headers.putSingle("X-Frame-Options", "DENY");
-
+
// XSS protection
headers.putSingle("X-Content-Type-Options", "nosniff");
headers.putSingle("X-XSS-Protection", "1; mode=block");
-
+
// HSTS
headers.putSingle("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
-
+
// CSP — avoid 'unsafe-inline' for script-src as it negates XSS protection;
// use nonces or hashes instead. 'unsafe-inline' for style-src is acceptable
// when CSS frameworks require it, but prefer nonces where possible.
- headers.putSingle("Content-Security-Policy",
+ headers.putSingle("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
}
}
@@ -412,11 +412,11 @@ public class AuditService {
SecurityIdentity securityIdentity;
public void logAccess(String resource, String action) {
- String user = securityIdentity.isAnonymous()
- ? "anonymous"
+ String user = securityIdentity.isAnonymous()
+ ? "anonymous"
: securityIdentity.getPrincipal().getName();
-
- LOG.infof("AUDIT: user=%s action=%s resource=%s timestamp=%s",
+
+ LOG.infof("AUDIT: user=%s action=%s resource=%s timestamp=%s",
user, action, resource, Instant.now());
}
}
diff --git a/skills/quarkus-tdd/SKILL.md b/skills/quarkus-tdd/SKILL.md
index beae1952..7529b590 100644
--- a/skills/quarkus-tdd/SKILL.md
+++ b/skills/quarkus-tdd/SKILL.md
@@ -34,19 +34,19 @@ Follow this structured approach for comprehensive, readable tests:
@ExtendWith(MockitoExtension.class)
@DisplayName("OrderService Unit Tests")
class OrderServiceTest {
-
+
@Mock
private OrderRepository orderRepository;
-
+
@Mock
private EventService eventService;
-
+
@Mock
private FulfillmentPublisher fulfillmentPublisher;
-
+
@InjectMocks
private OrderService orderService;
-
+
private CreateOrderCommand validCommand;
@BeforeEach
@@ -60,16 +60,16 @@ class OrderServiceTest {
@Nested
@DisplayName("Tests for createOrder")
class CreateOrder {
-
+
@Test
@DisplayName("Should persist order and publish fulfillment event")
void givenValidCommand_whenCreateOrder_thenPersistsAndPublishes() {
// ARRANGE
doNothing().when(orderRepository).persist(any(Order.class));
-
+
// ACT
OrderReceipt receipt = orderService.createOrder(validCommand);
-
+
// ASSERT
assertThat(receipt).isNotNull();
assertThat(receipt.customerId()).isEqualTo("customer-123");
@@ -83,7 +83,7 @@ class OrderServiceTest {
void givenMissingCustomerId_whenCreateOrder_thenThrowsBadRequest() {
// ARRANGE
CreateOrderCommand invalid = new CreateOrderCommand("", validCommand.lines());
-
+
// ACT & ASSERT
WebApplicationException exception = assertThrows(
WebApplicationException.class,
@@ -101,13 +101,13 @@ class OrderServiceTest {
// ARRANGE
doThrow(new PersistenceException("database unavailable"))
.when(orderRepository).persist(any(Order.class));
-
+
// ACT & ASSERT
PersistenceException exception = assertThrows(
PersistenceException.class,
() -> orderService.createOrder(validCommand)
);
-
+
assertThat(exception.getMessage()).contains("database unavailable");
verify(eventService).createErrorEvent(
eq(validCommand),
@@ -125,7 +125,7 @@ class OrderServiceTest {
NullPointerException.class,
() -> orderService.createOrder(null)
);
-
+
verify(orderRepository, never()).persist(any(Order.class));
}
}
@@ -184,7 +184,7 @@ class BusinessRulesRouteTest {
// ARRANGE
MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);
mockRabbitMQ.expectedMessageCount(1);
-
+
// Replace real endpoint with mock for testing
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
@@ -192,13 +192,13 @@ class BusinessRulesRouteTest {
advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
-
+
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
-
+
// ASSERT — body is a JSON String after .marshal().json(JsonLibrary.Jackson)
mockRabbitMQ.assertIsSatisfied(5000);
-
+
assertThat(mockRabbitMQ.getExchanges()).hasSize(1);
String body = mockRabbitMQ.getExchanges().get(0).getIn().getBody(String.class);
assertThat(body).contains("\"documentId\":1");
@@ -211,19 +211,19 @@ class BusinessRulesRouteTest {
MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");
camelContext.addEndpoint("mock:marshal", mockMarshal);
mockMarshal.expectedMessageCount(1);
-
+
camelContext.getRouteController().stopRoute("business-rules-publisher");
AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {
advice.weaveAddLast().to("mock:marshal");
});
camelContext.getRouteController().startRoute("business-rules-publisher");
-
+
// ACT
producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
-
+
// ASSERT
mockMarshal.assertIsSatisfied(5000);
-
+
String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);
assertThat(body).contains("\"documentId\":1");
assertThat(body).contains("\"flowProfile\":\"BASIC\"");
@@ -240,17 +240,17 @@ class BusinessRulesRouteTest {
// ARRANGE
MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);
mockInvoice.expectedMessageCount(1);
-
+
camelContext.getRouteController().stopRoute("document-processing");
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");
});
camelContext.getRouteController().startRoute("document-processing");
-
+
// ACT
- producerTemplate.sendBodyAndHeader("direct:process-document",
+ producerTemplate.sendBodyAndHeader("direct:process-document",
testPayload, "documentType", "INVOICE");
-
+
// ASSERT
mockInvoice.assertIsSatisfied(5000);
}
@@ -261,23 +261,23 @@ class BusinessRulesRouteTest {
// ARRANGE
MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);
mockError.expectedMessageCount(1);
-
+
camelContext.getRouteController().stopRoute("document-processing");
AdviceWith.adviceWith(camelContext, "document-processing", advice -> {
advice.weaveByToString(".*direct:validation-error-handler.*")
.replace().to("mock:error");
});
camelContext.getRouteController().startRoute("document-processing");
-
+
// Mock validator bean to throw exception
when(documentValidator.validate(any())).thenThrow(new ValidationException("Invalid document"));
-
+
// ACT
producerTemplate.sendBody("direct:process-document", testPayload);
-
+
// ASSERT
mockError.assertIsSatisfied(5000);
-
+
Exception exception = mockError.getExchanges().get(0).getException();
assertThat(exception).isInstanceOf(ValidationException.class);
assertThat(exception.getMessage()).contains("Invalid document");
@@ -295,13 +295,13 @@ class EventServiceTest {
@Mock
private EventRepository eventRepository;
-
+
@Mock
private ObjectMapper objectMapper;
-
+
@InjectMocks
private EventService eventService;
-
+
private BusinessRulesPayload testPayload;
@BeforeEach
@@ -314,19 +314,19 @@ class EventServiceTest {
@Nested
@DisplayName("Tests for createSuccessEvent")
class CreateSuccessEvent {
-
+
@Test
@DisplayName("Should create success event with correct attributes")
void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {
// ARRANGE
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
-
+
// ACT
- assertDoesNotThrow(() ->
+ assertDoesNotThrow(() ->
eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));
-
+
// ASSERT
- verify(eventRepository).persist(argThat(event ->
+ verify(eventRepository).persist(argThat(event ->
event.getType().equals("DOCUMENT_PROCESSED") &&
event.getStatus() == EventStatus.SUCCESS &&
event.getPayload().equals("{\"documentId\":1}") &&
@@ -339,13 +339,13 @@ class EventServiceTest {
void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {
// ARRANGE
Object nullPayload = null;
-
+
// ACT & ASSERT
NullPointerException exception = assertThrows(
NullPointerException.class,
() -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")
);
-
+
assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");
verify(eventRepository, never()).persist(any());
}
@@ -354,20 +354,20 @@ class EventServiceTest {
@Nested
@DisplayName("Tests for createErrorEvent")
class CreateErrorEvent {
-
+
@Test
@DisplayName("Should create error event with error message")
void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {
// ARRANGE
String errorMessage = "Processing failed";
when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
-
+
// ACT
- assertDoesNotThrow(() ->
+ assertDoesNotThrow(() ->
eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
-
+
// ASSERT
- verify(eventRepository).persist(argThat(event ->
+ verify(eventRepository).persist(argThat(event ->
event.getType().equals("PROCESSING_ERROR") &&
event.getStatus() == EventStatus.ERROR &&
event.getErrorMessage().equals(errorMessage) &&
@@ -384,7 +384,7 @@ class EventServiceTest {
IllegalArgumentException.class,
() -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)
);
-
+
assertThat(exception.getMessage()).contains("Error message cannot be blank");
}
}
@@ -400,13 +400,13 @@ class FileStorageServiceTest {
@Mock
private S3Client s3Client;
-
+
@Mock
private ExecutorService executorService;
-
+
@InjectMocks
private FileStorageService fileStorageService;
-
+
private InputStream testInputStream;
private LogContext testLogContext;
@@ -421,7 +421,7 @@ class FileStorageServiceTest {
@Nested
@DisplayName("Tests for uploadOriginalFile")
class UploadOriginalFile {
-
+
@Test
@DisplayName("Should successfully upload file and return document info")
void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {
@@ -430,23 +430,23 @@ class FileStorageServiceTest {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(executorService).execute(any(Runnable.class));
-
+
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
.thenReturn(PutObjectResponse.builder().build());
-
+
// ACT
- CompletableFuture future =
- fileStorageService.uploadOriginalFile(testInputStream, 1024L,
+ CompletableFuture future =
+ fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL);
-
+
StoredDocumentInfo result = future.join();
-
+
// ASSERT
assertThat(result).isNotNull();
assertThat(result.getPath()).isNotBlank();
assertThat(result.getSize()).isEqualTo(1024L);
assertThat(result.getUploadedAt()).isNotNull();
-
+
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
}
@@ -458,15 +458,15 @@ class FileStorageServiceTest {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(executorService).execute(any(Runnable.class));
-
+
when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
.thenThrow(new StorageException("S3 unavailable"));
-
+
// ACT
- CompletableFuture future =
- fileStorageService.uploadOriginalFile(testInputStream, 1024L,
+ CompletableFuture future =
+ fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL);
-
+
// ASSERT
assertThatThrownBy(() -> future.join())
.isInstanceOf(CompletionException.class)
@@ -479,17 +479,17 @@ class FileStorageServiceTest {
void givenLogContext_whenUpload_thenContextPropagated() throws Exception {
// ARRANGE
AtomicReference capturedContext = new AtomicReference<>();
-
+
doAnswer(invocation -> {
capturedContext.set(CustomLog.getCurrentContext());
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(executorService).execute(any(Runnable.class));
-
+
// ACT
- fileStorageService.uploadOriginalFile(testInputStream, 1024L,
+ fileStorageService.uploadOriginalFile(testInputStream, 1024L,
testLogContext, InvoiceFormat.UBL).join();
-
+
// ASSERT
assertThat(capturedContext.get()).isNotNull();
assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");
@@ -641,7 +641,7 @@ class DocumentIntegrationTest {
prepare-agent
-
+
report
@@ -650,7 +650,7 @@ class DocumentIntegrationTest {
report
-
+
check
@@ -705,14 +705,14 @@ mvn jacoco:check
quarkus-junit5-mockito
test
-
+
org.mockito
mockito-core
test
-
+
org.assertj
@@ -720,14 +720,14 @@ mvn jacoco:check
3.24.2
test
-
+
io.rest-assured
rest-assured
test
-
+
org.apache.camel.quarkus
diff --git a/skills/quarkus-verification/SKILL.md b/skills/quarkus-verification/SKILL.md
index 54748e99..7b0194c9 100644
--- a/skills/quarkus-verification/SKILL.md
+++ b/skills/quarkus-verification/SKILL.md
@@ -437,28 +437,28 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
-
+
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
-
+
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
-
+
- name: Build
run: mvn clean verify -DskipTests
-
+
- name: Test with Coverage
run: mvn test jacoco:report jacoco:check
-
+
- name: Security Scan
run: mvn org.owasp:dependency-check-maven:check
-
+
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
diff --git a/tests/ci/supply-chain-watch-workflow.test.js b/tests/ci/supply-chain-watch-workflow.test.js
index b1388137..fa8198ae 100644
--- a/tests/ci/supply-chain-watch-workflow.test.js
+++ b/tests/ci/supply-chain-watch-workflow.test.js
@@ -43,7 +43,7 @@ function run() {
if (test('uses read-only permissions and non-persisting checkout credentials', () => {
assert.match(source, /permissions:\r?\n\s+contents: read/);
assert.doesNotMatch(source, /^\s+[A-Za-z-]+:\s*write\b/m);
- assert.match(source, /uses: actions\/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd/);
+ assert.match(source, /uses: actions\/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10/);
assert.match(source, /persist-credentials: false/);
assert.doesNotMatch(source, /id-token:\s*write/);
assert.doesNotMatch(source, /actions\/cache@/);