modify coding template

This commit is contained in:
NINI
2026-05-09 02:20:01 +09:00
parent 38291723f0
commit a1aea6a449
55 changed files with 437 additions and 2862 deletions
@@ -1,11 +0,0 @@
name = "harness_reviewer"
description = "Read-only reviewer for Harness projects, focused on architecture drift, critical rule violations, and missing validation."
model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
developer_instructions = """
Review changes like a repository owner.
Prioritize correctness, architecture compliance, behavior regressions, and missing tests over style.
Always compare the patch against AGENTS.md, docs/ARCHITECTURE.md, docs/ADR.md, and the requested acceptance criteria.
Lead with concrete findings and file references. If no material issues are found, say so explicitly and mention residual risks.
"""
@@ -1,12 +0,0 @@
name = "phase_planner"
description = "Read-heavy Harness planner that decomposes docs into minimal, self-contained phase and step files."
model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
developer_instructions = """
Plan before implementing.
Read AGENTS.md and the docs directory, identify the smallest coherent phase boundaries, and draft self-contained steps.
Keep each step scoped to one layer or one module when possible.
Do not make code changes unless the parent agent explicitly asks you to write files.
Return concrete file paths, acceptance commands, and blocking assumptions.
"""
+1 -6
View File
@@ -1,9 +1,4 @@
# Project-scoped Codex defaults for the Harness template.
# As of 2026-04-15, hooks are experimental and disabled on native Windows.
#:schema https://developers.openai.com/codex/config-schema.json
[features]
codex_hooks = true
[agents]
max_threads = 6
max_depth = 1
+9 -9
View File
@@ -2,24 +2,24 @@
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"",
"statusMessage": "Checking risky shell 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"
}
]
}
],
"Stop": [
},
{
"matcher": "^(apply_patch|Edit|Write)$",
"hooks": [
{
"type": "command",
"command": "python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/stop_continue.py\"",
"statusMessage": "Running Harness validation",
"timeout": 300
"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,111 @@
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(
["git", "rev-parse", "--show-toplevel"],
cwd=cwd,
text=True,
stderr=subprocess.DEVNULL,
).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
return cwd
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
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 _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."
for check in CHECKS:
if check not in scripts:
continue
result = subprocess.run(
[npm, "run", check],
cwd=root,
capture_output=True,
text=True,
)
if result.returncode != 0:
details = _tail(result.stdout + "\n" + result.stderr)
if details:
return f"npm run {check} failed:\n{details}"
return f"npm run {check} 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, _load_scripts(root))
if failure:
_deny(f"PRE-COMMIT CHECKS: {failure}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,47 +0,0 @@
#!/usr/bin/env python3
"""Block obviously destructive shell commands before Codex runs them."""
from __future__ import annotations
import json
import re
import sys
BLOCK_PATTERNS = (
r"\brm\s+-rf\b",
r"\bgit\s+push\s+--force(?:-with-lease)?\b",
r"\bgit\s+reset\s+--hard\b",
r"\bDROP\s+TABLE\b",
r"\btruncate\s+table\b",
r"\bRemove-Item\b.*\b-Recurse\b",
r"\bdel\b\s+/s\b",
)
def main() -> int:
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
return 0
command = payload.get("tool_input", {}).get("command", "")
for pattern in BLOCK_PATTERNS:
if re.search(pattern, command, re.IGNORECASE):
json.dump(
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Harness guardrail blocked a risky shell command.",
}
},
sys.stdout,
)
return 0
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,55 +0,0 @@
#!/usr/bin/env python3
"""Run repository validation when a Codex turn stops and request one more pass if it fails."""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
def main() -> int:
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
return 0
if payload.get("stop_hook_active"):
return 0
root = Path(payload.get("cwd") or ".").resolve()
validator = root / "scripts" / "validate_workspace.py"
if not validator.exists():
return 0
result = subprocess.run(
[sys.executable, str(validator)],
cwd=root,
capture_output=True,
text=True,
timeout=240,
)
if result.returncode == 0:
return 0
summary = (result.stdout or result.stderr or "workspace validation failed").strip()
if len(summary) > 1200:
summary = summary[:1200].rstrip() + "..."
json.dump(
{
"decision": "block",
"reason": (
"Validation failed. Review the output, fix the repo, then continue.\n\n"
f"{summary}"
),
},
sys.stdout,
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+189
View File
@@ -0,0 +1,189 @@
import json
import subprocess
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",
}
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]
return (
"__tests__/" in normalized
or ".test." in name
or ".spec." in name
or "test" in name
or "spec" in name
)
def _is_exempt(path_text: str) -> bool:
normalized = _normalize(path_text)
path = Path(path_text)
name = path.name.lower()
if _is_test_path(path_text):
return True
if name in NEXT_SPECIAL_FILES:
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"}:
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 (".tsx", ".ts", ".jsx", ".js"):
if path.name.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] = []
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_existing_test(path, root):
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())