← Phase 3
D9
Standard: agentskills/agentskills

Day 9: Dynamic Exemplar Mining

12-Day Series
🗺️ SECTION 1 / SYSTEM BLUEPRINT & INTEGRATION

The "Where & Why": วิศวกรรม Dynamic Exemplar Mining และการขุดค้น Best Practices สดจาก Repo

🎯 Mental Map (148 Words): ใน Day 4 เราฝัง Static PRS Few-Shot Exemplars ใน SKILL.md และใน Day 8 เราติดตั้งเลเยอร์ references/ เพื่อเก็บ Schemas ขนาดใหญ่

ปัญหาคอขวดที่พบในโปรเจกต์จริง: Static Few-Shot ใน Prompt เกิดอาการ Exemplar Rot อย่างรวดเร็วเมื่อทีมปรับแต่ง Custom Test Helpers, In-house Assertions, หรือ Domain Fixture Builders การ Hardcode โค้ดตัวอย่างไว้ใน Skill จึงไม่สอดคล้องกับสไตล์โค้ดปัจจุบันของ Repo
เป้าหมายของ Day 9: เราจะติดตั้งเครื่องมือ Dynamic Exemplar Mining ใน scripts/harvest-exemplars.py ซึ่งทำหน้าที่ค้นหาและคัดกรอง "Golden Unit Test" ที่คอมไพล์ผ่านจริงในโปรเจกต์ปัจจุบัน มาแปลงเป็น PRS Context Slice ขนาดไม่เกิน 250 Tokens เพื่อป้อนให้ Host Agent ลอกเลียนแบบสไตล์โค้ดจริงของทีมแบบ On-the-Fly
Dynamic Exemplar Mining & Context Injection Pipeline Mining Protocol v3.0
┌────────────────────────────────────────────────────────────────────────┐
│ HOST AGENT RECEIVES SCAFFOLDING REQUEST                                │
│ Target: src/main/kotlin/.../SettlementReconciliationService.kt         │
└───────────────────────────────────┬────────────────────────────────────┘
                                    │
                                    ▼ (Pre-flight Tool Call: Dynamic Harvesting)
