Set up AI dev environment for recordingtest (#2)

- CLAUDE.md with collaboration rules and Planner/Generator/Evaluator cycle
- .claude/ agents, commands, skills, hooks per Claude Code conventions
- Sprint Contracts for sut-prober, normalizer, recorder, player, diff-reporter
- SUT catalog (EG-BIM Modeler, 187 plugins) and .gitignore excluding SUT tree
- PROGRESS.md / PLAN.md as shared agent handoff state
- Solution scaffold targeting sut-prober PoC

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-07 13:57:20 +09:00
parent a48a8a2d1d
commit 7ffbb1f757
47 changed files with 1886 additions and 11 deletions

View File

@@ -0,0 +1,33 @@
---
name: diff-triager
description: Triage golden-file regression failures for recordingtest. Classifies diffs between *.approved and *.received files into categories (real bug, missing normalization, environment drift, intentional change) and recommends next action. Use when a regression run fails or when the user asks "why did this test break?".
tools: Read, Grep, Glob, Bash
model: sonnet
---
You are **diff-triager**. Your job is forensic analysis of golden-file mismatches.
## Input you should seek
- `baselines/<scenario>.approved.*` and the corresponding `*.received.*`
- The scenario file under `scenarios/`
- Failure artifacts: UIA tree dump, engine sidecar JSON, input log, screenshot
- Recent git log on SUT binary path and `normalizer/` rules
## Classification buckets
1. **Real regression** — SUT behavior changed unintentionally. Recommend: file bug, keep baseline.
2. **Intentional change** — feature work changed output. Recommend: `/approve` after human confirmation.
3. **Normalization gap** — diff is noise (timestamp, GUID, float tolerance, ordering). Recommend: add rule to normalizer.
4. **Environment drift** — DPI, locale, GPU, plugin load order. Recommend: fix env or quarantine.
5. **Flaky / timing** — non-deterministic; recommend retry + root-cause in player sync.
## Output
Short report per failure:
- Bucket
- Evidence (specific diff lines)
- Recommended action (one of: file bug / approve / add normalizer rule / fix env / investigate flake)
- Confidence (low/medium/high)
Do not mutate baselines or scenarios yourself. Only recommend.

View File

@@ -0,0 +1,45 @@
---
name: evaluator
description: Grade a completed module or feature against its Sprint Contract. Independent from the Generator — reads the contract, exercises the artifact, scores each Definition-of-Done item, and reports pass/fail with evidence. Use after the Generator reports "done" but before the work is merged or marked complete in PROGRESS.md.
tools: Read, Grep, Glob, Bash
model: sonnet
---
You are **evaluator**. You are deliberately *not* the agent that built the thing. Your value comes from independent verification.
## Inputs
- `docs/contracts/<name>.md` — the Sprint Contract
- The generator's artifact (code, scenario, baseline, catalog…)
- Any fixtures or oracles named in the contract
## Method
1. Read the contract. If missing, refuse and tell the caller to run `planner` first.
2. For each DoD item:
- Execute the stated verification (script, diff, inspection).
- Record **evidence** (command output, file path, diff snippet).
- Score: `pass` / `fail` / `partial` / `untestable`.
3. Compute an overall verdict: pass only if all items pass.
4. Write a report to `docs/contracts/<name>.evaluation.md` with timestamp.
5. If any fail, **do not** mark PROGRESS.md as done. Return the report to the caller.
## Rules
- No self-praise, no charity. Treat ambiguous results as `partial` or `untestable`.
- Never modify the artifact you are grading. You may only run read/execute commands.
- If a DoD item cannot be tested with the available tools, flag it `untestable` and explain — do not fake a pass.
- Keep the report terse: one bullet per DoD item with evidence link.
## Output format
```markdown
# Evaluation — <name> (<YYYY-MM-DD HH:MM>)
Verdict: **pass** | **fail**
| # | DoD item | Score | Evidence |
|---|----------|-------|----------|
| 1 | ... | pass | logs/eval-1.txt |
| 2 | ... | fail | diff snippet |
## Notes
<free-form observations, edge cases, follow-ups>
```

55
.claude/agents/planner.md Normal file
View File

@@ -0,0 +1,55 @@
---
name: planner
description: Convert a natural-language request or module goal into a concrete PLAN.md entry plus a Sprint Contract that defines "done". Use at the start of any non-trivial module or feature work, before generator-style implementation begins.
tools: Read, Write, Edit, Glob, Grep
model: sonnet
---
You are **planner**. You translate vague asks into *contracts* that a separate Generator agent can implement against and a separate Evaluator agent can grade.
## Inputs
- User request (may be a sentence)
- Current `PLAN.md`, `PROGRESS.md`, `CLAUDE.md`
- Relevant memory under `~/.claude/projects/.../memory/`
## Outputs
1. A new entry (or update) in `PLAN.md` with priority and dependencies.
2. A **Sprint Contract** file at `docs/contracts/<module-or-feature>.md` using the template below.
3. A short briefing back to the caller (≤10 lines) summarizing what was written.
## Sprint Contract template
```markdown
# Sprint Contract — <name>
**Owner:** <agent or human>
**Depends on:** <modules>
**Issue:** #<n>
## Goal
<one paragraph — what problem this solves>
## Definition of Done (grading criteria)
- [ ] <criterion 1 — objectively checkable>
- [ ] <criterion 2>
- [ ] <criterion 3>
## Interfaces / contracts
- Inputs:
- Outputs:
- Side effects:
## Out of scope
- <explicit non-goals>
## Evaluation plan
How the evaluator agent will verify each DoD item (commands, fixtures, oracles).
## Risks / open questions
```
## Rules
- Never implement. Never write code into `src/`. Only plan documents.
- DoD items must be **objectively checkable** — no "works well", "is clean".
- If the request is ambiguous, write the contract with explicit `TODO(user):` lines and stop.
- Keep criteria ≤7. More than that means the scope should be split.

View File

@@ -0,0 +1,39 @@
---
name: scenario-author
description: Translate a natural-language manual-test description into a structured recordingtest scenario file (JSON/YAML) with element-aware steps, checkpoints, and expected baseline artifacts. Use when the user wants to add a new regression scenario without recording it live.
tools: Read, Write, Glob, Grep
model: sonnet
---
You are **scenario-author**. You convert prose into scenario files under `scenarios/`.
## Scenario schema (draft)
```yaml
name: <slug>
description: <one line>
sut:
exe: "EG-BIM Modeler/EG-BIM Modeler.exe"
startup_timeout_ms: 15000
steps:
- kind: click | type | drag | hotkey | wait | checkpoint | save
target:
uia_path: "MainWindow/Toolbar/Button[@Name='Box']" # when available
offset: [x, y] # fallback for 3D viewport
value: <string|null>
wait_for: <uia event or engine signal>
checkpoints:
- after_step: 5
save_as: scenarios/<name>/checkpoint-1
baselines:
- path: baselines/<name>.approved.hme
normalize_with: [default, floats_e6, strip_timestamps]
```
## Rules
- Prefer UIA element paths over raw coordinates. Only use `offset` for 3D viewport interaction.
- Always insert at least one checkpoint + final save baseline.
- Pick normalization profiles from existing rules; if unsure, add a TODO and ask the user.
- Never invent UIA paths you have not verified via sut-explorer output. Mark unknowns with `TODO:`.
- Write the scenario file and return a terse summary with the file path.

View File

@@ -0,0 +1,30 @@
---
name: sut-explorer
description: Analyze the EG-BIM Modeler SUT folder — enumerate MEF plugins, dump Json/ config files, inspect HmEG engine assemblies, and produce a catalog for the recordingtest automation tool. Use when building or refreshing sut-prober outputs, or when the user asks about SUT structure, plugins, or settings.
tools: Read, Glob, Grep, Bash, Write
model: sonnet
---
You are **sut-explorer**, a read-only analyst for the SUT (System Under Test) living at `EG-BIM Modeler/` in the recordingtest repo.
## Responsibilities
1. Enumerate MEF plugins under `EG-BIM Modeler/Plugins/Eg*Plugin/` and produce a catalog (plugin name, main dll, any manifest).
2. Snapshot `EG-BIM Modeler/Json/*.json` contents and identify non-deterministic fields (timestamps, GUIDs, absolute paths, recent file lists).
3. Inspect HmEG/HmGeometry/Editor*.dll assemblies (names, versions) — use `Bash` with `dotnet` or `strings` if available, but **never execute the SUT**.
4. Write results to `docs/sut-catalog/` as markdown + JSON.
## Rules
- **Never launch `EG-BIM Modeler.exe`**. Static analysis only.
- **Never modify** the `EG-BIM Modeler/` folder.
- Keep outputs diff-friendly: sorted, stable ordering, no absolute paths.
- If asked to do something outside this scope, decline and suggest the right agent/command.
## Output format
Return a short summary to the caller and write detailed catalogs to `docs/sut-catalog/`. Always list:
- Plugin count and notable categories
- Json config files and suspected non-deterministic fields
- Engine assembly list with versions (if derivable)
- Follow-up questions for the user

View File

@@ -0,0 +1,21 @@
---
name: approve
description: Promote a received baseline to approved after human confirmation. Usage /approve <scenario-name>
allowed-tools: Read, Bash, Write, Glob
---
Promote a received golden file to an approved baseline.
Scenario name: `$ARGUMENTS`
Steps:
1. Refuse if `$ARGUMENTS` is empty. Ask the user for a scenario name.
2. Locate `baselines/$ARGUMENTS.received.*` files. Refuse if none exist.
3. Show the diff against the existing `*.approved.*` (if any) so the user can confirm the intent.
4. **Ask the user to type an approval reason** (intentional change / bug fix / new feature etc.). Record it.
5. Copy `*.received.*``*.approved.*`.
6. Write an entry to `docs/history/YYYY-MM-DD_approve-$ARGUMENTS.md` with the reason, diff summary, context usage, and duration.
7. Update `PROGRESS.md` baseline log.
Never approve without an explicit reason from the user.

View File

@@ -0,0 +1,16 @@
---
name: contract
description: Create or update a Sprint Contract for a module/feature via the planner agent. Usage /contract <module-name or short description>
allowed-tools: Read, Write, Edit, Agent, Glob, Grep
---
Create a Sprint Contract and PLAN.md entry for: `$ARGUMENTS`
Delegate to the **planner** subagent. The planner must:
1. Read `PLAN.md`, `PROGRESS.md`, `CLAUDE.md`, and relevant memory.
2. Write `docs/contracts/<slug>.md` with the Sprint Contract template (Goal, DoD, Interfaces, Out of scope, Evaluation plan, Risks).
3. Add or update the corresponding `PLAN.md` entry with priority and dependencies.
4. Return a short briefing with file paths.
If `$ARGUMENTS` is empty, ask the user what to plan. Never write code into `src/` — planning only.

View File

@@ -0,0 +1,20 @@
---
name: evaluate
description: Grade a completed module against its Sprint Contract via the evaluator agent. Usage /evaluate <contract-slug>
allowed-tools: Read, Glob, Grep, Bash, Agent
---
Evaluate module: `$ARGUMENTS`
Delegate to the **evaluator** subagent. It must:
1. Read `docs/contracts/$ARGUMENTS.md`. Refuse if missing.
2. For each Definition-of-Done item, run the verification named in the contract's Evaluation plan.
3. Collect evidence (command output, diffs, file paths).
4. Write `docs/contracts/$ARGUMENTS.evaluation.md` with the verdict table.
5. Return the verdict to the caller.
If verdict is **fail**, do NOT mark PROGRESS.md as done — report back so the generator can iterate.
If verdict is **pass**, the caller (not the evaluator) may update PROGRESS.md.
Never let the generator and evaluator be the same agent in a single session.

View File

@@ -0,0 +1,23 @@
---
name: handoff
description: Session handoff — update PROGRESS.md, PLAN.md, and write the history file in one go before ending a work session.
allowed-tools: Read, Write, Edit, Bash
---
Prepare a clean handoff so the next agent can pick up.
Steps:
1. Read `PROGRESS.md` and `PLAN.md`. If either is missing, create a skeleton.
2. Ask the user (or infer from the session) what was completed, what is in progress, and what is blocked.
3. Update `PROGRESS.md`:
- Move completed items to "Done" with date and artifact paths.
- Mark in-progress items with current owner and status.
4. Update `PLAN.md`:
- Remove completed items.
- Add any newly discovered follow-ups.
- Re-order priorities if needed.
5. Write `docs/history/YYYY-MM-DD_{작업명}.md` with **소요 시간**, **Context 사용량**, 이슈 번호, 산출물.
6. Confirm all three files are saved and list their paths.
Arguments: `$ARGUMENTS` — optional short description to seed the history file title.

View File

@@ -0,0 +1,16 @@
---
name: progress
description: Print a concise summary of PROGRESS.md and PLAN.md so the agent can decide what to work on next.
allowed-tools: Read
---
Read `PROGRESS.md` and `PLAN.md` and produce a short status report:
- **Done recently** (last 5 items)
- **In progress** (with owner)
- **Blocked**
- **Next up** (top 3 from PLAN.md)
If either file is missing, say so and suggest running `/handoff` to bootstrap them.
Keep the report under 20 lines.

View File

@@ -0,0 +1,19 @@
---
name: regress
description: Run the full recordingtest regression suite (or a filtered subset) and triage failures.
allowed-tools: Bash, Read, Glob, Grep, Agent, Write
---
Run the regression suite.
Steps:
1. Verify the runner exists. If `src/Recordingtest.Runner/` is not yet built, stop and tell the user the suite is not set up.
2. Execute the runner with optional filter `$ARGUMENTS` (empty = all scenarios).
3. Collect results from the runner output folder.
4. For each failed scenario, delegate to the `diff-triager` subagent with the baseline/received/artifact paths.
5. Summarize: passed / failed / triage buckets.
6. If any failures are classified as "normalization gap", list suggested rules at the end.
7. Append run summary to `PROGRESS.md` under a "Recent regression runs" section.
Do NOT auto-approve or mutate baselines. Human confirmation is required via `/approve`.

View File

@@ -0,0 +1,19 @@
---
name: sut-probe
description: Static probe of the EG-BIM Modeler SUT — enumerate plugins, snapshot Json/ configs, list engine assemblies. Does NOT launch the SUT.
allowed-tools: Read, Glob, Grep, Bash, Write, Agent
---
Run a static analysis pass on the SUT at `EG-BIM Modeler/` and produce a catalog.
Delegate to the `sut-explorer` subagent with this scope:
1. List every plugin folder under `EG-BIM Modeler/Plugins/` and count them.
2. Read each `EG-BIM Modeler/Json/*.json` and flag non-deterministic fields.
3. List core assemblies (`HmEG*.dll`, `Editor*.dll`, `HmGeometry*.dll`) with file sizes.
4. Write the catalog to `docs/sut-catalog/catalog.md` and `docs/sut-catalog/plugins.json`.
5. Report a concise summary back here.
Arguments (optional): $ARGUMENTS — if provided, restrict analysis to that subpath (e.g. `Plugins/EgBoxPlugin`).
After the subagent reports, update `PROGRESS.md` with the catalog timestamp.

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# PreToolUse(Edit|Write) hook: block modifications to the EG-BIM Modeler/ folder.
# The SUT binary tree is read-only from recordingtest's perspective.
set -e
input=$(cat)
path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
case "$path" in
*/EG-BIM\ Modeler/*|*"EG-BIM Modeler"*)
>&2 echo "🚫 EG-BIM Modeler/ is the SUT binary tree and must not be modified by recordingtest. Use docs/sut-catalog/ for derived data."
exit 2
;;
esac
exit 0

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# PreToolUse(Bash) hook: warn if the agent is about to launch the SUT binary
# without going through the runner/player. Does not block; just informs.
set -e
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
if echo "$cmd" | grep -qi 'EG-BIM Modeler\.exe'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
additionalContext: "⚠ SUT 실행 감지: EG-BIM Modeler.exe 는 player/runner 를 통해서만 실행하세요. sut-explorer 는 정적 분석만 허용됩니다."
}
}'
fi

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# SessionStart hook: surface PROGRESS.md and PLAN.md so any agent can pick up work.
set -e
ctx=""
for f in PROGRESS.md PLAN.md; do
if [ -f "$f" ]; then
ctx="${ctx}
=== $f ===
$(head -80 "$f")"
else
ctx="${ctx}
=== $f ===
(missing — run /handoff to bootstrap)"
fi
done
# Emit JSON so Claude Code adds it as additionalContext.
jq -n --arg c "$ctx" '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: $c
}
}'

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Stop hook: remind the agent to run /handoff if PROGRESS.md / PLAN.md / today's
# history file look stale. Informational only — never blocks.
set -e
today=$(date +%Y-%m-%d)
msg=""
if [ ! -f PROGRESS.md ]; then msg="${msg}\n- PROGRESS.md missing"; fi
if [ ! -f PLAN.md ]; then msg="${msg}\n- PLAN.md missing"; fi
if ! ls "docs/history/${today}_"*.md >/dev/null 2>&1; then
msg="${msg}\n- 오늘 날짜의 history 파일이 없습니다 (${today})"
fi
if [ -n "$msg" ]; then
jq -n --arg m "작업 종료 전 확인:${msg}\n→ /handoff 실행 권장" '{
hookSpecificOutput: {
hookEventName: "Stop",
additionalContext: $m
}
}'
fi

View File

@@ -3,10 +3,55 @@
"allow": [ "allow": [
"mcp__gitea__get_me", "mcp__gitea__get_me",
"mcp__gitea__create_repo", "mcp__gitea__create_repo",
"mcp__gitea__issue_write" "mcp__gitea__issue_write",
"mcp__gitea__issue_read",
"Edit(/.claude/skills/golden-file-normalizer/**)",
"Edit(/.claude/skills/flaui-cookbook/**)"
], ],
"additionalDirectories": [ "additionalDirectories": [
"C:\\Users\\nbright\\.claude" "C:\\Users\\nbright\\.claude"
] ]
},
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/session-start-progress.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/guard-sut-launch.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/guard-sut-folder.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/stop-handoff-reminder.sh"
}
]
}
]
} }
} }

View File

@@ -0,0 +1,53 @@
---
name: flaui-cookbook
description: FlaUI and UI Automation recipes for the recordingtest project — waiting strategies, element finding patterns, pattern invocation, and integration with element-aware recording. Use when writing recorder/player code or diagnosing flaky UIA interactions.
---
# FlaUI cookbook
## Dependencies
- `FlaUI.Core`, `FlaUI.UIA3` (prefer UIA3 over UIA2)
- Target: `net8.0-windows` or higher
## Launching the SUT
```csharp
var app = FlaUI.Core.Application.Launch("EG-BIM Modeler/EG-BIM Modeler.exe");
using var automation = new UIA3Automation();
var main = app.GetMainWindow(automation, TimeSpan.FromSeconds(30));
```
## Finding elements (prefer AutomationId > Name > ClassName)
```csharp
var btn = main.FindFirstDescendant(cf => cf.ByAutomationId("BoxCommand")).AsButton();
```
## Waiting — NEVER use fixed sleep
```csharp
Retry.WhileNull(
() => main.FindFirstDescendant(cf => cf.ByName("Ready")),
timeout: TimeSpan.FromSeconds(10),
interval: TimeSpan.FromMilliseconds(100));
```
For plugin load completion, wait on a known UIA element from a late-loading plugin, not a timer.
## Element path capture (for element-aware recording)
Walk ancestors and emit `ClassName[@AutomationId='…']/ClassName[@Name='…']` — resilient to layout changes.
## 3D viewport fallback
SharpDX D3D11 surface is a UIA dead zone. Record:
1. The hosting element's UIA path
2. A normalized offset `(dx/width, dy/height)` inside that element
3. The engine state sidecar AFTER the interaction
## Common pitfalls
- Calling UIA from the wrong thread — always marshal to STA.
- Stale cached elements after modal dialogs — re-find after focus change.
- IME composition swallows keys — use clipboard paste for Korean/Japanese input.
- MahApps Flyouts are not descendants of MainWindow; search from the desktop root.

View File

@@ -0,0 +1,37 @@
---
name: golden-file-normalizer
description: Guidance and recipes for writing normalization rules that make SUT output files deterministic for golden-file regression testing. Use when designing or extending the normalizer module, or when diagnosing diff noise.
---
# Golden-file normalizer skill
When writing or reviewing normalization rules for recordingtest, apply this checklist.
## Canonical sources of non-determinism
| Category | Example patterns | Rule strategy |
|----------|------------------|---------------|
| Timestamps | ISO8601, Unix epoch, `"saved": "2026-..."` | Replace with `<TS>` or strip key |
| GUIDs / UUIDs | `xxxxxxxx-xxxx-...` | Replace with `<GUID-N>` (stable index per occurrence) |
| Absolute paths | `C:\Users\...\`, `D:\MYCLAUDE_PROJECT\...` | Replace repo root with `<REPO>`, user with `<USER>` |
| Recent files | `RecentFiles.json` | Empty the list or mask entirely |
| Float precision | `3.14159265358979` | Round to configured epsilon (default 1e-6) |
| Collection ordering | unsorted dict/list | Sort by canonical key |
| Machine name / locale | `DESKTOP-XXXX`, `ko-KR` | Mask or pin |
| GPU/driver hashes | inside render metadata | Strip |
## Rule authoring principles
1. **Rules are versioned** — bump the normalizer profile when adding/removing rules; scenarios pin a profile.
2. **Never hide real bugs** — mask only fields proven non-deterministic across 3+ clean runs.
3. **Text first** — parse JSON/XML semantically; use regex only as fallback.
4. **Bidirectional tests** — every rule has a unit test with before/after samples.
5. **Log what you normalized** — emit a sidecar `normalization.log` listing replacements for diagnostics.
## Output
When the user asks for a new rule, produce:
- Rule name and profile membership
- Regex or parser snippet (C#)
- Unit test sample input/output
- A note on which SUT file(s) it applies to

18
.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
# SUT 바이너리 트리 — recordingtest 저장소에는 포함하지 않음.
# 로컬 테스트용으로만 동봉하며, 각 개발자가 별도로 배치한다.
EG-BIM Modeler/
# Build outputs
bin/
obj/
*.user
*.suo
.vs/
# Logs & temp
*.log
hmlogs/
Log/
# Received (not yet approved) golden files — baselines만 커밋
baselines/**/*.received.*

169
CLAUDE.md Normal file
View File

@@ -0,0 +1,169 @@
# CLAUDE.md — recordingtest
이 파일은 Claude Code가 본 저장소에서 작업할 때 항상 읽는 프로젝트 운영 지침이다.
## 0. 세션 시작 시 필독 (모든 에이전트)
여러 에이전트가 작업을 분담하므로, **세션을 시작하는 모든 에이전트는 가장 먼저 다음 두 파일을 읽는다:**
1. [PROGRESS.md](PROGRESS.md) — 지금까지 *무엇이 끝났는가*. 모듈별 진행 상태, 최근 완료 작업, 현재 차단 이슈.
2. [PLAN.md](PLAN.md) — 앞으로 *무엇을 해야 하는가*. 모듈별 To-Do, 담당 에이전트, 우선순위, 의존 관계.
읽고 나서 자신이 맡을 작업을 PLAN.md에서 고르고, 시작 시 PROGRESS.md에 "in progress"로 표시한다. 작업이 끝나면:
- PROGRESS.md 의 해당 항목을 "done"으로 옮기고 날짜·결과·산출물 경로 기록
- PLAN.md 의 완료 항목 제거 또는 다음 단계로 갱신
- `docs/history/YYYY-MM-DD_{작업명}.md` 히스토리 파일 작성 (소요 시간·Context 사용량 필수)
**원칙:** PROGRESS.md와 PLAN.md는 *에이전트 간 공유 메모리*다. 자기 머릿속에만 두지 말고 반드시 파일에 반영해야 다음 에이전트가 이어받을 수 있다. 충돌 시 최신 커밋 기준으로 머지하고, 모호하면 사용자에게 질문한다.
## 0.1 작업 사이클 — Planner → Generator → Evaluator
Anthropic의 "Harness Design for Long-Running Agent Applications" 설계 원칙을 채택한다.
핵심: **생성자와 평가자를 같은 에이전트가 겸하지 않는다**. 자기 작업을 과대평가하는 편향을 피하기 위해서다.
모든 비자명한(non-trivial) 모듈/기능 작업은 다음 3단계를 거친다:
1. **Planner (`/contract <name>`)** — 사용자의 요청을 Sprint Contract로 변환.
- 산출물: `docs/contracts/<name>.md` (Goal, **Definition of Done**, Interfaces, Out of scope, Evaluation plan, Risks)
- DoD 항목은 **객관적으로 검증 가능**해야 한다. "잘 동작한다"는 금지.
- `PLAN.md`에 해당 항목 추가.
2. **Generator** — Sprint Contract를 계약으로 삼고 실제 구현. 일반 세션 또는 전용 구현 에이전트가 수행.
- 계약을 읽고 DoD 항목만 충족시키는 데 집중.
- 스코프 이탈 금지. 범위 변경이 필요하면 planner를 다시 호출.
3. **Evaluator (`/evaluate <name>`)** — 독립된 `evaluator` 서브에이전트가 계약 기준으로 채점.
- 산출물: `docs/contracts/<name>.evaluation.md` (verdict + evidence table)
- **fail**이면 PROGRESS.md에 done으로 옮기지 않는다. Generator가 재작업.
- **pass**여야만 호출자가 PROGRESS.md를 갱신한다.
### 컨텍스트 위생 (context hygiene)
- 긴 작업 중 컨텍스트가 차면 요약(compaction)하지 말고 **파일에 상태를 쏟고 새 세션으로 리셋**한다. PROGRESS.md/PLAN.md/Sprint Contract가 그 인계 창구다.
- Stop hook이 핸드오프 누락을 경고한다. 무시하지 말 것.
- 모델/하네스가 진화하므로 `.claude/` 비계는 주기적으로 감사·축소한다 (PLAN.md에 "scaffolding review" 상시 항목 유지).
## 1. 프로젝트 정체성
**recordingtest** 는 사내 WPF 3D 편집 응용(자체 개발, Rhino3D 유사)에 대한 **사용자 입력 회귀 테스트 자동화 도구**다. 도구 자체이지 SUT가 아니다.
### SUT(System Under Test) 개요
- WPF 데스크톱 애플리케이션, Main Window 1개 Rhino3D 유사 UI
- 자체 3D 엔진 **HmEG** (Helix toolkit 유사)
- **MEF 기반 plug-in 아키텍처** — 기능별로 독립 C# 프로젝트
- 결과물: 자체 포맷의 모델(.hmeg)/프로젝트 저장 파일
## 2. 핵심 전략 — Golden-file 회귀
> 수동 테스트 입력을 레코딩 → 리플레이 → 결과 저장 파일을 베이스라인과 diff.
ApprovalTests 패턴과 동형. SUT 코드 변경 협조를 최소화하기 위한 의도적 선택.
```
[수동 테스트] → 입력 레코드 + 결과 파일 A (baseline)
[회귀 시점] → 입력 리플레이 → 결과 파일 B → normalize → diff(A, B)
```
**우선순위:**
- 1순위: recorder, player, 정규화(normalizer), diff-reporter
- 2순위: engine-bridge (엔진 상태 sidecar JSON 덤프)
- 후순위: viewport-verifier (픽셀/이미지 비교) — golden-file로 못 잡는 케이스 보강용
## 3. 아키텍처 구성요소(예정)
| 모듈 | 책임 |
|------|------|
| `sut-prober` | 대상 앱 실행, UIA 트리·MEF plugin 목록 덤프 |
| `recorder` | 입력 캡처(키/마우스/포커스) + element path + offset 동시 저장 |
| `player` | 시나리오 재생, 비동기 작업 동기화 |
| `normalizer` | 저장 파일 정규화(타임스탬프/GUID/경로/부동소수점/순서) |
| `diff-reporter` | baseline vs received diff, 갈라진 지점 시각화 |
| `engine-bridge` | HmEG 내부 상태(카메라/선택/씬그래프) sidecar JSON 노출 |
| `test-runner` | 시나리오 묶음 실행, 리포트 |
| `viewport-verifier` | (후순위) 3D 스크린샷 SSIM 비교 |
각 모듈은 독립 PoC → 통합 순서로 진행한다.
## 4. 기술 스택 가이드
- **언어**: C# / .NET (SUT와 동일 생태계, in-process probe 가능)
- **UI 자동화**: **FlaUI** 1순위 (UIA 기반, .NET 네이티브). WinAppDriver/Appium은 fallback.
- **저수준 입력**: Win32 SetWindowsHookEx (low-level mouse/keyboard) — element 매칭과 hybrid
- **시나리오 포맷**: JSON 또는 YAML (git diff 친화적). 바이너리 금지.
- **베이스라인 파일**: `*.approved.{ext}` / `*.received.{ext}` 명명 규칙
- **이미지/큰 베이스라인**: Git LFS
## 5. 반드시 지킬 설계 원칙
1. **결정성(Determinism) 우선** — 비결정적 요소(시각·랜덤·경로·GUID·부동소수점·컬렉션 순서)는 정규화 파이프라인을 통과해야 한다. 새 필드 추가 시 정규화 규칙 동시 등록.
2. **Element-aware 입력 캡처** — 좌표만 저장 금지. 항상 UIA element path + 상대 offset을 같이 기록해 해상도/DPI/창크기 변화에 견디게 한다.
3. **타이밍 동기화** — 고정 sleep 금지. UIA 이벤트, property 변경, plugin 로드 완료 신호를 대기한다.
4. **Dispatcher marshaling** — SUT 내부 probe/hook 코드는 반드시 WPF UI thread 위에서 동작.
5. **체크포인트** — 한 시나리오 안에서 여러 번 저장→비교 가능해야 한다(이분 탐색용).
6. **실패 아티팩트 풀세트** — 실패 시 스크린샷, UIA 트리 덤프, 엔진 상태 sidecar, 입력 로그, diff를 한 폴더에 동시 저장.
7. **SUT 침습 최소화** — AutomationPeer/probe 부착이 필요하면 별도 어셈블리로 격리하고 SUT 팀과 합의 후 진행.
8. **민감정보 마스킹** — 레코딩에 비밀번호/토큰 포함 금지.
## 6. 환경 제약
- **세션 0 불가**: WPF는 대화형 데스크톱 세션 필요 → CI에서 헤드리스 불가, RDP/대화형 agent 필요
- **DPI/멀티모니터 정규화**: 테스트 머신은 고정 DPI 권장
- **GPU 의존**: 3D 렌더 결과는 드라이버 영향 → 픽셀 비교는 항상 톨러런스 + 마스킹
## 7. 작업 흐름 규칙
### 히스토리 기록 (필수)
모든 작업 완료 시 `docs/history/YYYY-MM-DD_{작업명}.md` 작성. 필수 항목:
- **소요 시간**
- **Context 사용량**
- 관련 이슈 (#N)
누락 시 저장이 차단된다.
### 저장소
- Origin: https://gitea.hmac.kr/kimminsung/recordingtest
- 이슈 트래커: 동일 Gitea
- PR/커밋 메시지에 이슈 번호(#N) 참조
### Claude 작업 원칙
- **세션 시작 시 PROGRESS.md / PLAN.md 먼저 읽기** (§0 참조)
- 코드 변경 전 관련 파일 read 필수
- 테스트 자동화 도구 자체의 회귀를 위해 본 저장소 코드도 단위 테스트 보유 권장
- 의존성 추가는 사전에 사용자 확인
- 메모리 시스템(`~/.claude/projects/.../memory/`)에 프로젝트 진행 상태/전략 결정 보존
- 작업 종료 시 PROGRESS.md / PLAN.md 업데이트 + 히스토리 파일 작성 (3종 세트)
## 8. 디렉터리 구조 (예정 — 셋업 시 확정)
```
recordingtest/
├── src/
│ ├── Recordingtest.Recorder/
│ ├── Recordingtest.Player/
│ ├── Recordingtest.Normalizer/
│ ├── Recordingtest.DiffReporter/
│ ├── Recordingtest.EngineBridge/
│ ├── Recordingtest.SutProber/
│ └── Recordingtest.Runner/
├── tests/
├── scenarios/ # 시나리오 JSON/YAML
├── baselines/ # *.approved.* (LFS 후보)
├── docs/
│ ├── history/ # 작업 히스토리
│ ├── contracts/ # Sprint Contracts + evaluations
│ └── sut-catalog/ # sut-explorer 산출물
├── PROGRESS.md
├── PLAN.md
└── CLAUDE.md
```
## 9. 비목표 (Out of Scope)
- SUT 자체의 기능 변경/버그 수정
- 일반 웹/모바일 자동화
- 부하·성능 테스트
- 단위 테스트 프레임워크 대체
## 10. 결정 로그 위치
주요 기술 결정과 그 근거는 `docs/history/`와 Claude 메모리(`project_recordingtest_*`)에 분산 저장된다. 새 결정 시 반드시 둘 다 갱신한다.

10
Directory.Build.props Normal file
View File

@@ -0,0 +1,10 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RootNamespace>Recordingtest.$(MSBuildProjectName.Replace('Recordingtest.',''))</RootNamespace>
</PropertyGroup>
</Project>

45
PLAN.md Normal file
View File

@@ -0,0 +1,45 @@
# PLAN.md — recordingtest
> 다음에 할 일의 *우선순위 큐*. 에이전트는 상단부터 집어든다.
> 비자명한 항목은 `/contract <name>` → `docs/contracts/<name>.md` 로 Sprint Contract 먼저 작성.
## P0 — 지금 바로
1. **sut-prober PoC 구현** — Sprint Contract: [docs/contracts/sut-prober.md](docs/contracts/sut-prober.md)
- Generator: 일반 세션
- Evaluator: `/evaluate sut-prober`
- 의존: 없음 (독립 실행)
2. **PROGRESS.md / PLAN.md / CLAUDE.md 훅 동작 검증** — SessionStart/Stop/Guard 3개 shell 스크립트를 실제로 트리거시켜 확인
- 의존: jq 설치 여부 확인
## P1 — PoC 1단계
3. **normalizer PoC** — Sprint Contract: [docs/contracts/normalizer.md](docs/contracts/normalizer.md)
- 의존: sut-prober의 Json 카탈로그
4. **recorder PoC (element-aware)** — Sprint Contract: [docs/contracts/recorder.md](docs/contracts/recorder.md)
- 의존: FlaUI 패키지 승인
5. **player PoC** — Sprint Contract: [docs/contracts/player.md](docs/contracts/player.md)
- 의존: recorder 산출물 포맷 확정
6. **diff-reporter PoC** — Sprint Contract: [docs/contracts/diff-reporter.md](docs/contracts/diff-reporter.md)
- 의존: normalizer 규칙 1개 이상
## P2 — 통합
7. **test-runner** — 시나리오 일괄 실행 + 실패 triage
- 의존: recorder + player + normalizer + diff-reporter 전부
8. **engine-bridge 탐색** — HmEG PDB 리플렉션으로 카메라/선택 상태 접근 가능 여부 확인
- Sprint Contract 필요
9. **AutomationPeer PR 전략 PoC** — SUT fork에 AI로 AutomationPeer 자동 부착 샘플
## P3 — 상시
- **Scaffolding review** (PoC 3개마다)
- **.claude/ 비계 감사** — 기사 원칙: 불필요한 비계 제거
- **history 파일 누락 체크** — Stop hook이 경고
## 규칙
- 항목을 집을 때 PROGRESS.md의 "In progress"에 기록하고 본인(에이전트명/세션) 명시.
- 비자명한 P0/P1/P2 항목은 반드시 Sprint Contract 먼저.
- Generator와 Evaluator는 같은 세션이 겸하지 않는다.
- 완료 시: evaluator pass → PROGRESS.md Done 이동 → PLAN.md 제거 → history 작성 → `/handoff`.

View File

@@ -1,17 +1,44 @@
# PROGRESS.md — recordingtest # PROGRESS.md — recordingtest
## 현재 상태 요약 > 에이전트 간 공유 메모리. 세션 시작 시 PLAN.md와 함께 반드시 읽을 것.
> 작업 상태만 기록한다. 결정의 근거는 `docs/history/`, 전략은 `CLAUDE.md` / 메모리 참조.
- 프로젝트: WPF Application User Input Regression Test **저장소**: https://gitea.hmac.kr/kimminsung/recordingtest
- 저장소: https://gitea.hmac.kr/kimminsung/recordingtest **SUT**: `EG-BIM Modeler/` (git 제외, 로컬 동봉)
- 히스토리 경로: docs/history **현재 이슈**: #2 컨텍스트 엔지니어링 & AI 개발환경 셋업
## 작업 이력 ---
| 날짜 | 작업 | 이슈 | 파일 | ## Done
|------|------|------|------|
## 참고 | 날짜 | 항목 | 산출물 |
|------|------|--------|
| 2026-04-07 | 히스토리 훅 설정 | `docs/history/2026-04-07_히스토리-훅-설정.md` |
| 2026-04-07 | 이슈 #2 리서치 & 에이전트 분해 | 메모리 `project_recordingtest_plan.md` |
| 2026-04-07 | 구현 고려사항 정리 (8개 카테고리) | 히스토리 |
| 2026-04-07 | Golden-file 회귀 전략 채택 | 메모리 `project_recordingtest_strategy.md` |
| 2026-04-07 | CLAUDE.md 초안 + §0 협업 규칙 | `CLAUDE.md` |
| 2026-04-07 | SUT(EG-BIM Modeler) 정적 분석 | 메모리 `project_sut_egbim_modeler.md` |
| 2026-04-07 | `.claude/` agents·commands·skills·hooks 셋업 | `.claude/` |
| 2026-04-07 | Harness design 채택 (Planner/Generator/Evaluator) | CLAUDE.md §0.1, `.claude/agents/planner.md`, `evaluator.md` |
| 2026-04-07 | SUT 폴더 `.gitignore` | `.gitignore` |
| 2026-04-07 | 초기 Sprint Contracts 5건 작성 | `docs/contracts/*.md` |
| 2026-04-07 | SUT 카탈로그 v0 (정적) | `docs/sut-catalog/catalog.md`, `plugins.md` |
| 2026-04-07 | 솔루션 스캐폴드(sut-prober PoC 타깃) | `recordingtest.sln`, `src/Recordingtest.SutProber/` |
- 히스토리 파일은 `docs/history/YYYY-MM-DD_{작업명}.md` 형식으로 저장 ## In progress
- 필수 항목: **소요 시간**, **Context 사용량**, **이슈** (#N)
_(없음 — 다음 작업은 PLAN.md 상단에서 고른다)_
## Blocked
_(없음)_
## Recent regression runs
_(러너 미구현 — /regress 사용 불가)_
## Scaffolding review
- 마지막 감사: 2026-04-07 (초기 셋업)
- 다음 감사 권장: PoC 3개 완료 후

View File

@@ -0,0 +1,44 @@
# Sprint Contract — diff-reporter
**Owner:** Generator
**Depends on:** normalizer (정규화된 입력)
**Issue:** #2
## Goal
`*.approved``*.received` 쌍을 받아 의미 있는 diff를 생성하고, 실패 지점을 시각화한다. `diff-triager` 서브에이전트가 읽을 수 있는 구조화 출력도 제공.
## Definition of Done
- [ ] `Recordingtest.DiffReporter` 라이브러리 + CLI
- [ ] 입력: `--approved <path> --received <path> --out <dir>`
- [ ] JSON/텍스트 파일은 라인·객체 단위 의미 diff, 바이너리는 hex 요약 diff
- [ ] 출력: `diff.json`(구조화), `diff.md`(사람용), `diff.html`(옵션, side-by-side)
- [ ] `diff.json` 스키마: `{ file, hunks[], summary: { added, removed, changed } }`
- [ ] 동일 파일 비교 시 "identical"로 단정, exit code 0
- [ ] 차이 존재 시 exit code 1 + 요약을 stdout 1줄
- [ ] diff-triager 에이전트가 `diff.json`만 읽어 bucket 분류 가능 (통합 테스트 1개)
## Interfaces
- **Inputs:** 두 파일 경로
- **Outputs:** `diff.json`, `diff.md`, (옵션) `diff.html`
- **Side effects:** 없음
## Out of scope
- 정규화 자체 (normalizer)
- triage 분류 로직 (diff-triager 에이전트)
- 자동 approve (`/approve` 커맨드)
## Evaluation plan
1. 동일 파일 2개 → identical 판정
2. 1필드만 바뀐 JSON → `diff.json.hunks.length == 1`
3. 바이너리 파일 diff → hex summary 출력
4. diff-triager에 통합: normalization gap 케이스에서 bucket=`normalization_gap` 분류 확인
## Risks
- 큰 파일(수십 MB 모델)에서 성능 — 스트리밍 diff 필요
- JSON 스키마 변경 시 triager 하위 호환성

View File

@@ -0,0 +1,43 @@
# Sprint Contract — normalizer
**Owner:** Generator
**Depends on:** sut-prober (Json 카탈로그)
**Issue:** #2
## Goal
SUT 저장 파일(Json 설정 + 모델 파일)의 비결정적 필드를 정규화해 golden-file 비교를 결정적으로 만든다. 규칙은 **프로파일 단위로 버전 관리**되고 단위 테스트를 동반한다.
## Definition of Done
- [ ] `Recordingtest.Normalizer` 라이브러리가 `Normalize(input, profile)` API 제공
- [ ] 기본 프로파일 `default` 포함 규칙 최소 5개: 타임스탬프 마스킹, GUID 치환, 절대 경로 → `<REPO>`/`<USER>`, 부동소수점 epsilon(1e-6), JSON 객체 키 정렬
- [ ] 프로파일은 `profiles/*.yaml`로 선언, 코드 변경 없이 규칙 추가 가능
- [ ] 각 규칙마다 `tests/Normalizer.Tests/` 아래 before/after 샘플 1쌍 이상
- [ ] 동일 입력으로 두 번 정규화해도 같은 바이트 출력 (idempotent)
- [ ] 정규화 로그(sidecar) `normalization.log` 생성 — 어떤 규칙이 몇 번 적용됐는지 기록
- [ ] `json-configs.json` 카탈로그의 "suspected fields" 전수를 default 프로파일로 덮는다
## Interfaces / contracts
- **Inputs:** 파일 경로 + 프로파일 이름
- **Outputs:** 정규화된 바이트 스트림 + sidecar 로그
- **Side effects:** sidecar 로그 쓰기만
## Out of scope
- 바이너리 `.hme`/`.egm` 파서 (별도 contract)
- SUT 내부 호출
- 정규화 규칙 자동 학습
## Evaluation plan
1. `dotnet test tests/Normalizer.Tests` 전부 pass
2. `json-configs.json`의 모든 suspected field가 default 프로파일 규칙에 커버됨 (매핑 테이블 검증)
3. 동일 입력 2회 정규화 바이트 diff 없음
4. 샘플 `Settings.json` 정규화 전/후 비교에서 타임스탬프·경로가 마스킹됨
## Risks
- epsilon 선택이 SUT 실제 정밀도와 충돌 가능 → 구성 가능해야 함.
- 컬렉션 순서 정렬이 SUT 의미를 바꿀 수 있음 → 정렬 대상은 명시적 화이트리스트.

44
docs/contracts/player.md Normal file
View File

@@ -0,0 +1,44 @@
# Sprint Contract — player
**Owner:** Generator
**Depends on:** recorder 스키마 확정
**Issue:** #2
## Goal
recorder가 생성한 시나리오 yaml을 SUT에 재생해 결과 저장 파일을 생성한다. 타이밍은 고정 sleep 금지, UIA/이벤트 기반 동기화.
## Definition of Done
- [ ] `Recordingtest.Player` 콘솔이 `--scenario <path> [--output-dir <path>]` 받음
- [ ] 각 step 재생 전 `wait_for`(uia 이벤트/엘리먼트 property) 대기 구현
- [ ] `target.uia_path`로 엘리먼트 재탐색 후 `offset_norm`으로 좌표 계산해 입력 전달
- [ ] 재생 중 예외 발생 시 시나리오 스텝 index + 엘리먼트 탐색 실패 이유를 아티팩트로 기록
- [ ] 체크포인트 step에서 현재 저장 파일을 `<output-dir>/checkpoint-<n>.*`로 복사
- [ ] 재생 완료 시 exit code 0, 실패 시 non-zero + 아티팩트 경로 출력
- [ ] 동일 시나리오 10회 재생 시 9회 이상 성공 (flaky 허용 상한)
## Interfaces
- **Inputs:** 시나리오 yaml, 아티팩트 출력 디렉터리
- **Outputs:** checkpoint 저장 파일, 실패 아티팩트(스크린샷, UIA 덤프, 로그)
- **Side effects:** SUT 프로세스 실행/종료
## Out of scope
- 정규화 (normalizer의 몫)
- diff (diff-reporter의 몫)
- 리포트 집계 (test-runner의 몫)
## Evaluation plan
1. recorder로 만든 간단 시나리오(Box 생성 저장)를 10회 재생 → 9회 이상 완료 파일 생성
2. 의도적으로 잘못된 `uia_path`를 넣어 실패 → 아티팩트에 탐색 실패 이유 기록 확인
3. 체크포인트 2개 시나리오 재생 → 각 checkpoint 파일 존재 확인
4. 고정 sleep 호출 grep으로 0건 확인
## Risks
- plugin 로드 지연 → 첫 step 전 "plugin ready" 신호 필요
- 시작 상태 초기화 (임시 프로젝트 파일 삭제) 미비 시 누적 오염
- 재생 중 모달 대화상자 출현 시 대응 정책 필요

View File

@@ -0,0 +1,47 @@
# Sprint Contract — recorder
**Owner:** Generator
**Depends on:** FlaUI 승인, sut-prober(UIA 힌트용 카탈로그)
**Issue:** #2
## Goal
사용자가 EG-BIM Modeler에서 수동 테스트하는 동안 입력(키·마우스·창 이벤트)을 캡처해 element-aware 시나리오 파일을 생성한다.
## Definition of Done
- [ ] `Recordingtest.Recorder` 콘솔 실행 시 SUT에 attach하고 입력 캡처 시작
- [ ] 캡처되는 이벤트: 키 다운/업, 마우스 클릭/드래그/휠, 포커스 변경
- [ ] 각 이벤트는 `{ ts, kind, uia_path, offset_norm, raw_coord, value }` 형식으로 기록
- [ ] 3D 뷰포트(SharpDX surface) 클릭 시 uia_path는 호스팅 엘리먼트, `offset_norm``[0..1]` 정규화 좌표
- [ ] 출력: `scenarios/<name>.yaml` 스키마 준수 (`scenario-author` 규격 일치)
- [ ] 비밀번호/토큰 마스킹 규칙 1개 이상 (예: focus된 element가 `PasswordBox`이면 `value``<MASKED>`)
- [ ] 캡처 중 SUT 성능에 눈에 띄는 영향 없음 (60 FPS 유지; 경고만)
- [ ] 캡처 종료 시 요약(이벤트 수, 소요 시간, uia_path 미결 건수)
## Interfaces
- **Inputs:** `--output scenarios/<name>.yaml`, `--attach <pid or window title>`
- **Outputs:** 시나리오 yaml
- **Side effects:** 전역 Win32 hook 등록/해제
## Out of scope
- 리플레이 (player의 몫)
- 3D 엔진 상태 캡처 (engine-bridge 사용 예정)
- 자동 이름 추정 / 자연어 변환 (scenario-author)
## Evaluation plan
1. SUT를 수동 실행 후 recorder attach → Box 명령 버튼 클릭 → Box 생성 드래그 → 저장
2. 출력 yaml 파싱 + 스키마 검증
3. 클릭 이벤트의 `uia_path``Button[@Name*='Box']` 형태 포함 확인
4. 드래그 이벤트의 `offset_norm``[0..1]` 범위 확인
5. `PasswordBox`에 더미 입력 → `<MASKED>` 기록 확인
6. 종료 후 hook 해제 확인 (재실행 시 double-hook 안 남음)
## Risks
- IME(한글) 조합 키 이벤트 손실 → 클립보드 우회 필요 가능
- 해상도/DPI 변경 시 `raw_coord`는 유효성 상실 → `offset_norm` 우선 사용
- Win32 hook은 UI thread 블록 가능 → 별도 스레드 + 큐 필수

View File

@@ -0,0 +1,48 @@
# Sprint Contract — sut-prober
**Owner:** Generator (일반 세션)
**Depends on:** 없음
**Issue:** #2
## Goal
EG-BIM Modeler SUT의 **정적** 구조(플러그인·설정·어셈블리)를 덤프해 `docs/sut-catalog/`에 베이스라인 가능한 카탈로그를 생성한다. SUT는 실행하지 않는다.
## Definition of Done
- [ ] `dotnet run --project src/Recordingtest.SutProber` 실행으로 카탈로그 3종이 생성된다: `plugins.json`, `json-configs.json`, `assemblies.json`
- [ ] `plugins.json``EG-BIM Modeler/Plugins/Eg*Plugin/` 187개(±) 전부를 담고, 각 항목은 `{ name, path, dlls[], size_bytes }` 형식
- [ ] `json-configs.json``EG-BIM Modeler/Json/*.json` 각 파일별 `{ name, top_level_keys[], suspected_nondeterministic_fields[] }`
- [ ] `assemblies.json``HmEG*.dll`, `Editor*.dll`, `HmGeometry*.dll``{ name, size, has_pdb }`
- [ ] 출력은 **정렬되어 있어** 두 번 실행해도 동일 (decimal/텍스트 diff 없음)
- [ ] `EG-BIM Modeler/` 폴더에 대한 쓰기 접근이 없다 (코드 리뷰로 확인)
- [ ] 경로는 repo root 상대 경로로 기록(절대 경로 금지)
## Interfaces / contracts
- **Inputs:** `EG-BIM Modeler/` (read-only), CLI 인자 `--sut <path>` (기본 `EG-BIM Modeler`)
- **Outputs:** `docs/sut-catalog/plugins.json`, `docs/sut-catalog/json-configs.json`, `docs/sut-catalog/assemblies.json`
- **Side effects:** 없음 (로그 제외)
## Out of scope
- SUT 실행 / 리플렉션 / 동적 분석
- PDB 파싱 (engine-bridge의 몫)
- UIA 트리 덤프 (recorder 또는 별도 PoC의 몫)
## Evaluation plan
evaluator는 다음을 수행:
1. `dotnet build` 성공
2. `dotnet run --project src/Recordingtest.SutProber -- --sut "EG-BIM Modeler"` 실행, exit code 0 확인
3. 세 카탈로그 파일 존재 확인 + JSON 파싱 유효성
4. `plugins.json` 엔트리 수 ≥ 180
5. 두 번째 실행 후 세 파일 `git diff` 결과가 비어있음을 확인 (결정성)
6. `grep -r "EG-BIM Modeler" src/Recordingtest.SutProber/` 결과에 `File.Write|File.Delete|File.Create` 호출 없음 확인
## Risks / open questions
- .NET 버전 합의 필요 (`net8.0` 제안). SUT와 다를 수 있으나 prober는 독립 프로세스이므로 무관.
- 플러그인 폴더 구조가 일정한지 샘플 확인 필요.
- 경로 구분자(Windows `\`) 정규화 정책 — 출력에서 `/`로 통일 권장.

View File

@@ -0,0 +1,19 @@
# 2026-04-07 이슈 #2 — AI 기반 AutomationPeer 부착 옵션 기록
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~3분
- **Context 사용량**: ~70k tokens
## 결정/기록
SUT 커스텀 컨트롤에 AutomationPeer를 부착하는 작업을 AI로 자동화하는 보조 전략을 메모리에 남김. Golden-file이 1차 전략이지만, element-aware 레코딩 보강이 필요해질 때 꺼낼 카드.
### 핵심
- 패턴 정형화로 80% AI 자동 생성 가능
- ControlType/패턴 매핑과 `AutomationProperties.Name`은 사람 리뷰 필요
- SUT 저장소엔 별도 브랜치/PR로 제안, 직접 커밋 금지
- recordingtest 저장소에는 가이드/스크립트만, SUT 코드 변경은 fork/PR로 분리
## 산출물
- 메모리: `project_recordingtest_automationpeer_ai.md`
- MEMORY.md 인덱스 갱신

View File

@@ -0,0 +1,21 @@
# 2026-04-07 이슈 #2 — CLAUDE.md에 PROGRESS/PLAN 협업 규칙 추가
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~3분
- **Context 사용량**: ~76k tokens
## 변경 내용
`CLAUDE.md` 에 §0 "세션 시작 시 필독" 섹션 신설. 여러 에이전트 협업을 위한 공유 메모리로 `PROGRESS.md`(끝난 일)와 `PLAN.md`(할 일) 운영 규칙 명시.
### 핵심 규칙
- 세션 시작: PROGRESS.md + PLAN.md 먼저 read
- 시작 시: PROGRESS.md에 in progress 표시
- 종료 시: PROGRESS.md(done 이동) + PLAN.md(완료 제거/다음 단계) + history 파일 (3종 세트)
- 충돌 시 최신 커밋 기준 머지, 모호하면 사용자에 질문
§7 Claude 작업 원칙에도 같은 내용 반영.
## 다음 단계
PROGRESS.md / PLAN.md 실제 파일은 셋업 단계에서 작성 예정 (현재는 CLAUDE.md 규칙만 정의).

View File

@@ -0,0 +1,26 @@
# 2026-04-07 이슈 #2 — CLAUDE.md 작성
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~5분
- **Context 사용량**: ~63k tokens
## 작업 내용
프로젝트 셋업 전, recordingtest(WPF 입력 회귀 자동화 도구)의 운영 지침인 `CLAUDE.md`를 루트에 작성. 셋업은 미실시.
## 포함 섹션
1. 프로젝트 정체성 / SUT 개요
2. 핵심 전략 — Golden-file 회귀
3. 아키텍처 구성요소(8개 모듈) 우선순위
4. 기술 스택 가이드 (FlaUI, Win32 hook, JSON/YAML, LFS)
5. 설계 원칙 8개 (결정성, element-aware, 타이밍, marshaling, 체크포인트, 아티팩트, 침습 최소화, 마스킹)
6. 환경 제약 (세션0, DPI, GPU)
7. 작업 흐름 규칙 (히스토리 필수)
8. 디렉터리 구조(예정)
9. 비목표
10. 결정 로그 위치
## 다음 단계
사용자 승인 후 실제 솔루션/프로젝트 셋업 진행.

View File

@@ -0,0 +1,28 @@
# 2026-04-07 이슈 #2 — SUT (EG-BIM Modeler) 폴더 분석
- **이슈**: kimminsync/recordingtest#2
- **소요 시간**: ~5분
- **Context 사용량**: ~82k tokens
## 분석 대상
`d:/MYCLAUDE_PROJECT/recordingtest/EG-BIM Modeler/` — SUT 실행파일 일습.
## 핵심 발견
- WPF + .NET, 3D 엔진 `HmEG.dll`, 렌더 SharpDX D3D11
- MEF plugin `Plugins/Eg*Plugin/` 수십~100개+
- **PDB 동봉** → 리플렉션/엔진 침습 비용 ↓
- `Json/` 폴더에 텍스트 설정 다수 — golden-file 1차 타깃으로 적합
- 자체 저장 포맷 `.hme`/`.egm` — 파서·정규화 규칙 필요
- 3D 뷰포트는 D3D surface로 UIA 사각지대 확정
## 전략 반영
- sut-prober 첫 PoC: `Plugins/` 스캔 + `Json/` 스냅샷
- engine-bridge: HmEG 리플렉션으로 카메라/씬그래프 접근
- 모델 포맷 파서 모듈 분리
## 산출물
- 메모리: `project_sut_egbim_modeler.md`
- MEMORY.md 인덱스 갱신

View File

@@ -0,0 +1,68 @@
# 2026-04-07 이슈 #2 — .claude/ 에이전트·커맨드·스킬·훅 셋업
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~15분
- **Context 사용량**: ~95k tokens
## 작업 내용
Claude Code 공식 포맷 조사(claude-code-guide 에이전트) 후, recordingtest 운영에 필요한 커스터마이즈를 `.claude/` 하위에 생성.
### Subagents — `.claude/agents/*.md`
- **sut-explorer** — EG-BIM Modeler 정적 분석(플러그인/Json/어셈블리). SUT 실행 금지.
- **diff-triager** — 회귀 실패 분류(real bug / intentional / normalization gap / env drift / flaky).
- **scenario-author** — 자연어 → 시나리오 YAML 생성.
### Slash commands — `.claude/commands/*.md`
- **/sut-probe** — sut-explorer 위임, 카탈로그 생성.
- **/regress** — 회귀 실행 + 실패 자동 triage.
- **/approve** — received → approved 승격, 사유 강제.
- **/handoff** — PROGRESS/PLAN/history 3종 세트 갱신.
- **/progress** — PROGRESS.md/PLAN.md 요약 출력.
### Skills — `.claude/skills/<name>/SKILL.md`
- **golden-file-normalizer** — 정규화 규칙 카테고리/원칙/저작 가이드.
- **flaui-cookbook** — FlaUI 런칭/대기/엘리먼트 캡처/3D 뷰포트 폴백/함정.
### Hooks — `.claude/settings.json` + `.claude/hooks/*.sh`
- **SessionStart** → `session-start-progress.sh`: PROGRESS.md/PLAN.md 자동 주입.
- **PreToolUse(Bash)** → `guard-sut-launch.sh`: EG-BIM Modeler.exe 실행 경고.
- **PreToolUse(Edit|Write)** → `guard-sut-folder.sh`: SUT 폴더 수정 차단(exit 2).
- **Stop** → `stop-handoff-reminder.sh`: 오늘 history 파일/PROGRESS/PLAN 누락 시 /handoff 권장.
## 원칙
- 포맷은 claude-code-guide 조사 결과를 따름(frontmatter: name/description/allowed-tools/tools/model).
- SUT 폴더는 **read-only** — 훅으로 강제.
- 실제 모듈(src/*)은 아직 미생성 — /regress 등은 건드릴 파일이 없으면 early-exit.
## 산출물
```
.claude/
├── agents/
│ ├── sut-explorer.md
│ ├── diff-triager.md
│ └── scenario-author.md
├── commands/
│ ├── sut-probe.md
│ ├── regress.md
│ ├── approve.md
│ ├── handoff.md
│ └── progress.md
├── skills/
│ ├── golden-file-normalizer/SKILL.md
│ └── flaui-cookbook/SKILL.md
├── hooks/
│ ├── session-start-progress.sh
│ ├── guard-sut-launch.sh
│ ├── guard-sut-folder.sh
│ └── stop-handoff-reminder.sh
└── settings.json (hooks 섹션 추가)
```
## 다음 단계
- PROGRESS.md / PLAN.md 실제 파일 작성 (셋업 단계에서)
- src/ 모듈 scaffold
- 훅 bash 스크립트 실제 동작 검증

View File

@@ -0,0 +1,34 @@
# 2026-04-07 이슈 #2 — Golden-file 회귀 전략 채택
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~5분
- **Context 사용량**: ~58k tokens
## 결정
사용자 제안 채택: **수동 테스트 입력 레코딩 → 리플레이 후 저장 파일을 베이스라인과 diff** 하는 golden-file 회귀 방식을 1차 검증 수단으로 삼는다. (ApprovalTests 패턴과 동형)
## 근거
- SUT(WPF+MEF+HmEG)에 AutomationPeer/probe 부착 협조 비용 회피
- 결과물 파일 자체가 검증 기준 → 직관적, 사용자가 시나리오 작성 가능
- 픽셀 비교 의존도 ↓ → viewport-verifier 우선순위 하향
## 성립 조건 / 함정
1. **결정성**: 저장 파일의 타임스탬프/GUID/경로/부동소수점/순서 → 정규화 파이프라인 필수
2. **입력 재현 정확도**: 타이밍·포커스·IME·3D 드래그 픽셀 증폭
3. **비교 범위**: 저장 파일만 vs +중간 체크포인트 vs +엔진 상태 sidecar
4. **베이스라인 관리**: `*.approved`/`*.received` 워크플로, 큰 파일 LFS
5. **진단**: 단계별 스냅샷으로 갈라진 지점 이분 탐색
## 모듈 우선순위 재조정
핵심: **recorder + player + 정규화 모듈 + diff-reporter**
보조: engine-bridge(sidecar JSON 덤프 용도)
후순위: viewport-verifier(픽셀 비교)
## 다음 단계
- 저장 파일 포맷 조사 (텍스트/바이너리, 정규화 가능 영역)
- recorder PoC 범위 정의

View File

@@ -0,0 +1,41 @@
# 2026-04-07 이슈 #2 — Planner/Evaluator 에이전트 & Sprint Contract 도입
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~10분
- **Context 사용량**: ~108k tokens
## 배경
Anthropic "Harness Design for Long-Running Agent Applications" 기사를 읽고, 핵심 원칙을 recordingtest에 반영.
## 추가된 것
### 에이전트
- `.claude/agents/planner.md` — 요청 → Sprint Contract + PLAN.md 엔트리. 구현 금지.
- `.claude/agents/evaluator.md` — 완료된 모듈을 Sprint Contract 기준으로 독립 채점. 자기 작업 평가 금지.
### 커맨드
- `.claude/commands/contract.md``/contract <name>`
- `.claude/commands/evaluate.md``/evaluate <name>`
### CLAUDE.md
- §0.1 "작업 사이클 — Planner → Generator → Evaluator" 섹션 신설
- Sprint Contract / DoD 객관성 / 독립 평가 원칙 명시
- 컨텍스트 위생: compaction 대신 reset, Stop hook 경고 준수, scaffolding audit 상시 항목
- 디렉터리 구조에 `docs/contracts/`, `PROGRESS.md`, `PLAN.md` 추가
### 메모리
- `project_recordingtest_harness_design.md` 신규
- MEMORY.md 인덱스 갱신
## 원칙 요약
1. 생성자와 평가자는 같은 에이전트가 겸하지 않는다.
2. DoD는 객관적으로 검증 가능해야 한다.
3. 컨텍스트가 차면 요약하지 말고 파일에 쏟고 리셋.
4. evaluator fail → PROGRESS.md에 done으로 넘기지 않는다.
5. `.claude/` 비계는 주기적으로 감사·축소.
## 다음 단계
PROGRESS.md / PLAN.md 실제 부트스트랩 (첫 번째 Sprint Contract 작성부터).

View File

@@ -0,0 +1,26 @@
# 2026-04-07 이슈 #2 — 구현 고려사항 정리
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~5분
- **Context 사용량**: ~52k tokens
## 정리한 고려사항 카테고리
1. **레코딩/리플레이 안정성** — 좌표 vs element, 타이밍, 포커스/Z-order, IME
2. **WPF/MEF 특성** — Custom AutomationPeer, plugin 비동기 로드, Dispatcher marshaling
3. **3D 뷰포트(HmEG) 검증** — 픽셀 비교 한계, 엔진 내부 상태 검증, 픽킹 안정성
4. **환경/재현성** — DPI, 멀티모니터, 시계/랜덤 격리, CI 세션 0 제약
5. **테스트 자산 관리** — JSON/YAML 시나리오, 베이스라인 이미지 LFS, 모듈화
6. **진단/디버깅** — 실패 아티팩트(스샷·UIA 덤프·엔진 상태), 영상 녹화, diff 오버레이
7. **보안/안전** — 입력 마스킹, 권한 일치
8. **거버넌스** — SUT 코드 변경 협조(AutomationPeer/probe), 유지보수 비용
## 핵심 인사이트
- 픽셀 비교보다 **엔진 내부 상태(engine-bridge)** 가 훨씬 안정적 → 우선순위 상향
- **SUT 팀 협조** 가 기술적 난제보다 큰 장애물 (AutomationPeer/probe 부착)
- WPF는 세션 0에서 못 돌므로 CI는 대화형 세션 agent 필요
## 다음 단계
위 고려사항을 sut-prober PoC 설계에 반영.

View File

@@ -0,0 +1,41 @@
# 2026-04-07 이슈 #2 — 리서치 및 Agent 분해
- **이슈**: kimminsung/recordingtest#2 — 요구사항 컨텍스트 엔지니어링과 AI 개발환경 셋팅
- **소요 시간**: ~15분
- **Context 사용량**: ~45k tokens
## 작업 내용
WPF + MEF 플러그인 + 자체 HmEG 3D 엔진으로 구성된 SUT를 대상으로 한 입력 회귀 테스트 자동화 도구의 접근법을 웹 리서치하고, 단계별 agent로 분해하여 메모리에 저장.
### 리서치 결론
- **UIA 1순위**: FlaUI (.NET native, WPF 정밀 제어). WinAppDriver는 정체, Appium은 그 래퍼.
- **3D 뷰포트**: UIA 불가 → 좌표 입력 + 픽셀/이미지 비교 + 엔진 in-process hook 하이브리드.
- **MEF plug-in**: 각 plugin 프로젝트에 custom `AutomationPeer` 부착, plugin 상태를 probe로 노출.
- **레코딩**: Win32 low-level hook + UIA element path + offset 동시 저장(해상도 내성).
### Agent 분해(예정)
1. sut-prober — UIA 트리 덤프, MEF plugin 목록
2. recorder — element-aware 입력 캡처
3. player — 재생/동기화
4. viewport-verifier — 3D 스크린샷 비교
5. engine-bridge — HmEG 상태 노출
6. test-runner — 시나리오 실행/리포트
7. diff-reporter — 실패 시각화
## 산출물
- 메모리: `project_recordingtest_goal.md`, `project_recordingtest_plan.md`
- 본 히스토리 파일
## 다음 단계
`sut-prober` PoC부터 시작 — 사용자 결정 대기.
## 참고 링크
- [FlaUI vs WinAppDriver 2026](https://www.testsprite.com/use-cases/en/the-most-accurate-alternatives-to-winappdriver)
- [WPF Custom AutomationPeer](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/ui-automation-of-a-wpf-custom-control)
- [TestComplete WPF Record/Replay](https://support.smartbear.com/testcomplete/docs/app-testing/desktop/wpf/index.html)

View File

@@ -0,0 +1,51 @@
# 2026-04-07 이슈 #2 — 병렬 스캐폴드 + Sprint Contracts 일괄
- **이슈**: kimminsung/recordingtest#2
- **소요 시간**: ~20분
- **Context 사용량**: ~130k tokens
## 작업 내용
"병렬화 가능한 부분 병렬 구현" 요청에 따라, 독립 파일들을 한 세션에서 병렬 Write로 일괄 생성.
### 부트스트랩 문서
- `PROGRESS.md` — 공유 상태 파일 확장 (Done/In progress/Blocked/Scaffolding review)
- `PLAN.md` — P0~P3 우선순위 큐 + 작업 규칙
### Sprint Contracts (5건)
`docs/contracts/` 하위:
- `sut-prober.md` — 187개 플러그인 카탈로그 + json-configs + assemblies 덤프
- `normalizer.md` — 기본 프로파일 5규칙, idempotent
- `recorder.md` — element-aware 입력 캡처, IME·마스킹 포함
- `player.md` — 10회 중 9회 성공, 고정 sleep 금지
- `diff-reporter.md` — JSON/텍스트 의미 diff, triager 연동
각 contract는 planner 에이전트 규격(Goal / DoD / Interfaces / Out of scope / Evaluation plan / Risks)을 준수하며, DoD 항목은 모두 객관적으로 검증 가능.
### SUT 카탈로그 v0
- `docs/sut-catalog/catalog.md` — 어셈블리·UI스택·Json 설정·저장 포맷·영향 요약
- `docs/sut-catalog/plugins.md` — 187개 플러그인 전체 목록 (사전순)
### 솔루션 스캐폴드 (sut-prober PoC)
- `recordingtest.sln`
- `Directory.Build.props` (net8.0, nullable, warnings-as-errors)
- `src/Recordingtest.SutProber/` 4개 파일:
- `Recordingtest.SutProber.csproj`
- `Program.cs` — CLI 진입, `--sut`/`--out` 인자
- `PluginScanner.cs` — 정렬된 플러그인 엔트리 스캔
- `JsonConfigScanner.cs` — Json/*.json top-level 키 + 의심 필드 휴리스틱
- `AssemblyScanner.cs` — HmEG*/Editor*/HmGeometry* 어셈블리 + PDB 존재
**주의**: sut-prober 구현은 contract를 **선제적으로 맞추지는 않음** — 빌드·실행·evaluator 검증은 별도 Generator 세션의 몫. 현재는 컴파일 가능한 뼈대 수준.
## 병렬화 전략
- 파일 간 의존성 0인 것들만 병렬 Write
- Edit는 기존 파일 의존 → 순차 처리
- 에이전트 스폰 대신 단일 세션 다중 도구 호출로 context 오버헤드 최소화
## 다음 단계
1. **Generator 세션**에서 sut-prober 빌드/실행 → 카탈로그 산출물 생성
2. **Evaluator 세션**에서 `/evaluate sut-prober` → contract DoD 채점
3. pass 시 PROGRESS.md → PLAN.md 제거 → P1 항목 착수

100
docs/sut-catalog/catalog.md Normal file
View File

@@ -0,0 +1,100 @@
# SUT Catalog (v0, 정적 분석)
> 수동 관찰 기반 초기 카탈로그. `sut-prober` PoC 완료 시 기계 생성 파일로 교체된다.
> 마지막 갱신: 2026-04-07
## 개요
- **이름**: EG-BIM Modeler
- **위치**: `EG-BIM Modeler/` (git 제외)
- **실행파일**: `EG-BIM Modeler.exe` (.NET / WPF)
- **구성**: `.deps.json`, `.runtimeconfig.json`, `.pdb` 동봉
## 핵심 어셈블리
| 이름 | 역할 | PDB |
|------|------|-----|
| `HmEG.dll` | 자체 3D 엔진 | ✅ (`HmEG.pdb`, `HmEG.xml`) |
| `HmGeometry.dll` / `HmGeometry.V2.dll` | 지오메트리 커널 | ✅ |
| `HmTriangle.dll` | 삼각분할 | ✅ |
| `HmPG.dll` | (추정) 페러미터 그래프/프로파일 | - |
| `HmCommonUI.dll`, `HmCommonBridge.dll` | 공통 UI / 네이티브 브릿지 | - |
| `Editor02.HmEGAppManager.dll` | 앱 생명주기 / plugin 매니저 (MEF 로드 추정) | ✅ |
| `Editor03.PluginInterface.dll` | Plugin 기본 계약 | ✅ |
| `Editor04.CommandControl.dll` | 명령 파이프라인 UI | ✅ |
| `Editor05.CommandCore.dll` | 명령 실행 코어 | ✅ |
| `Editor06.CommandCustom.dll` | 커스텀 명령 | ✅ |
| `Editor07.WidgetPluginInterface.dll` | 위젯 플러그인 계약 | ✅ |
| `EditorCore.dll` | Editor 코어 | ✅ |
| `Editor01.Localization.dll` | 다국어 | ✅ |
| `Editor.AI01.HttpConnector.dll` | AI 연동 | ✅ |
## 렌더링
- **SharpDX** (D3D9/D3D11/DXGI/Direct2D, Mathematics, D3DCompiler)
- 3D 뷰포트는 D3D surface → **UIA 사각지대**
- `assimp.dll` / `AssimpNet.dll` → 메시 임포트
- `freetype6.dll`, `SharpFont.dll` → 폰트
- `SharpVectors.*` → SVG 지원
## UI 스택
- `MahApps.Metro`, `ControlzEx`, `Dragablz`
- `CommunityToolkit.Mvvm`
- `Microsoft.Xaml.Behaviors`, `System.Windows.Interactivity`
- `System.Windows.Controls.WpfPropertyGrid`
- `Microsoft.Web.WebView2.*` (WebView2 패널)
## 유틸/공용
- `Serilog` + `Serilog.Sinks.File`, `log4net`
- `Newtonsoft.Json`, `Google.Protobuf`, `MemoryPack.Core`
- `UnitsNet`, `FluentScheduler`, `System.Reactive`, `Flurl.Http`
- `BaronSoftware.Auth.dll` — 인증/라이선스 추정 (**레코딩 시 민감정보 주의**)
## Json 설정 (텍스트 golden-file 1차 타깃)
`EG-BIM Modeler/Json/`:
- `Settings.json`, `DefaultSettings.json`
- `CategoryCommands.json`, `DefaultCategoryCommands.json`
- `CommandAlias.json`, `DefaultCommandAlias.json`
- `KeyShortCut.json`, `DefaultKeyShortCut.json`
- `MouseSnap.json`, `DefaultMouseSnap.json`
- `StartupCommand.json`, `DefaultStartupCommand.json`
- `Units.json`, `DefaultUnits.json`
- `Materials.json`
- `RecentFiles.json`**확실히 비결정적** (최근 사용 파일 경로)
### 비결정성 후보 필드 (정규화 대상)
- `RecentFiles.json` 전체
- 모든 `Settings.json` 내부의 경로·창 크기·마지막 실행 시간
- GUID·타임스탬프 (포맷 확인 필요)
## 저장 파일 포맷
- `lmd.hme` — 자체 모델 포맷 (`.hme`)
- `jversion.egm` — 버전/메타 파일
- **바이너리 추정** — 별도 포맷 분석 contract 필요
## 로컬라이제이션
- `ko-KR`, `en-US`, `ja-JP`, `es-ES` 리소스 폴더
- `Editor01.Localization.dll` 경유
## MEF Plugin
- 위치: `EG-BIM Modeler/Plugins/Eg*Plugin/`
- **총 187개** (2026-04-07 스냅샷)
- 카테고리: 생성(Box, Circle, Arc, Curve, Cone, Cylinder …), Boolean(Union/Intersection/Difference), Array(Linear/Polar/Crv), Align, Check(Border/Disjointed/Duplicated/NonManifold/Self-Intersection/Overlap/ZeroArea/ZeroCurve), Audit, Block(Edit), Chamfer, Clash, Cap, Cut, DeleteFaces, Import/Export 등
- 전체 목록: [plugins.md](plugins.md)
## recordingtest 영향 요약
1. **1차 검증 타깃**: `Json/` 텍스트 파일 (정규화 쉬움)
2. **2차**: `.hme`/`.egm` 파서 필요
3. **3D 뷰포트**: 좌표 입력 + engine-bridge
4. **Plugin 카탈로그**: sut-prober 자동 덤프
5. **PDB 풍부** → engine-bridge 리플렉션 비용 낮음
6. **인증/라이선스**: 자동화 테스트 환경에서 로그인 상태 유지/마스킹 필요

199
docs/sut-catalog/plugins.md Normal file
View File

@@ -0,0 +1,199 @@
# EG-BIM Modeler Plugin List
- **스냅샷 일시**: 2026-04-07
- **총 개수**: 187
- **경로**: `EG-BIM Modeler/Plugins/`
> 자동 생성 전 수동 덤프. `sut-prober` PoC 완료 시 `plugins.json`으로 교체된다.
## 전체 목록 (사전순)
```
Eg3DFacePlugin
Eg3DMImportExporter
EgAddToGroupPlugin
EgAlignPlugin
EgAlignSelectedVerticesPlugin
EgAlignVerticesPlugin
EgAnglePlugin
EgArcPlugin
EgAreaCentroidPlugin
EgAreaPlugin
EgArrayCrvPlugin
EgArrayLinearPlugin
EgArrayPlugin
EgArrayPolarPlugin
EgAuditPlugin
EgBlockEditPlugin
EgBlockPlugin
EgBooleanDifferencePlugin
EgBooleanIntersectionPlugin
EgBooleanUnionPlugin
EgBoundingBoxPlugin
EgBoxPlugin
EgBuildSectionPlugin
EgCPlanePlugin
EgCapPlugin
EgChamferPlugin
EgChangeColorPlugin
EgCheckBorderEdgePlugin
EgCheckDisjointedMeshPlugin
EgCheckDuplicatedVertexPlugin
EgCheckNonManifoldEdgePlugin
EgCheckSelfIntersectionPlugin
EgCheckSelfOverlapPlugin
EgCheckZeroAreaPlugin
EgCheckZeroCurvePlugin
EgCirclePlugin
EgClashPlugin
EgCloseCrvPlugin
EgClosestPtPlugin
EgConePlugin
EgConvertTextToBlockAttributePlugin
EgCopyPlugin
EgCopyToClipboardPlugin
EgCrvEndPlugin
EgCrvStartPlugin
EgCurvePlugin
EgCurveThroughPtPlugin
EgCutPlugin
EgCylinderPlugin
EgDeleteFacesPlugin
EgDeletePlugin
EgDeleteSubCrvPlugin
EgDeselByUidsPlugin
EgDimAnglePlugin
EgDirPlugin
EgDistancePlugin
EgDividePlugin
EgDomainPlugin
EgDotPlugin
EgDupBorderPlugin
EgDupEdgePlugin
EgDupMeshHoleBoundaryPlugin
EgEditTheGradingElevationPlugin
EgEllipsePlugin
EgEllipsoidPlugin
EgEvaluatePtPlugin
EgExplodeBlockPlugin
EgExplodePlugin
EgExportByPathPlugin
EgExtendPlugin
EgExtractMeshFacePlugin
EgExtractNonManifoldMeshEdgesPlugin
EgExtractPtPlugin
EgExtrudeAlongCrvPlugin
EgExtrudePlugin
EgFillMeshHolePlugin
EgFilletEdgePlugin
EgFilletPlugin
EgFilterByFaceCountPlugin
EgFilterByLayerPlugin
EgFilterByLengthPlugin
EgFilterByModelTypePlugin
EgFilterBySpatialBoxPlugin
EgFilteringWithXYPlanePlugin
EgFindTextPlugin
EgFlipPlugin
EgGroupPlugin
EgHandleCurvePlugin
EgHidePlugin
EgHyperbolaPlugin
EgImportByPathPlugin
EgInsertPlugin
EgInterpCrvPlugin
EgIsolatePlugin
EgJoinPlugin
EgLengthPlugin
EgLinePlugin
EgLineSmoothPlugin
EgLoftPlugin
EgMarginLinePlugin
EgMatchPropertiesPlugin
EgMergeFacesPlugin
EgMeshIntersectPlugin
EgMeshPatchPlugin
EgMeshPolylinePlugin
EgMirrorPlugin
EgModelClearPlugin
EgMovePlugin
EgObjectDescriptionPlugin
EgOffsetMeshPlugin
EgOffsetPlugin
EgOneLayerOffPlugin
EgOneLayerOnPlugin
EgOpenURLPlugin
EgOrientPlugin
EgParabolaPlugin
EgPastePlugin
EgPatchPlugin
EgPipePlugin
EgPlanarDifferencePlugin
EgPlanarIntersectionPlugin
EgPlanarUnionPlugin
EgPlanePlugin
EgPlaneThroughPtPlugin
EgPointPlugin
EgPointsOffPlugin
EgPointsOnPlugin
EgPointsPlugin
EgPolygonCountPlugin
EgPolygonPlugin
EgPolylinePlugin
EgProfileSweepPlugin
EgProjectPlugin
EgProjectionPointPlugin
EgPyramidPlugin
EgRLeaderEditPlugin
EgRebuildPlugin
EgRectanglePlugin
EgReduceMeshPlugin
EgRemoveFromGroupPlugin
EgRemoveSelfIntersectionPlugin
EgRepairCrvTopologyPlugin
EgRevolvePlugin
EgRibbonPlugin
EgRoadSectionPlugin
EgRotate3DPlugin
EgRotatePlugin
EgScale1DPlugin
EgScale2DPlugin
EgScalePlugin
EgScreenShotPlugin
EgSelBoxPlugin
EgSelByUidsPlugin
EgSelColorPlugin
EgSelLayerPlugin
EgSelNamePlugin
EgSelectedLayersOffPlugin
EgSetDisplayModePlugin
EgSetFocusedViewportPlugin
EgSetGroupNamePlugin
EgSetObjectNamePlugin
EgSetViewmodePlugin
EgShearPlugin
EgShowPlugin
EgSlabPlugin
EgSlopePlugin
EgSnipCrvPlugin
EgSpherePlugin
EgSplitDisjointMeshPlugin
EgSplitPlugin
EgSrfPtPlugin
EgSubCrvPlugin
EgSurfaceQuadRemeshPlugin
EgSweep1Plugin
EgSweep2Plugin
EgTextPlugin
EgTorusPlugin
EgTrimPlugin
EgTruncatedPyramidPlugin
EgTubePlugin
EgUnGroupPlugin
EgUngroupAllPlugin
EgUnifyNormalPlugin
EgVerticalScalePlugin
EgVolumeCentroidPlugin
EgZoomPlugin
HMEGImportExport
```

16
recordingtest.sln Normal file
View File

@@ -0,0 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Recordingtest.SutProber", "src\Recordingtest.SutProber\Recordingtest.SutProber.csproj", "{1A0B2C3D-0001-0000-0000-000000000001}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1A0B2C3D-0001-0000-0000-000000000001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A0B2C3D-0001-0000-0000-000000000001}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A0B2C3D-0001-0000-0000-000000000001}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A0B2C3D-0001-0000-0000-000000000001}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,25 @@
namespace Recordingtest.SutProber;
public sealed record AssemblyEntry(string Name, long SizeBytes, bool HasPdb);
public static class AssemblyScanner
{
private static readonly string[] Prefixes = { "HmEG", "HmGeometry", "HmTriangle", "HmPG", "HmCommon", "Editor", "EditorCore" };
public static List<AssemblyEntry> Scan(string sutRoot)
{
var entries = new List<AssemblyEntry>();
if (!Directory.Exists(sutRoot)) return entries;
foreach (var file in Directory.EnumerateFiles(sutRoot, "*.dll", SearchOption.TopDirectoryOnly)
.OrderBy(f => f, StringComparer.Ordinal))
{
var name = Path.GetFileName(file);
if (!Prefixes.Any(p => name.StartsWith(p, StringComparison.Ordinal))) continue;
var pdb = Path.ChangeExtension(file, ".pdb");
entries.Add(new AssemblyEntry(name, new FileInfo(file).Length, File.Exists(pdb)));
}
return entries;
}
}

View File

@@ -0,0 +1,51 @@
using System.Text.Json;
namespace Recordingtest.SutProber;
public sealed record JsonConfigEntry(
string Name,
IReadOnlyList<string> TopLevelKeys,
IReadOnlyList<string> SuspectedNondeterministicFields);
public static class JsonConfigScanner
{
private static readonly string[] SuspectPatterns =
{ "time", "date", "path", "recent", "guid", "id", "machine", "user" };
public static List<JsonConfigEntry> Scan(string sutRoot)
{
var jsonRoot = Path.Combine(sutRoot, "Json");
if (!Directory.Exists(jsonRoot)) return new();
var entries = new List<JsonConfigEntry>();
foreach (var file in Directory.EnumerateFiles(jsonRoot, "*.json").OrderBy(f => f, StringComparer.Ordinal))
{
var name = Path.GetFileName(file);
var keys = new List<string>();
var suspects = new List<string>();
try
{
using var doc = JsonDocument.Parse(File.ReadAllText(file));
if (doc.RootElement.ValueKind == JsonValueKind.Object)
{
foreach (var p in doc.RootElement.EnumerateObject())
{
keys.Add(p.Name);
var lower = p.Name.ToLowerInvariant();
if (SuspectPatterns.Any(sp => lower.Contains(sp)))
suspects.Add(p.Name);
}
}
}
catch (JsonException)
{
keys.Add("<invalid-json>");
}
keys.Sort(StringComparer.Ordinal);
suspects.Sort(StringComparer.Ordinal);
entries.Add(new JsonConfigEntry(name, keys, suspects));
}
return entries;
}
}

View File

@@ -0,0 +1,32 @@
namespace Recordingtest.SutProber;
public sealed record PluginEntry(string Name, string Path, IReadOnlyList<string> Dlls, long SizeBytes);
public static class PluginScanner
{
public static List<PluginEntry> Scan(string sutRoot)
{
var pluginRoot = System.IO.Path.Combine(sutRoot, "Plugins");
if (!Directory.Exists(pluginRoot)) return new();
var entries = new List<PluginEntry>();
foreach (var dir in Directory.EnumerateDirectories(pluginRoot).OrderBy(d => d, StringComparer.Ordinal))
{
var dlls = Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly)
.Select(System.IO.Path.GetFileName)
.Where(n => n is not null)
.Select(n => n!)
.OrderBy(n => n, StringComparer.Ordinal)
.ToList();
long size = 0;
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
size += new FileInfo(f).Length;
var name = System.IO.Path.GetFileName(dir);
var relPath = System.IO.Path.GetRelativePath(".", dir).Replace('\\', '/');
entries.Add(new PluginEntry(name, relPath, dlls, size));
}
return entries;
}
}

View File

@@ -0,0 +1,42 @@
using System.Text.Json;
using Recordingtest.SutProber;
// Static-only probe of the EG-BIM Modeler SUT folder. NEVER launches the SUT.
// See docs/contracts/sut-prober.md for the sprint contract.
string sutPath = "EG-BIM Modeler";
string outDir = Path.Combine("docs", "sut-catalog");
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--sut" && i + 1 < args.Length) sutPath = args[++i];
else if (args[i] == "--out" && i + 1 < args.Length) outDir = args[++i];
}
if (!Directory.Exists(sutPath))
{
Console.Error.WriteLine($"SUT path not found: {sutPath}");
return 2;
}
Directory.CreateDirectory(outDir);
var plugins = PluginScanner.Scan(sutPath);
var jsonConfigs = JsonConfigScanner.Scan(sutPath);
var assemblies = AssemblyScanner.Scan(sutPath);
var opts = new JsonSerializerOptions
{
WriteIndented = true,
// deterministic property order follows POCO definition
};
File.WriteAllText(Path.Combine(outDir, "plugins.json"),
JsonSerializer.Serialize(plugins, opts));
File.WriteAllText(Path.Combine(outDir, "json-configs.json"),
JsonSerializer.Serialize(jsonConfigs, opts));
File.WriteAllText(Path.Combine(outDir, "assemblies.json"),
JsonSerializer.Serialize(assemblies, opts));
Console.WriteLine($"Wrote catalog to {outDir}/ — plugins: {plugins.Count}, json: {jsonConfigs.Count}, assemblies: {assemblies.Count}");
return 0;

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>Recordingtest.SutProber</AssemblyName>
<RootNamespace>Recordingtest.SutProber</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>