Developing standalone bash/python validation runners executed by Host Agents (GitHub Copilot, Cursor, Claude Code)
Where are we? ใน Phase 1 (Day 1-4) เราได้สร้าง Frontmatter Metadata, Semantic Router Description, Negative Boundaries (RFC 2119), และ Few-shot Exemplars ภายใน SKILL.md สำเร็จ ทำให้ LLM เข้าใจขอบเขตของภาษาและโครงสร้างโค้ดโดยสมบูรณ์
What is missing? แม้ LLM จะมี In-context Constraints แต่ตัวมันคือ Probabilistic Engine ซึ่งไม่สามารถรัน Compiler, ไม่สามารถตรวจ Abstract Syntax Tree จริง, และไม่สามารถการันตีความถูกต้อง 100% ได้ด้วยตัวอักษรเพียงอย่างเดียว การพึ่งพา "Prompting เพื่อตรวจโค้ด" คือช่องโหว่ความน่าเชื่อถือ วันนี้เราจึงก้าวเข้าสู่ Phase 2 โดยสร้าง Execution Layer ผ่านไดเรกทอรี scripts/ ซึ่งประกอบด้วย Deterministic Verification Runners ที่ Host Agent สามารถเรียกทำงานผ่าน CLI สั่ง Compile และตรวจสอบโค้ดก่อนส่งมอบ
.github/skills/spring-boot-unit-tester/
├── SKILL.md <- Metadata, Constraints, Exemplars (Phase 1)
├── scripts/ <- [TODAY'S INTEGRATION: DETERMINISTIC TOOLING]
│ ├── run-unit-tests.sh <- Host CLI runner: maven verification & mock check
│ └── verify-spring-context-leak.py <- Fast AST/Token linter to halt heavy context boot
├── references/ <- DTO schemas & contracts (Phase 3)
└── assets/ <- Diagrams & templates
[Host Agent Lifecycle Flow]
User Request ──> Semantic Match (SKILL.md) ──> LLM Generates Test
▲ │
│ [Fail: Non-Zero Exit] ▼
└────── Stderr Feedback Pipeline ◄────── CLI: scripts/run-unit-tests.sh
│
▼ [Pass: Exit 0]
Return Code to User
ตามมาตรฐาน agentskills Standard สคริปต์ในไดเรกทอรี scripts/ ไม่ใช่เพียง helper สุ่มๆ ในโปรเจกต์ แต่คือ Deterministic Verifiers ที่ทำหน้าที่เป็น Pre-commit Gate ให้กับ Host Agent การออกแบบสคริปต์ระดับ Enterprise จะต้องปฏิบัติตามสัญญาการทำงาน (Execution Contracts) ที่เคร่งครัด
scripts/ ก็คือ Hardware Watchdog Timer ที่ไม่สนใจข้ออ้างหรือคำอธิบาย หากโปรแกรมละเมิด Invariant หรือ Compile ไม่ผ่าน สคริปต์จะตัดวงจร (Exit code != 0) และโยน Stderr กลับเข้าไปใน Context ทันที
/bin/bash หรือ /bin/sh) ห้ามพึ่งพา alias พิเศษ เครื่องมือ CLI ที่ Agent สั่งรันต้องรองรับการรันแบบ Non-Interactive Mode (เช่น mvn -B --no-transfer-progress)| มิติการประเมิน | In-Context Prompting (LLM Self-Check) | Deterministic Scripts (scripts/) |
|---|---|---|
| Deterministic Guarantee | ต่ำ (Probabilistic: มีโอกาสตอบว่าถูกทั้งที่ syntax ผิด) | 100% (Binary: Compile ผ่านหรือไม่ผ่าน) |
| Token Consumption | สูงมาก (ต้องป้อนโค้ดกลับเข้าไปถามซ้ำ สิ้นเปลือง Context Window) | ต่ำมาก (กิน Token เฉพาะ stdout/stderr ที่ส่งกลับมา) |
| Execution Speed | ช้า (ขึ้นกับ Token generation rate ของโมเดล) | เร็วมาก (Native process runtime: Bash/Native AST) |
| Feedback Accuracy | คลุมเครือ ("I think this looks correct...") | ชัดเจน (Stack Trace, Line Number, Compilation Error) |
.github/skills/spring-boot-unit-tester/SKILL.mdอัปเดต SKILL.md เพื่อแจ้ง Host Agent ว่ามี Deterministic Tool ให้เรียกใช้งาน
---
name: spring-boot-unit-tester
description: Generates isolated Spring Boot unit tests using Kotlin, MockK, and AssertJ. MUST be used for unit testing service layers.
version: 1.2.0
tools:
- name: run-unit-tests
description: Executes local project verification script to validate test compilation, MockK conventions, and isolation.
file: scripts/run-unit-tests.sh
---
## Tool Execution Rules
1. After generating or refactoring any unit test, the Host Agent MUST execute the validation script:
`bash .github/skills/spring-boot-unit-tester/scripts/run-unit-tests.sh `
2. If the script exits with status code != 0, inspect stderr, fix the violations, and re-run until it passes.
3. NEVER return a test implementation to the user without a successful (exit code 0) verification pass.
.github/skills/spring-boot-unit-tester/scripts/run-unit-tests.sh#!/usr/bin/env bash
# ==============================================================================
# Script: run-unit-tests.sh
# Standard: agentskills Standard v1.0 (Phase 2 Deterministic Tooling)
# Purpose: Validates MockK syntax, checks context pollution, runs Maven test.
# Exit Codes:
# 0: Success - All checks passed
# 1: Missing/Invalid Input Arguments
# 2: Architectural Constraint Violation (AST/Regex Check)
# 3: Build/Compilation Failure (Maven)
# ==============================================================================
set -euo pipefail
# 1. Parameter Validation
TARGET_FILE="${1:-}"
if [[ -z "${TARGET_FILE}" ]]; then
echo "[ERROR] Missing target test file argument." >&2
echo "Usage: $0 <path/to/TargetTest.kt>" >&2
exit 1
fi
if [[ ! -f "${TARGET_FILE}" ]]; then
echo "[ERROR] Target file '${TARGET_FILE}' does not exist." >&2
exit 1
fi
echo "[INFO] Commencing deterministic inspection for: ${TARGET_FILE}"
# 2. Fast Static Invariant Linter (Negative Constraints)
# // [TODAY'S LESSON PLUGS IN HERE - SHELL VALIDATION GATES]
VIOLATIONS_FOUND=0
# Rule A: Check for forbidden Mockito imports
if grep -En "org\.mockito" "${TARGET_FILE}" >&2; then
echo "[VIOLATION] Forbidden Mockito import detected. Use io.mockk instead." >&2
VIOLATIONS_FOUND=$((VIOLATIONS_FOUND + 1))
fi
# Rule B: Check for forbidden Spring Boot Context Loading in pure Unit Tests
if grep -En "@SpringBootTest|@DataJpaTest|@WebMvcTest" "${TARGET_FILE}" >&2; then
echo "[VIOLATION] Spring ApplicationContext runner detected. Pure unit tests MUST NOT use Spring test runners." >&2
VIOLATIONS_FOUND=$((VIOLATIONS_FOUND + 1))
fi
# Rule C: Enforce confirmVerified on MockK dependencies
if ! grep -q "confirmVerified(" "${TARGET_FILE}"; then
echo "[WARNING] No 'confirmVerified(...)' statement found. Host should verify all mocks." >&2
fi
if [[ ${VIOLATIONS_FOUND} -gt 0 ]]; then
echo "[FAILED] Static validation gates failed with ${VIOLATIONS_FOUND} critical violation(s)." >&2
exit 2
fi
echo "[SUCCESS] Static architectural invariant checks passed."
# 3. Dynamic Compilation & Targeted Test Execution
# Extract Class Name for Maven targeted runner
CLASS_NAME=$(basename "${TARGET_FILE}" .kt)
echo "[INFO] Running Maven targeted compilation for class: ${CLASS_NAME}"
# Execute Maven in non-interactive batch mode, suppressing progress noise
if ! mvn test -B --no-transfer-progress -Dtest="${CLASS_NAME}" > /tmp/mvn_test_output.log 2>&1; then
echo "[FAILED] Maven compilation or test execution failed." >&2
echo "---------------- Maven Output Diagnostics ----------------" >&2
# Filter error messages to conserve agent context window
grep -E "ERROR|FAILURE|Compilation failure|\[ERROR\].*:[0-9]+" /tmp/mvn_test_output.log >&2 || cat /tmp/mvn_test_output.log | tail -n 30 >&2
exit 3
fi
echo "[PASSED] Test compiled and passed successfully under standard Maven build."
exit 0
OrderSettlementService.kt แต่ Agent เผลอใส่ @SpringBootTest และนำเข้า org.mockito.Mockito.when ติดมาด้วย ส่งผลให้การรัน CI/CD ช้าลง 20 เท่า และเกิด Flaky Test ใน Pipeline
OrderSettlementServiceTest.kt ที่จำลองข้อผิดพลาดดังกล่าวscripts/run-unit-tests.sh เพื่อยืนยันว่าสคริปต์สามารถ Reject โค้ดที่ผิดพลาดด้วย Exit Code 2 ได้จริงsrc/test/kotlin/OrderSettlementServiceTest.kt)package com.enterprise.trading.service
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest // VIOLATION
import org.mockito.Mockito // VIOLATION
@SpringBootTest
class OrderSettlementServiceTest {
@Test
fun `should settle order successfully`() {
val mockRepo = Mockito.mock(OrderRepository::class.java)
// ...
}
}
$ bash .github/skills/spring-boot-unit-tester/scripts/run-unit-tests.sh src/test/kotlin/OrderSettlementServiceTest.kt
[INFO] Commencing deterministic inspection for: src/test/kotlin/OrderSettlementServiceTest.kt
4:import org.mockito.Mockito // VIOLATION
[VIOLATION] Forbidden Mockito import detected. Use io.mockk instead.
3:@SpringBootTest
[VIOLATION] Spring ApplicationContext runner detected. Pure unit tests MUST NOT use Spring test runners.
[FAILED] Static validation gates failed with 2 critical violation(s).
$echo$?
2
package com.enterprise.trading.service
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.mockk.confirmVerified
import org.junit.jupiter.api.Test
import org.assertj.core.api.Assertions.assertThat
class OrderSettlementServiceTest {
private val repository = mockk<OrderRepository>()
private val service = OrderSettlementService(repository)
@Test
fun `should settle order successfully`() {
// Given
val orderId = "ORD-9921"
every { repository.updateStatus(orderId, "SETTLED") } returns true
// When
val result = service.settle(orderId)
// Then
assertThat(result).isTrue()
verify(exactly = 1) { repository.updateStatus(orderId, "SETTLED") }
confirmVerified(repository)
}
}
ทดสอบความเข้าใจเชิงสถาปัตยกรรมด้าน Deterministic Tooling และ CLI Execution สัญญาณตรวจคำตอบจะทำงานทันทีที่ท่านเลือกตัวเลือก