┌────────────────────────────────────────────────────────────────────────┐
│ 🎯 SCRIPTS/HARVEST-EXEMPLARS.PY [TODAY'S ARTIFACT]                     │
│ 1. Scans existing test suites in src/test/kotlin/                      │
│ 2. Applies Heuristic Quality Scoring:                                  │
│    + MockK syntax verified       + AAA structure present               │
│    - Mockito detected (PRUNED)   - SpringRunner detected (PRUNED)      │
│ 3. Selects Top-1 Golden Test Method in sibling package                 │
│ 4. Slices out method body & creates dense PRS Exemplar Card (< 250 Tok)│
└───────────────────────────────────┬────────────────────────────────────┘
                                    │
                                    ▼ (Dynamic In-Context Injection)
┌────────────────────────────────────────────────────────────────────────┐
│ HOST AGENT INGESTS FRESH REPO-NATIVE EXEMPLAR                          │
│ Employs the project's exact custom builders, naming, and assertions!   │
│ Zero Prompt Rot • 100% Repository-Idiomatic Alignment                  │
└────────────────────────────────────────────────────────────────────────┘
        
PILLAR 01: ZERO PROMPT ROT

Live Repo Alignment

ไม่จำเป็นต้องคอยอัปเดตตัวอย่างโค้ดใน SKILL.md ตัวสคริปต์จะดึงโค้ดสดที่ผ่านการยอมรับจากเพื่อนร่วมทีมมาเป็นแม่แบบเสมอ

PILLAR 02: INVARIANT-GATED MINING

Anti-Drift Quality Gates

ป้องกันปัญหา Garbage In, Garbage Out ด้วยการคัดกรองไฟล์ที่มี Mockito หรือ @SpringBootTest ทิ้งก่อนนำมาเป็นตัวอย่าง

PILLAR 03: SURGICAL SLICING

< 250 Token Economy

ไม่ส่งไฟล์ทดสอบความยาว 500 บรรทัดทั้งไฟล์ แต่สกัดเฉพาะเมธอดตัวอย่างที่ดีที่สุด 1 เมธอดพร้อม Setup Statement เท่านั้น

⚙️ SECTION 2 / ARCHITECTURAL DEEP DIVE

วิศวกรรม Dynamic Exemplar Mining: กฎเกณฑ์การให้คะแนน (Heuristic Scoring) และการสกัดแบบแม่นยำ

ในโลกของ LLM In-Context Learning การให้ตัวอย่างโค้ดที่เป็น Generic Example จากโลกภายนอกมักทำให้เกิดปัญหาเรื่อง "Style Incompatibility" เช่น AI เขียน assertion ด้วยไวยากรณ์มาตรฐาน แต่โปรเจกต์ของบริษัทมี Custom Extension Functions หรือ Base Test Classes ประจำทีม การสกัดตัวอย่างสดจากโค้ดจริงจึงเป็นสะพานเชื่อมที่ทำให้ Agent กลมกลืนไปกับวัฒนธรรมวิศวกรรมขององค์กร

มิติสถาปัตยกรรม (Architectural Dimension) Hardcoded Static Exemplars (Day 4) Raw Repository Grep (ค้นหาคำสุ่ม) Dynamic Exemplar Mining (Day 9)
Repository Idiom Alignment ต่ำ: โค้ดตัวอย่างมักใช้ Standard Lib ไม่รู้จัก Custom DSL ของทีม ปานกลาง: ได้โค้ดในโปรเจกต์ แต่อาจเป็นโค้ดเก่าที่เขียนแย่ สูงสุด: ดึงเฉพาะโค้ดที่มี Quality Score สูงสุดใน Repo
Risk of Legacy Contamination ไม่มีความเสี่ยง: ผ่านการตรวจโดยผู้เขียน Skill วิกฤต: เผลอดึงไฟล์เทสต์ยุค Java 8 / Mockito มาสอน Agent เป็นศูนย์: มี Invariant Gates คัดกรอง Anti-patterns ทิ้ง 100%
Maintenance Effort สูง: เมื่อ Framework เปลี่ยนเวอร์ชัน ต้องตามแก้ SKILL.md ไม่มีการดูแลระบบ Zero-Maintenance: โค้ดตัวอย่างพัฒนาตามการ Refactor ของทีม
Context Token Cost คงที่: กิน Token 600-800 ตัวใน SKILL.md ตลอดเวลา บวมมหาศาล: เสียรอบอ่านไฟล์ทั้งก้อน 2,000+ Tokens ต่ำมาก: โหลดแบบ On-demand เฉพาะเมธอดเดี่ยว (< 250 Tokens)
Heuristic Quality Scoring Formula

สคริปต์ขุดค้นไม่ได้หยิบไฟล์แรกที่เจอ แต่คำนวณ Exemplar Quality Score ($Q$) เพื่อเลือกเฉพาะชุดทดสอบระดับ Masterpiece:

Q = (AAA_Comments * 20) + (MockK_Verify * 25) + (AssertJ * 20) - (Mockito_Found * 1000) - (SpringRunner * 1000)

หากไฟล์ใดมีคะแนนติดลบ (จากการละเมิด Negative Boundaries ใน Day 3) สคริปต์จะตัดทิ้งทันที โดยจะเลือกเฉพาะไฟล์ที่ได้คะแนนสูงสุดและตั้งอยู่ใน Package เดียวกันหรือใกล้เคียงกับคลาสเป้าหมายมากที่สุด

The Surgical Method Extractor

การสกัดโค้ดตัวอย่างต้องปฏิบัติตามกฎ Surgical Extraction Contract:

  • 1. Dependency Declaration Slice: สกัดเฉพาะบรรทัดประกาศ val mock: Dependency = mockk() และ constructor injection ของคลาสนั้น
  • 2. Single Canonical Test Slice: สกัดเฉพาะเมธอดเทสต์ตัวแรกที่มีโครงสร้าง AAA ครบถ้วน ไม่นำเมธอดเทสต์อื่นอีก 15 ตัวมาด้วย
  • 3. PRS Wrapping: แปลงโค้ดที่สกัดได้ให้กลายเป็น Markdown บล็อก ### Pattern / Reasoning / Solution ทันทีก่อนส่งให้ Agent
🧩 SECTION 3 / END-TO-END CODE BLUEPRINT

Production Artifact: Dynamic Exemplar Mining Engine

ชุดเครื่องมือตามมาตรฐาน agentskills/agentskills ที่ประกอบด้วยคำสั่งใน SKILL.md, สคริปต์ไพธอนขุดค้นตัวอย่างสด (Zero-Dependency), และตัวห่อหุ้มคำสั่งสำหรับ Agent

Skill Directory Placement
.github/skills/spring-boot-unit-tester/
├── SKILL.md                          ◄── [UPDATED: Added Pre-flight Dynamic Mining Protocol]
├── scripts/
│   ├── run-unit-tests.sh             ◄── Day 5-6: Sandboxed Execution & Triage Runner
│   ├── extract-target-ast.py         ◄── Day 7: AST Class Skeleton Inspector
│   ├── query-reference.sh            ◄── Day 8: Targeted Schema Slicer
│   └── harvest-exemplars.py          ◄── [TODAY'S ARTIFACT: Local Test Exemplar Miner]
└── references/
    └── billing-domain-contracts.json ◄── Day 8: Canonical DTO Specifications
        
FILE: .github/skills/spring-boot-unit-tester/SKILL.md Updated Three-Phase Protocol
---
name: spring-boot-unit-tester
schema-version: "1.0.0"
description: >
  Generates deterministic, isolated Kotlin unit tests for Spring Boot 3.x 
  using MockK and AssertJ. MUST NOT load Spring ApplicationContext.
tools:
  - name: inspect-class-context
    description: Extracts concise AST skeleton from target source file.
    file: scripts/extract-target-ast.py
  - name: harvest-exemplars
    description: Dynamically harvests idiomatic golden unit test exemplars from the local repository.
    file: scripts/harvest-exemplars.py
  - name: query-reference
    description: Queries on-demand domain DTO schemas and fixtures from references/ directory.
    file: scripts/query-reference.sh
  - name: run-unit-tests
    description: Runs sandboxed test execution with automatic stderr triage.
    file: scripts/run-unit-tests.sh
metadata:
  domain: "backend-quality"
  target-framework: "Spring Boot 3.x / Kotlin 1.9+"
---

# Spring Boot Unit Test Architect Specification

## 1. Architectural Invariants
1. Follow AAA pattern with isolated JVM memory execution (< 50ms per method).
2. MUST NOT use `@SpringBootTest`, `@DataJpaTest`, or `org.mockito.*`.
3. Wrap all `suspend` methods inside `runTest` using `coEvery { ... }`.


## 2. Pre-flight Context Harvesting Protocol
Before authoring any new unit test, the Host Agent MUST execute this sequential pre-flight:
1. **Target AST Skeleton**: Run `python3 scripts/extract-target-ast.py <TARGET_FILE>` to harvest constructor dependencies.
2. **Dynamic Exemplar Mining**: Run `python3 scripts/harvest-exemplars.py <TARGET_FILE>` to mine existing golden test patterns in the repo.
   - Adhere to the mined exemplar's exact assertion styling, mock naming, and builder patterns.
3. **Domain References (Conditional)**: If the target method interacts with complex billing DTOs, query `query-reference.sh`.
          
FILE: scripts/harvest-exemplars.py Zero-Dependency Python 3
#!/usr/bin/env python3
"""
scripts/harvest-exemplars.py
Standard: agentskills Standard v1.0 (Phase 3 Reference Architecture)
Scans existing test suites in the repository, filters out anti-patterns,
and emits a concise (< 250 tokens) PRS exemplar card of idiomatic repository code.
"""
import os
import sys
import re
from pathlib import Path

# // [TODAY'S LESSON PLUGS IN HERE: HEURISTIC QUALITY SCORING & SLICING]
def score_test_content(content: str) -> int:
    """Calculates canonical quality score. Disqualifies anti-patterns immediately."""
    # Negative Invariant Gate: Disqualify immediately if legacy Mockito or Spring runner present
    if re.search(r"org\.mockito|@SpringBootTest|@DataJpaTest|@ExtendWith\(SpringExtension", content):
        return -9999

    score = 0
    if "io.mockk" in content: score += 40
    if "confirmVerified" in content: score += 20
    if "runTest" in content: score += 15
    if "// Arrange" in content and "// Act" in content and "// Assert" in content: score += 30
    if "assertThat(" in content: score += 20
    return score

def extract_surgical_slice(file_path: Path) -> str:
    """Extracts mock dependencies and the first canonical test method from the candidate file."""
    try:
        text = file_path.read_text(encoding="utf-8", errors="ignore")
    except Exception:
        return ""

    lines = text.splitlines()
    mock_declarations = []
    test_method_lines = []
    inside_test = False
    brace_count = 0
    test_captured = False

    for line in lines:
        stripped = line.strip()
        
        # 1. Capture Mock declarations: private val foo = mockk<Foo>()
        if ("mockk<" in stripped or "mockk(" in stripped) and ("val " in stripped or "var " in stripped):
            if len(mock_declarations) < 3:
                mock_declarations.append(stripped)

        # 2. Capture First Canonical @Test Method
        if not test_captured:
            if "@Test" in stripped:
                inside_test = True
                test_method_lines.append(line)
                brace_count += line.count("{") - line.count("}")
                continue

            if inside_test:
                test_method_lines.append(line)
                brace_count += line.count("{") - line.count("}")
                if brace_count <= 0 and len(test_method_lines) > 2:
                    inside_test = False
                    test_captured = True

    if not test_method_lines:
        return ""

    # Format dense PRS card
    card = [
        f"### 💎 DYNAMIC REPO EXEMPLAR: `{file_path.name}`",
        "#### [P] Target Sibling Pattern:",
        f"Mined from existing codebase at `{file_path}`.",
        "#### [R] Idiomatic Conventions Observed in Project:",
        "- Dependencies instantiated via constructor MockK.",
        "- AAA structure followed with explicit assertion assertions.",
        "#### [S] Canonical Repository Code Snippet:",
        "```kotlin"
    ]
    if mock_declarations:
        card.extend(mock_declarations)
        card.append("")
    card.extend(test_method_lines[:25]) # Truncate method to prevent context flooding
    card.append("```")
    return "\n".join(card)

def harvest_exemplar(target_path_str: str, search_root: str = "src/test/kotlin") -> str:
    root = Path(search_root)
    if not root.exists():
        root = Path(".")

    target_name = Path(target_path_str).stem
    candidates = []

    # Traverse existing tests
    for p in root.rglob("*Test.kt"):
        try:
            content = p.read_text(encoding="utf-8", errors="ignore")
            score = score_test_content(content)
            if score > 50: # Only high-scoring canonical candidates
                candidates.append((score, p))
        except Exception:
            continue

    if not candidates:
        return "### ℹ️ NO LOCAL EXEMPLAR MINED (Follow baseline SKILL.md standards)"

    # Sort by quality score descending
    candidates.sort(key=lambda x: x[0], reverse=True)
    best_candidate = candidates[0][1]

    return extract_surgical_slice(best_candidate)

def main():
    target_file = sys.argv[1] if len(sys.argv) > 1 else "UnknownService.kt"
    slice_card = harvest_exemplar(target_file)
    sys.stdout.write(slice_card + "\n")

if __name__ == "__main__":
    main()
          
🛠️ SECTION 4 / HANDS-ON SCENARIO & LAB

Enterprise Incident: Custom Domain Assertion Hallucination vs. Repo Idioms

สถานการณ์จริง: ภายใน Microservice ด้านการเงิน ทีมสถาปัตยกรรมได้สร้าง Custom AssertJ Extension ชื่อ assertThatSettlement(result).isFullyReconciled() และกำหนดกฎว่า ห้ามใช้ assertThat(result.status).isEqualTo("RECONCILED") ตรงๆ แต่เมื่อ Host Agent เข้ามาทำงาน ตัวโมเดลไม่เคยเห็น Extension นี้ใน Training Data หรือใน SKILL.md ทั่วไป จึงสร้างโค้ดแบบดั้งเดิมที่ทำให้ Pull Request โดนปฏิเสธจาก CI Linter ประจำทีม

⚠️ LAB MISSION OBJECTIVES
  1. จำลองชุดทดสอบเดิมในโปรเจกต์ PaymentLedgerServiceTest.kt ที่ใช้ Custom Assertion และ MockK อย่างถูกต้อง
  2. รันสคริปต์ harvest-exemplars.py เพื่อพิสูจน์ว่าระบบสามารถสแกน คัดเลือก และสกัดตัวอย่างโค้ดที่มี Custom Assertion ออกมาเป็น PRS Card ได้อย่างแม่นยำ
  3. ยืนยันว่าสคริปต์ปฏิเสธไฟล์ที่มี Mockito ปนเปื้อน และผลิต Exemplar Card ขนาด < 200 Tokens สำหรับป้อนให้ Agent ทันที
📄 EXISTING SIBLING TEST (450 Lines / 18 KB)
package com.enterprise.trading.ledger

import com.enterprise.testing.assertions.assertThatSettlement
import io.mockk.*

class PaymentLedgerServiceTest {
    private val ledgerRepo: LedgerRepository = mockk()
    // ... 40 lines of mocks and fixtures ...

    @Test
    fun `should reconcile batch entries`() = runTest {
        // Arrange
        val batchId = UUID.randomUUID()
        coEvery { ledgerRepo.find(batchId) } returns sampleBatch()
        
        // Act
        val result = service.reconcile(batchId)
        
        // Assert: Uses Team's In-house Custom DSL!
        assertThatSettlement(result).isFullyReconciled()
        confirmVerified(ledgerRepo)
    }
    // ... 15 other test methods ...
}
          
💎 SURGICALLY HARVESTED PRS CARD (< 140 tokens)
### 💎 DYNAMIC REPO EXEMPLAR: `PaymentLedgerServiceTest.kt`
#### [P] Target Sibling Pattern:
Mined from existing codebase at `src/test/kotlin/.../PaymentLedgerServiceTest.kt`.
#### [R] Idiomatic Conventions Observed in Project:
- Dependencies instantiated via constructor MockK.
- AAA structure followed with explicit assertion assertions.
#### [S] Canonical Repository Code Snippet:
```kotlin
private val ledgerRepo: LedgerRepository = mockk()

@Test
fun `should reconcile batch entries`() = runTest {
    // Arrange
    val batchId = UUID.randomUUID()
    coEvery { ledgerRepo.find(batchId) } returns sampleBatch()
    
    // Act
    val result = service.reconcile(batchId)
    
    // Assert
    assertThatSettlement(result).isFullyReconciled()
    confirmVerified(ledgerRepo)
}
```
          
TERMINAL PROOF: Dynamic Exemplar Mining Execution Python CLI Output
$ python3 .github/skills/spring-boot-unit-tester/scripts/harvest-exemplars.py src/main/kotlin/com/enterprise/trading/SettlementReconciliationService.kt
### 💎 DYNAMIC REPO EXEMPLAR: `PaymentLedgerServiceTest.kt`
#### [P] Target Sibling Pattern:
Mined from existing codebase at `src/test/kotlin/com/enterprise/trading/ledger/PaymentLedgerServiceTest.kt`.
#### [R] Idiomatic Conventions Observed in Project:
- Dependencies instantiated via constructor MockK.
- AAA structure followed with explicit assertion assertions.
#### [S] Canonical Repository Code Snippet:
```kotlin
private val ledgerRepo: LedgerRepository = mockk()

@Test
fun `should reconcile batch entries`() = runTest {
    // Arrange
    val batchId = UUID.randomUUID()
    coEvery { ledgerRepo.find(batchId) } returns sampleBatch()
    // Act
    val result = service.reconcile(batchId)
    // Assert
    assertThatSettlement(result).isFullyReconciled()
    confirmVerified(ledgerRepo)
}
```

$ echo $?
0
        
🎯 SECTION 5 / INTERACTIVE ARCHITECTURE & VERIFICATION QUIZ

ทดสอบสถาปัตยกรรม Dynamic Exemplar Mining (Day 9)

Current Score
0 / 12

ตอบคำถามเชิงสถาปัตยกรรมทั้ง 12 ข้อด้านล่าง ระบบจะตรวจคำตอบและให้ Feedback ทันทีเมื่อคลิกเลือกช้อยส์ พร้อมแสดงคำอธิบายเชิงลึก