modify framework
This commit is contained in:
@@ -1,14 +1,10 @@
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CHECKS = ("lint", "build", "test")
|
||||
|
||||
|
||||
def _repo_root(cwd: Path) -> Path:
|
||||
try:
|
||||
root = subprocess.check_output(
|
||||
@@ -22,25 +18,11 @@ def _repo_root(cwd: Path) -> Path:
|
||||
return Path(root)
|
||||
|
||||
|
||||
def _load_scripts(root: Path) -> dict[str, str]:
|
||||
package_json = root / "package.json"
|
||||
if not package_json.exists():
|
||||
return {}
|
||||
|
||||
try:
|
||||
package = json.loads(package_json.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
_deny(f"Invalid package.json: {exc}")
|
||||
raise SystemExit(0) from exc
|
||||
|
||||
scripts = package.get("scripts", {})
|
||||
if not isinstance(scripts, dict):
|
||||
return {}
|
||||
return {str(name): str(command) for name, command in scripts.items()}
|
||||
|
||||
|
||||
def _is_git_commit(command: str) -> bool:
|
||||
return re.search(r"\bgit(?:\s+(?:-[A-Za-z]\s+\S+|--[A-Za-z0-9-]+(?:=\S+)?))*\s+commit\b", command) is not None
|
||||
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:
|
||||
@@ -64,26 +46,22 @@ def _tail(text: str, limit: int = 1200) -> str:
|
||||
return text[-limit:]
|
||||
|
||||
|
||||
def _run_checks(root: Path, scripts: dict[str, str]) -> str | None:
|
||||
npm = shutil.which("npm") or shutil.which("npm.cmd")
|
||||
if npm is None:
|
||||
return "npm was not found, so pre-commit checks could not run."
|
||||
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"],
|
||||
]
|
||||
|
||||
for check in CHECKS:
|
||||
if check not in scripts:
|
||||
continue
|
||||
result = subprocess.run(
|
||||
[npm, "run", check],
|
||||
cwd=root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
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"npm run {check} failed:\n{details}"
|
||||
return f"npm run {check} failed with exit code {result.returncode}."
|
||||
return f"{label} failed:\n{details}"
|
||||
return f"{label} failed with exit code {result.returncode}."
|
||||
|
||||
return None
|
||||
|
||||
@@ -100,7 +78,7 @@ def main() -> int:
|
||||
|
||||
cwd = Path(payload.get("cwd") or Path.cwd())
|
||||
root = _repo_root(cwd)
|
||||
failure = _run_checks(root, _load_scripts(root))
|
||||
failure = _run_checks(root)
|
||||
if failure:
|
||||
_deny(f"PRE-COMMIT CHECKS: {failure}")
|
||||
|
||||
|
||||
+67
-51
@@ -4,19 +4,9 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SOURCE_SUFFIXES = {".ts", ".tsx", ".js", ".jsx"}
|
||||
TEST_SUFFIXES = ("ts", "tsx", "js", "jsx")
|
||||
CONFIG_SUFFIXES = {".json", ".css", ".scss", ".md", ".yml", ".yaml"}
|
||||
NEXT_SPECIAL_FILES = {
|
||||
"layout.ts",
|
||||
"layout.tsx",
|
||||
"page.ts",
|
||||
"page.tsx",
|
||||
"loading.tsx",
|
||||
"error.tsx",
|
||||
"not-found.tsx",
|
||||
"globals.css",
|
||||
}
|
||||
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:
|
||||
@@ -72,13 +62,66 @@ def _normalize(path_text: str) -> str:
|
||||
def _is_test_path(path_text: str) -> bool:
|
||||
normalized = _normalize(path_text)
|
||||
name = normalized.rsplit("/", 1)[-1]
|
||||
path = Path(path_text)
|
||||
return (
|
||||
"__tests__/" in normalized
|
||||
"/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
|
||||
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:
|
||||
@@ -86,17 +129,13 @@ def _is_exempt(path_text: str) -> bool:
|
||||
path = Path(path_text)
|
||||
name = path.name.lower()
|
||||
|
||||
if _is_test_path(path_text):
|
||||
if name == "cmakelists.txt":
|
||||
return True
|
||||
if name in NEXT_SPECIAL_FILES:
|
||||
if _is_test_path(path_text):
|
||||
return True
|
||||
if path.suffix.lower() in CONFIG_SUFFIXES:
|
||||
return True
|
||||
if ".env" in name or ".config." in name:
|
||||
return True
|
||||
if any(token in name for token in ("tailwind", "postcss", "next.config", "tsconfig")):
|
||||
return True
|
||||
if "/types/" in normalized or name in {"types.ts", "types.d.ts"}:
|
||||
if "/cmake/" in normalized:
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -110,38 +149,15 @@ def _resolve_path(path_text: str, cwd: Path) -> Path:
|
||||
|
||||
|
||||
def _base_name(path: Path) -> str:
|
||||
for suffix in (".tsx", ".ts", ".jsx", ".js"):
|
||||
if path.name.endswith(suffix):
|
||||
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 _has_existing_test(path: Path, root: Path) -> bool:
|
||||
directory = path.parent
|
||||
parent = directory.parent
|
||||
base = _base_name(path)
|
||||
|
||||
for ext in TEST_SUFFIXES:
|
||||
if (directory / f"{base}.test.{ext}").exists():
|
||||
return True
|
||||
if (directory / f"{base}.spec.{ext}").exists():
|
||||
return True
|
||||
|
||||
for ext in TEST_SUFFIXES:
|
||||
if (parent / "__tests__" / f"{base}.test.{ext}").exists():
|
||||
return True
|
||||
if (directory / "__tests__" / f"{base}.test.{ext}").exists():
|
||||
return True
|
||||
|
||||
for ext in TEST_SUFFIXES:
|
||||
if (root / "src" / "__tests__" / f"{base}.test.{ext}").exists():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
@@ -149,7 +165,7 @@ def _guarded_paths(paths: list[str], cwd: Path, root: Path) -> list[str]:
|
||||
path = _resolve_path(path_text, cwd)
|
||||
if path.suffix.lower() not in SOURCE_SUFFIXES:
|
||||
continue
|
||||
if not _has_existing_test(path, root):
|
||||
if not _has_related_test(path, candidate_tests):
|
||||
missing_tests.append(_base_name(path))
|
||||
|
||||
return missing_tests
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
name: harness-review
|
||||
description: Use when reviewing this Harness repository: local changes, generated phase files, step outputs, implementation diffs, missing tests, build readiness, or compliance with AGENTS.md, docs/ARCHITECTURE.md, docs/ADR.md, and Harness acceptance criteria.
|
||||
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, and executable verification requirements. Prioritize bugs, regressions, missing tests, and rule violations.
|
||||
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, tests, critical rules, and build readiness.
|
||||
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.
|
||||
|
||||
@@ -21,11 +21,12 @@ Use this skill to review Harness work against the repository's persistent rules,
|
||||
|
||||
| Item | Question |
|
||||
| --- | --- |
|
||||
| Architecture | Does the change follow `docs/ARCHITECTURE.md` directory and module boundaries? |
|
||||
| Stack | Does the change stay within choices documented in `docs/ADR.md`? |
|
||||
| Tests | Are new or changed behaviors covered by tests? |
|
||||
| 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 relevant build/test/lint commands pass? |
|
||||
| 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
|
||||
|
||||
@@ -36,7 +37,8 @@ If there are findings, list them first in severity order with file and line refe
|
||||
| 아키텍처 준수 | PASS/FAIL | {상세} |
|
||||
| 기술 스택 준수 | PASS/FAIL | {상세} |
|
||||
| 테스트 존재 | PASS/FAIL | {상세} |
|
||||
| TDD Guard | PASS/FAIL | {상세} |
|
||||
| CRITICAL 규칙 | PASS/FAIL | {상세} |
|
||||
| 빌드 가능 | PASS/FAIL | {상세} |
|
||||
| 빌드/검증 가능 | PASS/FAIL | {상세} |
|
||||
|
||||
When there are no findings, say that clearly, then mention any commands not run or remaining risk.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: harness-workflow
|
||||
description: Use when planning or running this 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.
|
||||
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 the workflow grounded in repository docs and executable acceptance criteria.
|
||||
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
|
||||
|
||||
@@ -23,10 +23,10 @@ Use this skill to turn a user-approved task into small, self-contained Harness s
|
||||
- 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, security, data integrity, API contracts, or other non-negotiables.
|
||||
- Use executable acceptance criteria such as `npm run build && npm test`, not abstract statements.
|
||||
- Write cautions concretely: "Do not do X. Reason: Y."
|
||||
- Name steps with kebab-case slugs such as `project-setup`, `api-layer`, or `auth-flow`.
|
||||
- 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
|
||||
|
||||
@@ -47,12 +47,12 @@ Create `phases/{task-name}/index.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"project": "<project-name>",
|
||||
"project": "FESA Harness",
|
||||
"phase": "<task-name>",
|
||||
"steps": [
|
||||
{ "step": 0, "name": "project-setup", "status": "pending" },
|
||||
{ "step": 1, "name": "core-types", "status": "pending" },
|
||||
{ "step": 2, "name": "api-layer", "status": "pending" }
|
||||
{ "step": 2, "name": "validation-path", "status": "pending" }
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -85,11 +85,15 @@ Rules:
|
||||
|
||||
{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
|
||||
npm run build
|
||||
npm test
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## 검증 절차
|
||||
@@ -99,6 +103,7 @@ npm test
|
||||
- ARCHITECTURE.md 디렉토리 구조를 따르는가?
|
||||
- ADR 기술 스택을 벗어나지 않았는가?
|
||||
- AGENTS.md CRITICAL 규칙을 위반하지 않았는가?
|
||||
- C++ 변경에는 관련 테스트가 존재하는가?
|
||||
3. 결과에 따라 `phases/{task-name}/index.json`의 해당 step을 업데이트한다:
|
||||
- 성공: `"status": "completed"`, `"summary": "산출물 한 줄 요약"`
|
||||
- 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"`
|
||||
@@ -106,7 +111,7 @@ npm test
|
||||
|
||||
## 금지사항
|
||||
|
||||
- {Do not do X. Reason: Y.}
|
||||
- JavaScript/TypeScript/npm fallback을 추가하지 마라. Reason: 이 Harness는 C++/MSVC 전용이다.
|
||||
- 기존 테스트를 깨뜨리지 마라.
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user