modify template

This commit is contained in:
김경종
2026-06-10 17:12:23 +09:00
parent 2d59191df2
commit df3cc3e890
186 changed files with 24935 additions and 2 deletions
+4
View File
@@ -0,0 +1,4 @@
#:schema https://developers.openai.com/codex/config-schema.json
[features]
codex_hooks = true
+28
View File
@@ -0,0 +1,28 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "python -c \"import pathlib, runpy, subprocess; root = pathlib.Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip()); runpy.run_path(str(root / '.codex' / 'hooks' / 'pre_commit_checks.py'), run_name='__main__')\"",
"timeout": 600,
"statusMessage": "Running pre-commit checks"
}
]
},
{
"matcher": "^(apply_patch|Edit|Write)$",
"hooks": [
{
"type": "command",
"command": "python -c \"import pathlib, runpy, subprocess; root = pathlib.Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip()); runpy.run_path(str(root / '.codex' / 'hooks' / 'tdd-guard.py'), run_name='__main__')\"",
"timeout": 30,
"statusMessage": "Checking TDD guard"
}
]
}
]
}
}
@@ -0,0 +1,89 @@
import json
import re
import subprocess
import sys
from pathlib import Path
def _repo_root(cwd: Path) -> Path:
try:
root = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd=cwd,
text=True,
stderr=subprocess.DEVNULL,
).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return cwd
return Path(root)
def _is_git_commit(command: str) -> bool:
return re.search(
r"^\s*git(?:\s+(?:-[A-Za-z]\s+\S+|--[A-Za-z0-9-]+(?:=\S+)?))*\s+commit\b",
command,
) is not None
def _deny(reason: str) -> None:
print(
json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}
}
)
)
def _tail(text: str, limit: int = 1200) -> str:
text = text.strip()
if len(text) <= limit:
return text
return text[-limit:]
def _build_pre_commit_commands(root: Path) -> list[list[str]]:
return [
[sys.executable, "-m", "unittest", "discover", "-s", "scripts", "-p", "test_*.py"],
[sys.executable, "scripts/validate_workspace.py"],
]
def _run_checks(root: Path) -> str | None:
for command in _build_pre_commit_commands(root):
result = subprocess.run(command, cwd=root, capture_output=True, text=True)
if result.returncode != 0:
details = _tail(result.stdout + "\n" + result.stderr)
label = " ".join(command)
if details:
return f"{label} failed:\n{details}"
return f"{label} failed with exit code {result.returncode}."
return None
def main() -> int:
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
return 0
command = payload.get("tool_input", {}).get("command", "")
if not isinstance(command, str) or not _is_git_commit(command):
return 0
cwd = Path(payload.get("cwd") or Path.cwd())
root = _repo_root(cwd)
failure = _run_checks(root)
if failure:
_deny(f"PRE-COMMIT CHECKS: {failure}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+205
View File
@@ -0,0 +1,205 @@
import json
import subprocess
import sys
from pathlib import Path
SOURCE_SUFFIXES = {".h", ".hpp", ".hh", ".hxx", ".c", ".cc", ".cpp", ".cxx", ".ixx"}
TEST_SUFFIXES = {".h", ".hpp", ".hh", ".hxx", ".c", ".cc", ".cpp", ".cxx", ".ixx"}
CONFIG_SUFFIXES = {".json", ".md", ".yml", ".yaml", ".txt", ".cmake"}
def _repo_root(cwd: Path) -> Path:
try:
root = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd=cwd,
text=True,
stderr=subprocess.DEVNULL,
).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return cwd
return Path(root)
def _extract_patch_paths(command: str) -> list[str]:
prefixes = (
"*** Add File: ",
"*** Update File: ",
"*** Delete File: ",
"*** Move to: ",
)
paths: list[str] = []
for raw_line in command.splitlines():
line = raw_line.strip()
for prefix in prefixes:
if line.startswith(prefix):
paths.append(line[len(prefix) :].strip())
break
return paths
def _touched_paths(payload: dict) -> list[str]:
tool_input = payload.get("tool_input", {})
if not isinstance(tool_input, dict):
return []
file_path = tool_input.get("file_path")
if isinstance(file_path, str) and file_path:
return [file_path]
command = tool_input.get("command")
if isinstance(command, str):
return _extract_patch_paths(command)
return []
def _normalize(path_text: str) -> str:
return path_text.replace("\\", "/").lower()
def _is_test_path(path_text: str) -> bool:
normalized = _normalize(path_text)
name = normalized.rsplit("/", 1)[-1]
path = Path(path_text)
return (
"/tests/" in f"/{normalized}"
or "/test/" in f"/{normalized}"
or name.endswith("_test.cpp")
or name.startswith("test_")
or ".test." in name
or ".spec." in name
) and path.suffix.lower() in TEST_SUFFIXES
def _token(text: str) -> str:
return "".join(ch for ch in text.lower() if ch.isalnum())
def _module_token(path: Path) -> str:
parts = [part.lower() for part in path.parts]
for marker in ("include", "src"):
if marker not in parts:
continue
idx = parts.index(marker)
if marker == "include" and idx + 2 < len(parts) and parts[idx + 1] == "fesa":
return _token(parts[idx + 2])
if marker == "src" and idx + 1 < len(parts):
return _token(parts[idx + 1])
return ""
def _related_tokens(path: Path) -> set[str]:
tokens = {_token(_base_name(path))}
module = _module_token(path)
if module:
tokens.add(module)
return {token for token in tokens if token}
def _candidate_test_paths(paths: list[str], cwd: Path, root: Path) -> list[Path]:
candidates: list[Path] = []
for path_text in paths:
resolved = _resolve_path(path_text, cwd)
if _is_test_path(str(resolved)):
candidates.append(resolved)
for test_root_name in ("tests", "test"):
test_root = root / test_root_name
if not test_root.is_dir():
continue
for suffix in TEST_SUFFIXES:
candidates.extend(test_root.rglob(f"*{suffix}"))
return candidates
def _has_related_test(path: Path, candidate_tests: list[Path]) -> bool:
tokens = _related_tokens(path)
for test_path in candidate_tests:
test_token = _token(test_path.stem)
if any(token and token in test_token for token in tokens):
return True
return False
def _is_exempt(path_text: str) -> bool:
normalized = _normalize(path_text)
path = Path(path_text)
name = path.name.lower()
if name == "cmakelists.txt":
return True
if _is_test_path(path_text):
return True
if path.suffix.lower() in CONFIG_SUFFIXES:
return True
if "/cmake/" in normalized:
return True
return False
def _resolve_path(path_text: str, cwd: Path) -> Path:
path = Path(path_text)
if path.is_absolute():
return path
return (cwd / path).resolve()
def _base_name(path: Path) -> str:
for suffix in sorted(SOURCE_SUFFIXES, key=len, reverse=True):
if path.name.lower().endswith(suffix):
return path.name[: -len(suffix)]
return path.stem
def _guarded_paths(paths: list[str], cwd: Path, root: Path) -> list[str]:
missing_tests: list[str] = []
candidate_tests = _candidate_test_paths(paths, cwd, root)
for path_text in paths:
if _is_exempt(path_text):
continue
path = _resolve_path(path_text, cwd)
if path.suffix.lower() not in SOURCE_SUFFIXES:
continue
if not _has_related_test(path, candidate_tests):
missing_tests.append(_base_name(path))
return missing_tests
def main() -> int:
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
return 0
cwd = Path(payload.get("cwd") or Path.cwd())
root = _repo_root(cwd)
missing_tests = _guarded_paths(_touched_paths(payload), cwd, root)
if not missing_tests:
return 0
names = ", ".join(sorted(set(missing_tests)))
print(
json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": (
"TDD GUARD: missing test file for "
f"{names}. Write or add the test first."
),
}
}
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,44 @@
---
name: harness-review
description: Use when reviewing this C++/MSVC Harness repository: local changes, generated phase files, step outputs, implementation diffs, missing tests, MSVC build readiness, or compliance with AGENTS.md, docs/ARCHITECTURE.md, docs/ADR.md, and Harness acceptance criteria.
---
# Harness Review
## Overview
Use this skill to review Harness work against the repository's persistent rules, architecture docs, C++/MSVC constraints, TDD guard policy, and executable verification requirements. Prioritize bugs, regressions, missing tests, and rule violations.
## Review Process
1. Read `/AGENTS.md`, `/docs/ARCHITECTURE.md`, and `/docs/ADR.md`.
2. Inspect the changed files with `git status --short` and `git diff`.
3. Check architecture, stack choices, C++ test coverage, critical rules, and MSVC/CMake readiness.
4. Run relevant verification commands when feasible. If a command cannot be run, report that as residual risk.
5. Lead with actionable findings. Keep summaries secondary.
## Checklist
| Item | Question |
| --- | --- |
| Architecture | Does the change follow `docs/ARCHITECTURE.md` ownership boundaries? |
| Stack | Does the change stay within C++/MSVC/CMake decisions documented in `docs/ADR.md`? |
| Tests | Are new or changed behaviors covered by Python Harness tests or C++ tests? |
| TDD Guard | Would C++ production edits be blocked without related tests? |
| Critical Rules | Does the change violate any `AGENTS.md` CRITICAL rule? |
| Build | Do `python -m unittest discover -s scripts -p "test_*.py"` and `python scripts/validate_workspace.py` pass or provide an expected no-CMake message? |
## Output Format
If there are findings, list them first in severity order with file and line references when possible. Then include this table:
| 항목 | 결과 | 비고 |
| --- | --- | --- |
| 아키텍처 준수 | PASS/FAIL | {상세} |
| 기술 스택 준수 | PASS/FAIL | {상세} |
| 테스트 존재 | PASS/FAIL | {상세} |
| TDD Guard | PASS/FAIL | {상세} |
| CRITICAL 규칙 | PASS/FAIL | {상세} |
| 빌드/검증 가능 | PASS/FAIL | {상세} |
When there are no findings, say that clearly, then mention any commands not run or remaining risk.
@@ -0,0 +1,4 @@
interface:
display_name: "Harness Review"
short_description: "Review Harness changes safely"
default_prompt: "Use $harness-review to review Harness repository changes."
@@ -0,0 +1,129 @@
---
name: harness-workflow
description: Use when planning or running this C++/MSVC Harness framework: reading AGENTS.md and docs/*.md, discussing implementation scope, creating or updating phases/index.json, phases/{task}/index.json, phases/{task}/stepN.md, or invoking scripts/execute.py for staged Codex execution.
---
# Harness Workflow
## Overview
Use this skill to turn a user-approved task into small, self-contained Harness steps that another Codex session can execute reliably. Keep every step grounded in repository docs, C++/MSVC constraints, TDD, and executable acceptance criteria.
## Workflow
1. Read `AGENTS.md` and relevant files under `docs/`, especially `docs/PRD.md`, `docs/ARCHITECTURE.md`, and `docs/ADR.md`.
2. Discuss unresolved product or technical decisions with the user before writing phase files.
3. When the user asks for an implementation plan, draft steps and get approval before creating files.
4. Create or update `phases/index.json`, `phases/{task-name}/index.json`, and one `phases/{task-name}/stepN.md` per step.
5. Run the phase with `python scripts/execute.py {task-name}` when asked to execute it. Use `--push` only when the user asks to push.
## Step Design Rules
- Scope each step to one layer or module. Split steps when multiple modules would otherwise change together.
- Make every step self-contained. Do not rely on prior conversation; include all required context and file paths.
- Force context gathering. Each step must tell Codex which docs and previous outputs to read before editing.
- Specify interfaces and signatures, not full implementations, unless exact code is required for a constraint.
- Put core invariants directly in the step: idempotency, numerical conventions, data integrity, API contracts, or other non-negotiables.
- Use executable acceptance criteria such as `python scripts/validate_workspace.py`, not abstract statements.
- For C++ behavior changes, require tests first and name the expected test file or test executable.
- Name steps with kebab-case slugs such as `project-setup`, `core-types`, or `solver-validation`.
## Phase Files
Create or update `phases/index.json`:
```json
{
"phases": [
{
"dir": "0-mvp",
"status": "pending"
}
]
}
```
Create `phases/{task-name}/index.json`:
```json
{
"project": "FESA Harness",
"phase": "<task-name>",
"steps": [
{ "step": 0, "name": "project-setup", "status": "pending" },
{ "step": 1, "name": "core-types", "status": "pending" },
{ "step": 2, "name": "validation-path", "status": "pending" }
]
}
```
Rules:
- `project` comes from `AGENTS.md`.
- `phase` matches the task directory name.
- `steps[].step` starts at `0`.
- Initial status is always `pending`.
- Do not add timestamps when creating files. `scripts/execute.py` records `created_at`, `started_at`, `completed_at`, `failed_at`, and `blocked_at`.
## Step Template
```markdown
# Step {N}: {name}
## 읽어야 할 파일
먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 설계 의도를 파악하라:
- `/AGENTS.md`
- `/docs/ARCHITECTURE.md`
- `/docs/ADR.md`
- {previously created or modified files}
이전 step에서 만들어진 코드를 꼼꼼히 읽고, 설계 의도를 이해한 뒤 작업하라.
## 작업
{Concrete instructions with file paths, interfaces, signatures, and rules.}
## Tests To Write First
- {Exact C++ or Python test file and behavior to add before implementation.}
## Acceptance Criteria
```bash
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
```
## 검증 절차
1. 위 AC 커맨드를 실행한다.
2. 아키텍처 체크리스트를 확인한다:
- ARCHITECTURE.md 디렉토리 구조를 따르는가?
- ADR 기술 스택을 벗어나지 않았는가?
- AGENTS.md CRITICAL 규칙을 위반하지 않았는가?
- C++ 변경에는 관련 테스트가 존재하는가?
3. 결과에 따라 `phases/{task-name}/index.json`의 해당 step을 업데이트한다:
- 성공: `"status": "completed"`, `"summary": "산출물 한 줄 요약"`
- 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"`
- 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단
## 금지사항
- JavaScript/TypeScript/npm fallback을 추가하지 마라. Reason: 이 Harness는 C++/MSVC 전용이다.
- 기존 테스트를 깨뜨리지 마라.
```
## Execution And Recovery
Run:
```bash
python scripts/execute.py {task-name}
python scripts/execute.py {task-name} --push
```
`scripts/execute.py` creates or checks out `feat-{task-name}`, injects `AGENTS.md` and `docs/*.md` into each prompt, carries completed step summaries forward, retries failed steps up to three times, separates code and metadata commits, and records timestamps.
If a step is `error`, set it back to `pending` and remove `error_message` after fixing the cause. If a step is `blocked`, resolve `blocked_reason`, set it back to `pending`, remove `blocked_reason`, and rerun.
@@ -0,0 +1,4 @@
interface:
display_name: "Harness Workflow"
short_description: "Plan staged Harness workflow steps"
default_prompt: "Use $harness-workflow to plan Harness phases and step files."