From b2b2632fc502c950b25d0ff9a39fedf33d8c0989 Mon Sep 17 00:00:00 2001 From: Son Seong Jun <166786347+SonAIengine@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:26:05 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A4=91=EA=B0=84=20=EC=A0=90=EA=B2=80?= =?UTF-8?q?=EC=9A=A9=20=ED=95=A9=EC=84=B1=20=EA=B3=BC=EC=A0=9C=208?= =?UTF-8?q?=EA=B0=9C=EC=99=80=20=EC=A0=95=EB=8B=B5=C2=B7=EC=82=AC=EC=A0=84?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=EA=B3=A0=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=EC=9D=B8=EC=A6=9D=20=EC=B0=A8=EB=8B=A8=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MEMORY.md | 2 + evals/created-tasks-2026-10-31/README.md | 72 +++++ evals/created-tasks-2026-10-31/cases.json | 267 ++++++++++++++++++ evals/created-tasks-2026-10-31/check.py | 114 ++++++++ .../oracle-fixes.json | 28 ++ evals/created-tasks-2026-10-31/preflight.json | 92 ++++++ 6 files changed, 575 insertions(+) create mode 100644 evals/created-tasks-2026-10-31/README.md create mode 100644 evals/created-tasks-2026-10-31/cases.json create mode 100644 evals/created-tasks-2026-10-31/check.py create mode 100644 evals/created-tasks-2026-10-31/oracle-fixes.json create mode 100644 evals/created-tasks-2026-10-31/preflight.json diff --git a/MEMORY.md b/MEMORY.md index 0d89d82..a161fcc 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -158,3 +158,5 @@ - 상태 폴더 권한 진단(2026-10-31, ADR-0080, 구현·로컬 검증 완료): Unix mode의 group/other 비트 거절은 유지하고, root·runs·개별 Run 폴더 오류를 `state_directory_permissions`/exit64로 전달한다. CLI run/resume/usage/recover·REPL에서 문제 경로·현재 mode·`chmod go-rwx -- PATH` 안내를 표시하며 자동 chmod·재귀 수정은 없다. 명시적 로컬 진단만 경로를 escape해 표시하고 일반 Debug·오류 Display·journal·모델·progress에서는 가린다. 설계 root0775/runs0755와 별도 run0770·symlink·space·개행/ESC·비UTF8 경로를 검증했다. 사용자가 직접0700으로 고치면 권한 검사 다음 단계로 진행하며 fixture DB는 생성되지 않는다. 전체 Rust 회귀778개(4 ignored)·Clippy·fmt·protocol·공개 문서·배포 workflow 검사를 통과했다. 소유자는 xgen CLI이며 외부 도구 변경은 없다. - 상태 폴더 진단 변경 추적: [PR #137](https://github.com/PlateerLab/xgen-cli/pull/137), 코드 `7edf540`, [전체 CI](https://github.com/PlateerLab/xgen-cli/actions/runs/41077456833). 실제 외부 모델·다른 OS의 terminal 화면 실측은 하지 않았다. - 상태 폴더 진단의 [Linux PR 검사](https://github.com/PlateerLab/xgen-cli/actions/runs/41077575969)가 통과했다. 플랫폼 전체 결과는 위 전체 CI run을 참조한다. + +- 자체 작업 중간 점검(2026-10-31, 준비·과제 검증 완료 / 실제 모델 실행은 인증으로 대기): 사용자가 과제도 직접 만들도록 요청해 [합성 과제 8개](evals/created-tasks-2026-10-31/README.md)를 추가했다. 페이지네이션·설정 병합·CSV·CLI delimiter·세 파일 rename·지침/경로·CLI 조사/문서 수정·worktree 탐색을 새 workspace/state에서 각2회 측정할 계획이다. 원본은 8/8 실패하고 정답 수정본은 공개/숨김 테스트·빌드·보호 파일 검사8/8 통과했다. 실제 xgen 실행 gate는 활성 profile `authentication_rejected`, 별도 default는 `credential_not_found`였고 모델 작업 호출은0회다. 완료율·반복·token·모델 시간은 미측정이다. API key는 채팅으로 받지 않고 로컬 model setup이 필요하다. 기존 evaluator를 재사용하며 엔진·프롬프트 변경은 없다. 지침 답변 prefix와 서로 다른 step의 동일 명령 반복은 기존 runner의 자동 pass 판정 밖이라 실제 결과에서 별도 검토해야 한다. diff --git a/evals/created-tasks-2026-10-31/README.md b/evals/created-tasks-2026-10-31/README.md new file mode 100644 index 0000000..9ccb9c6 --- /dev/null +++ b/evals/created-tasks-2026-10-31/README.md @@ -0,0 +1,72 @@ +# 직접 만든 작업 8개로 xgen 중간 점검 + +- 기준일: 2026-10-31 +- 상태: 과제 사전 검증 8/8 통과, 실제 모델 실행은 인증 오류로 미실행. +- 성격: 실제 업무를 본뜬 합성 과제다. 사용자 실제 저장소 8개를 성공한 결과가 아니다. +- 범위: 평가 fixture와 기록만 추가하며 xgen 엔진·프롬프트·기본값은 바꾸지 않는다. + +## 과제 + +| 과제 | 확인할 작업 | 실행 방식 | +| --- | --- | --- | +| pagination | 마지막 페이지와 경계 입력의 cursor 버그 수정 | REPL | +| config-precedence | false·0·빈 문자열을 잃는 설정 병합 수정 | run → execute 승인 대기 → resume | +| csv-export | 쉼표·따옴표·개행·Unicode CSV 출력 수정 | REPL | +| cli-delimiter | `--` 뒤의 옵션 모양 파일명 처리 | run → execute 승인 대기 → resume | +| multi-file-rename | 모델·출력·호출부 세 파일의 필드 이름 변경 | REPL | +| safe-relative-path | 경로 정규화 수정과 AGENTS.md 지침 준수 | REPL | +| docs-cli-audit | CLI 구현·help를 조사해 README만 수정 | REPL | +| git-worktree-discovery | `.git`이 파일인 worktree도 탐색 | REPL | + +각 과제는 외부 패키지가 필요 없는 Python·Node 프로젝트다. 테스트·설정·지침 파일은 보호하고 +지정한 수정 대상만 바꾸도록 요청한다. 숨김 테스트는 모델 workspace에 넣지 않고 별도 validation copy에서 +실행한다. `oracle-fixes.json`은 과제 검증용 정답이며 모델 workspace에 복사하지 않는다. + +## 사전 고정과 판정 + +- 각 과제를 새 workspace·state에서 두 번씩, 총 16회 실행한다. 실패를 지우거나 성공할 때까지 재시도하지 않는다. +- 기존 `scripts/evaluate-projects.py`를 그대로 사용한다. 현재 두 실행 방식 모두 24 model-turn budget이며 + run/resume은 invocation당 256 ticks다. 개별 subprocess timeout은 240초다. +- 완료 candidate뿐 아니라 독립 공개·숨김 테스트, 빌드, 보호 파일 보존, 완료의 offline replay와 + journal/usage 불변성을 확인한다. run/resume은 execute 승인 전 실행이 없었는지도 확인한다. +- 호출 수·tool effect 수·Unknown·중복 effect 시작·소요 시간·입출력 token과 cache usage를 기록한다. + 같은 명령을 서로 다른 step으로 반복한 수와 자연어 답변의 사실성 전체는 기존 runner가 자동 판정하지 않는다. + 필요하면 해당 Run의 recipe·output·최종 답변을 별도 검토한다. +- safe-relative-path의 `CHECKPOINT:` 답변 접두 규칙은 최종 답변과 비교해 별도로 확인한다. + 기존 runner의 기능 pass에는 이 접두 규칙 판정이 포함되지 않는다. +- 첫 네 과제는 design, 뒤 네 과제는 held_out으로 표시했다. 이번은 수정 전 기준점 측정이며 A/B 개선 주장은 없다. + 모두 결과를 분석한 뒤에는 새로운 엔진 변경을 검증할 untouched holdout으로 재사용하지 않는다. +- 이 실행은 pipe REPL·headless 실행 점검이다. 실제 TUI 키 입력·화면 렌더링은 별도 + `scripts/smoke-inline-tui.py` 검사와 구분한다. + +## 실행 + +과제만 검증하며 모델 API를 호출하지 않는다. + +```bash +python3 evals/created-tasks-2026-10-31/check.py +``` + +원본 실패, 정답 수정 뒤 공개·숨김 테스트/빌드 통과, 보호 파일 보존을 확인한다. +로그·사전 등록은 새 0700 임시 폴더에 남긴다. binary·cases·oracle·runner hash를 먼저 기록한다. + +실제 모델로 실행하려면 로컬 터미널에서 `xgen model setup`으로 활성 profile의 인증을 설정한 뒤 실행한다. +인증 정보는 채팅이나 저장소에 넣지 않는다. 기존 runner는 API 환경 override를 제거하므로 +이번 실행도 같은 환경 규칙으로 profile 검사를 한다. + +```bash +python3 evals/created-tasks-2026-10-31/check.py --execute +``` + +모델 검사가 실패하면 작업을 시작하지 않고 실패 code와 모델 호출 0회를 기록한다. +모델 검사는 catalog GET이며 실제 planning 호출과 구분한다. +모델 검사가 통과하면 기존 evaluator가 별도 `REPORT_ROOT` 아래 실제 실행 결과를 남긴다. + +## 이번에 확인한 결과 + +[사전 검증 기록](preflight.json)의 8개 과제 모두 원본 실패·정답 수정 뒤 공개/숨김 테스트·빌드·보호 파일 +검사가 통과했다. 실제 실행 gate는 `authentication_rejected`로 닫혔다. 별도의 default profile 검사도 +`credential_not_found`였다. 이 환경에는 해당 모델용 API key 환경변수가 없었다. + +실제 모델의 작업 완료율·반복 행동·token 사용량·모델 응답 시간은 아직 측정하지 않았다. +사전 검증의 8/8을 xgen의 성공률로 해석하지 않는다. diff --git a/evals/created-tasks-2026-10-31/cases.json b/evals/created-tasks-2026-10-31/cases.json new file mode 100644 index 0000000..5f0423f --- /dev/null +++ b/evals/created-tasks-2026-10-31/cases.json @@ -0,0 +1,267 @@ +[ + { + "id": "pagination", + "title": "마지막 페이지의 불필요한 다음 cursor 수정", + "split": "design", + "language": "python", + "mode": "bare", + "source": "pages.py", + "sources": [ + "pages.py" + ], + "files": { + "pages.py": "def page(items, cursor, limit):\n end = cursor + limit\n return {\"items\": items[cursor:end], \"next\": end}\n", + "test_visible.py": "import unittest\nfrom pages import page\nclass Tests(unittest.TestCase):\n def test_last(self): self.assertEqual(page([1,2,3],2,2), {\"items\":[3],\"next\":None})\n" + }, + "commands": [ + [ + "python3", + "-m", + "unittest", + "-q" + ], + [ + "python3", + "-m", + "compileall", + "-q", + "." + ] + ], + "hidden_path": "test_hidden.py", + "hidden_tests": "import unittest\nfrom pages import page\nclass Hidden(unittest.TestCase):\n def test_edges(self):\n self.assertEqual(page([],0,4), {\"items\":[],\"next\":None})\n self.assertEqual(page([1,2,3],0,2)[\"next\"],2)\n self.assertIsNone(page([1],8,2)[\"next\"])\n with self.assertRaises(ValueError): page([1],0,0)\n with self.assertRaises(ValueError): page([1],-1,2)\n", + "contract": "items와 next를 반환한다. 다음 페이지에 항목이 남을 때만 next는 정수이고 그 밖에는 None이다. limit<=0 또는 cursor<0이면 ValueError.", + "response_mode": "summary" + }, + { + "id": "config-precedence", + "title": "설정 우선순위에서 false·0·빈 문자열 보존", + "split": "design", + "language": "node", + "mode": "run-resume", + "source": "source.mjs", + "files": { + "source.mjs": "export function resolve(cli, env, defaults) {\n const result={};\n for (const key of Object.keys(defaults)) result[key]=cli[key] || env[key] || defaults[key];\n return result;\n}\n", + "visible.test.mjs": "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport {resolve} from './source.mjs';\ntest('false and zero',()=>assert.deepEqual(resolve({verbose:false,retries:0},{verbose:true,retries:4},{verbose:true,retries:3}),{verbose:false,retries:0}));\n" + }, + "commands": [ + [ + "node", + "--test", + "visible.test.mjs" + ], + [ + "node", + "--check", + "source.mjs" + ] + ], + "hidden_path": "hidden.test.mjs", + "hidden_tests": "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport {resolve} from './source.mjs';\ntest('empty and null fallback',()=>{assert.deepEqual(resolve({name:''},{name:'env'},{name:'default'}),{name:''});assert.deepEqual(resolve({name:null},{name:'env'},{name:'default'}),{name:'env'});assert.deepEqual(resolve({}, {}, {name:'default'}),{name:'default'});});\n", + "contract": "defaults에 있는 key만 반환한다. CLI > env > defaults 순서이고 null/undefined만 미지정이다. false·0·빈 문자열은 값을 유지한다.", + "response_mode": "summary" + }, + { + "id": "csv-export", + "title": "CSV의 쉼표·따옴표·개행 처리", + "split": "design", + "language": "python", + "mode": "bare", + "source": "exporter.py", + "sources": [ + "exporter.py" + ], + "files": { + "exporter.py": "def export(rows):\n return \"\\n\".join(\",\".join(str(v) for v in row) for row in rows) + \"\\n\"\n", + "test_visible.py": "import unittest\nimport csv, io\nfrom exporter import export\nclass Tests(unittest.TestCase):\n def test_comma(self): self.assertEqual(list(csv.reader(io.StringIO(export([[\"a,b\",2]])))), [[\"a,b\",\"2\"]])\n" + }, + "commands": [ + [ + "python3", + "-m", + "unittest", + "-q" + ], + [ + "python3", + "-m", + "compileall", + "-q", + "." + ] + ], + "hidden_path": "test_hidden.py", + "hidden_tests": "import unittest\nimport csv, io\nfrom exporter import export\nclass Hidden(unittest.TestCase):\n def test_roundtrip(self):\n rows=[[\"한글\", \"quote\\\"\", \"line\\nnext\"], [\"\", \"x\", \"y\"]]\n self.assertEqual(list(csv.reader(io.StringIO(export(rows)))),rows)\n self.assertEqual(export([]), \"\")\n", + "contract": "표준 CSV 규칙으로 값을 str로 변환해 출력한다. 쉼표·따옴표·개행·Unicode·빈 문자열을 보존하고 입력 행이 없으면 빈 문자열을 반환한다.", + "response_mode": "summary" + }, + { + "id": "cli-delimiter", + "title": "CLI에서 -- 뒤의 옵션 모양 파일명 처리", + "split": "design", + "language": "node", + "mode": "run-resume", + "source": "source.mjs", + "files": { + "source.mjs": "export function parse(args) { const out={force:false,files:[]}; for(const arg of args) {if(arg==='--force') out.force=true; else if(arg!=='--') out.files.push(arg);} return out;}\n", + "visible.test.mjs": "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport {parse} from './source.mjs';\ntest('delimiter',()=>assert.deepEqual(parse(['--','--force']),{force:false,files:['--force']}));\n" + }, + "commands": [ + [ + "node", + "--test", + "visible.test.mjs" + ], + [ + "node", + "--check", + "source.mjs" + ] + ], + "hidden_path": "hidden.test.mjs", + "hidden_tests": "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport {parse} from './source.mjs';\ntest('edges',()=>{assert.deepEqual(parse(['--force','a','--','--','-b']),{force:true,files:['a','--','-b']});assert.deepEqual(parse([]),{force:false,files:[]});});\n", + "contract": "첫 -- 이전의 --force만 옵션이다. 첫 --는 제거하고 뒤의 --force나 --를 포함한 모든 인자는 파일명이다.", + "response_mode": "summary" + }, + { + "id": "multi-file-rename", + "title": "모델·출력·호출부의 필드 이름 변경", + "split": "held_out", + "language": "python", + "mode": "bare", + "source": "model.py", + "sources": [ + "model.py", + "report.py", + "app.py" + ], + "files": { + "model.py": "from dataclasses import dataclass\n@dataclass\nclass Product:\n name: str\n stock_count: int\n", + "test_visible.py": "import unittest\nfrom model import Product\nfrom report import as_dict\nclass Tests(unittest.TestCase):\n def test_new_field(self): self.assertEqual(as_dict(Product(\"item\",3)), {\"name\":\"item\",\"available_count\":3})\n", + "report.py": "def as_dict(product):\n return {\"name\":product.name,\"stock_count\":product.stock_count}\n", + "app.py": "from model import Product\nfrom report import as_dict\ndef summarize(name,count):\n return as_dict(Product(name=name,stock_count=count))\n" + }, + "commands": [ + [ + "python3", + "-m", + "unittest", + "-q" + ], + [ + "python3", + "-m", + "compileall", + "-q", + "." + ] + ], + "hidden_path": "test_hidden.py", + "hidden_tests": "import unittest\nfrom pathlib import Path\nfrom app import summarize\nclass Hidden(unittest.TestCase):\n def test_all_uses(self):\n self.assertEqual(summarize(\"x\",0), {\"name\":\"x\",\"available_count\":0})\n for name in [\"model.py\",\"report.py\",\"app.py\"]: self.assertNotIn(\"stock_count\",Path(name).read_text())\n", + "contract": "stock_count를 available_count로 변경한다. 속성·JSON key·constructor 호출을 통일하고 대상 세 파일에 옛 이름을 남기지 않는다.", + "response_mode": "summary" + }, + { + "id": "safe-relative-path", + "title": "경로 정규화와 저장소 지침 준수", + "split": "held_out", + "language": "python", + "mode": "bare", + "source": "paths.py", + "sources": [ + "paths.py" + ], + "files": { + "paths.py": "def normalize(value):\n return value.strip(\"/\")\n", + "test_visible.py": "import unittest\nfrom paths import normalize\nclass Tests(unittest.TestCase):\n def test_normalize(self): self.assertEqual(normalize(\"a/../b//c\"),\"b/c\")\n", + "AGENTS.md": "테스트는 python3 scripts/test.py로만 실행한다. unittest를 직접 호출하지 않는다. 최종 답변은 CHECKPOINT:로 시작한다.\n", + "scripts/test.py": "import unittest\nresult=unittest.TextTestRunner().run(unittest.defaultTestLoader.discover(\".\"))\nraise SystemExit(not result.wasSuccessful())\n" + }, + "commands": [ + [ + "python3", + "scripts/test.py" + ], + [ + "python3", + "-m", + "compileall", + "-q", + "." + ] + ], + "hidden_path": "test_hidden.py", + "hidden_tests": "import unittest\nfrom paths import normalize\nclass Hidden(unittest.TestCase):\n def test_boundary(self):\n self.assertEqual(normalize(\"\"),\".\")\n self.assertEqual(normalize(\"a..b\"),\"a..b\")\n for value in [\"/absolute\",\"../escape\",\"a/../../escape\"]:\n with self.assertRaises(ValueError): normalize(value)\n", + "contract": "POSIX 상대 경로를 정규화한다. 절대 경로와 정규화 후 부모로 탈출하는 경로는 ValueError, 빈 문자열은 .이다.", + "response_mode": "summary" + }, + { + "id": "docs-cli-audit", + "title": "CLI 구현을 조사해 잘못된 README 수정", + "split": "held_out", + "language": "python", + "mode": "bare", + "source": "README.md", + "sources": [ + "README.md" + ], + "files": { + "README.md": "# Export tool\n\nRun `python3 cli.py --destination out.csv`. Use `--force` to overwrite.\n", + "test_visible.py": "import unittest\nfrom pathlib import Path\nclass Tests(unittest.TestCase):\n def test_current_option(self): self.assertIn(\"--output\",Path(\"README.md\").read_text())\n", + "cli.py": "import argparse\np=argparse.ArgumentParser()\np.add_argument(\"--output\",required=True)\np.add_argument(\"--overwrite\",action=\"store_true\")\nif __name__==\"__main__\": p.parse_args()\n" + }, + "commands": [ + [ + "python3", + "-m", + "unittest", + "-q" + ], + [ + "python3", + "-m", + "compileall", + "-q", + "." + ] + ], + "hidden_path": "test_hidden.py", + "hidden_tests": "import unittest\nfrom pathlib import Path\nimport subprocess, sys\nclass Hidden(unittest.TestCase):\n def test_documented_flags(self):\n text=Path(\"README.md\").read_text()\n help_text=subprocess.check_output([sys.executable,\"cli.py\",\"--help\"],text=True)\n for flag in [\"--output\",\"--overwrite\"]:\n self.assertIn(flag,text); self.assertIn(flag,help_text)\n for old in [\"--destination\",\"--force\"]: self.assertNotIn(old,text)\n", + "contract": "cli.py 구현과 --help를 조사해 README 실행 예를 실제 CLI에 맞춘다. CLI 코드와 테스트는 수정하지 않는다.", + "response_mode": "summary" + }, + { + "id": "git-worktree-discovery", + "title": "일반 저장소와 worktree 탐색", + "split": "held_out", + "language": "python", + "mode": "bare", + "source": "discover.py", + "sources": [ + "discover.py" + ], + "files": { + "discover.py": "from pathlib import Path\nimport os\ndef find_repositories(root):\n found=[]\n for base, dirs, files in os.walk(root):\n if (Path(base)/\".git\").is_dir():\n found.append(str(Path(base).relative_to(root))); dirs.clear()\n return sorted(found)\n", + "test_visible.py": "import unittest\nimport tempfile\nfrom pathlib import Path\nfrom discover import find_repositories\nclass Tests(unittest.TestCase):\n def test_both(self):\n with tempfile.TemporaryDirectory() as folder:\n root=Path(folder); (root/\"ordinary\"/\".git\").mkdir(parents=True); (root/\"worktree\").mkdir(); (root/\"worktree\"/\".git\").write_text(\"gitdir: elsewhere\")\n self.assertEqual(find_repositories(root),[\"ordinary\",\"worktree\"])\n" + }, + "commands": [ + [ + "python3", + "-m", + "unittest", + "-q" + ], + [ + "python3", + "-m", + "compileall", + "-q", + "." + ] + ], + "hidden_path": "test_hidden.py", + "hidden_tests": "import unittest\nimport tempfile\nfrom pathlib import Path\nfrom discover import find_repositories\nclass Hidden(unittest.TestCase):\n def test_nested(self):\n with tempfile.TemporaryDirectory() as folder:\n root=Path(folder); (root/\"group\"/\"repo\"/\".git\").mkdir(parents=True); (root/\"group\"/\"empty\").mkdir(); (root/\"group\"/\"repo\"/\"child\"/\".git\").mkdir(parents=True)\n self.assertEqual(find_repositories(root),[\"group/repo\"])\n", + "contract": "디렉터리인 .git과 파일인 .git을 모두 저장소로 찾는다. 저장소 내부로 더 탐색하지 않고 root 기준 상대 경로를 정렬해 반환한다.", + "response_mode": "summary" + } +] diff --git a/evals/created-tasks-2026-10-31/check.py b/evals/created-tasks-2026-10-31/check.py new file mode 100644 index 0000000..172ed05 --- /dev/null +++ b/evals/created-tasks-2026-10-31/check.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Validate the frozen synthetic task catalog; optionally run the existing live evaluator.""" +import argparse +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parent +REPO = ROOT.parents[1] +RUNNER = REPO / 'scripts/evaluate-projects.py' + + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def execute(command, directory, environment=None): + return subprocess.run(command, cwd=directory, env=environment, capture_output=True, + text=True, timeout=60) + + +def preflight(cases, fixes, report_root): + spec = importlib.util.spec_from_file_location('project_eval', RUNNER) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + module.validate_cases(cases) + rows = [] + for case in cases: + folder = report_root / case['id'] + folder.mkdir() + for name, content in case['files'].items(): + path = folder / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + before = execute(case['commands'][0], folder) + allowed = set(case.get('sources', [case['source']])) + if set(fixes[case['id']]) != allowed: + raise ValueError('oracle must replace exactly the allowed files') + for name, content in fixes[case['id']].items(): + (folder / name).write_text(content) + visible = execute(case['commands'][0], folder) + build = execute(case['commands'][1], folder) + (folder / case['hidden_path']).write_text(case['hidden_tests']) + hidden_command = (['node', '--test', 'visible.test.mjs', case['hidden_path']] + if case['language'] == 'node' else case['commands'][0]) + hidden = execute(hidden_command, folder) + preserved = all((folder / name).read_text() == content + for name, content in case['files'].items() if name not in allowed) + row = {'case': case['id'], 'initial_tests_failed': before.returncode != 0, + 'reference_visible_passed': visible.returncode == 0, + 'reference_build_passed': build.returncode == 0, + 'reference_hidden_passed': hidden.returncode == 0, + 'protected_files_preserved': preserved} + row['pass'] = all(value for key, value in row.items() if key != 'case') + rows.append(row) + # Local diagnostic files are not model observations or public evaluation results. + (folder / 'preflight.log').write_text('\n'.join( + f'{label}\n{result.stdout}\n{result.stderr}' + for label, result in [('before', before), ('visible', visible), ('build', build), ('hidden', hidden)])) + return rows + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--execute', action='store_true', help='Use the actual configured model after preflight') + parser.add_argument('--binary', type=Path, default=REPO / 'target/debug/xgen') + args = parser.parse_args() + cases = json.loads((ROOT / 'cases.json').read_text()) + fixes = json.loads((ROOT / 'oracle-fixes.json').read_text()) + report_root = Path(tempfile.mkdtemp(prefix='xgen-created-tasks-')) + registration = {'source_commit': subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=REPO, text=True).strip(), + 'cases_sha256': sha(ROOT / 'cases.json'), 'oracle_sha256': sha(ROOT / 'oracle-fixes.json'), + 'runner_sha256': sha(RUNNER), 'preflight_sha256': sha(Path(__file__)), + 'binary_sha256': sha(args.binary), 'attempts': 2, 'timeout_seconds': 240, + 'task_count': len(cases), 'status': 'preflight_only', 'live_model_calls': 0} + rows = preflight(cases, fixes, report_root) + report = {'registration': registration, 'preflight': rows} + (report_root / 'preflight.json').write_text(json.dumps(report, ensure_ascii=False, indent=2) + '\n') + print(json.dumps({'report_root': str(report_root), 'preflight_passed': sum(row['pass'] for row in rows), + 'tasks': len(rows)}, ensure_ascii=False), flush=True) + if not all(row['pass'] for row in rows): + return 1 + if args.execute: + # Match the existing evaluator's environment rules: auth must be in the active profile. + environment = {key: value for key, value in os.environ.items() + if not key.startswith(('XGEN_', 'XGENY_', 'DEEPSEEK_'))} + try: + checked = execute([str(args.binary.resolve()), 'model', 'check'], REPO, environment) + codes = re.findall(r'reason=([a-z_]+)', checked.stdout + checked.stderr) + except subprocess.TimeoutExpired: + checked = None + codes = ['model_check_timeout'] + if checked is None or checked.returncode: + registration.update(status='blocked_model_configuration', check_codes=codes) + (report_root / 'preflight.json').write_text(json.dumps(report, ensure_ascii=False, indent=2) + '\n') + print(json.dumps({'status': registration['status'], 'reason_codes': codes, + 'live_model_calls': 0}), flush=True) + return 2 + # No retries of failed cases: each attempt gets its own fresh project/state. + registration.update(status='live_evaluator_started', live_model_calls=None) + (report_root / 'preflight.json').write_text(json.dumps(report, ensure_ascii=False, indent=2) + '\n') + return subprocess.call(['python3', str(RUNNER), '--binary', str(args.binary.resolve()), + '--cases', str(ROOT / 'cases.json'), '--attempts', '2', '--timeout', '240'], cwd=REPO) + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/evals/created-tasks-2026-10-31/oracle-fixes.json b/evals/created-tasks-2026-10-31/oracle-fixes.json new file mode 100644 index 0000000..ec32b2c --- /dev/null +++ b/evals/created-tasks-2026-10-31/oracle-fixes.json @@ -0,0 +1,28 @@ +{ + "pagination": { + "pages.py": "def page(items, cursor, limit):\n if limit <= 0 or cursor < 0:\n raise ValueError(\"invalid page\")\n end = cursor + limit\n return {\"items\": items[cursor:end], \"next\": end if end < len(items) else None}\n" + }, + "config-precedence": { + "source.mjs": "export function resolve(cli, env, defaults) {\n const result={};\n for (const key of Object.keys(defaults)) result[key]=cli[key] ?? env[key] ?? defaults[key];\n return result;\n}\n" + }, + "csv-export": { + "exporter.py": "import csv\nimport io\ndef export(rows):\n out=io.StringIO(newline=\"\")\n csv.writer(out, lineterminator=\"\\n\").writerows(rows)\n return out.getvalue()\n" + }, + "cli-delimiter": { + "source.mjs": "export function parse(args) { const out={force:false,files:[]}; let literal=false; for(const arg of args) {if(!literal && arg==='--') literal=true; else if(!literal && arg==='--force') out.force=true; else out.files.push(arg);} return out;}\n" + }, + "multi-file-rename": { + "model.py": "from dataclasses import dataclass\n@dataclass\nclass Product:\n name: str\n available_count: int\n", + "report.py": "def as_dict(product):\n return {\"name\":product.name,\"available_count\":product.available_count}\n", + "app.py": "from model import Product\nfrom report import as_dict\ndef summarize(name,count):\n return as_dict(Product(name=name,available_count=count))\n" + }, + "safe-relative-path": { + "paths.py": "import posixpath\ndef normalize(value):\n if value.startswith(\"/\"):\n raise ValueError(\"absolute\")\n result=posixpath.normpath(value)\n if result==\"..\" or result.startswith(\"../\"):\n raise ValueError(\"escape\")\n return result\n" + }, + "docs-cli-audit": { + "README.md": "# Export tool\n\nRun `python3 cli.py --output out.csv`. Use `--overwrite` to overwrite.\n" + }, + "git-worktree-discovery": { + "discover.py": "from pathlib import Path\nimport os\ndef find_repositories(root):\n found=[]\n for base, dirs, files in os.walk(root):\n marker=Path(base)/\".git\"\n if marker.is_dir() or marker.is_file():\n found.append(str(Path(base).relative_to(root))); dirs.clear()\n return sorted(found)\n" + } +} diff --git a/evals/created-tasks-2026-10-31/preflight.json b/evals/created-tasks-2026-10-31/preflight.json new file mode 100644 index 0000000..b1ea552 --- /dev/null +++ b/evals/created-tasks-2026-10-31/preflight.json @@ -0,0 +1,92 @@ +{ + "registration": { + "source_commit": "c8bacaedc83e6a33d52d46e295c5e11ce0267d36", + "cases_sha256": "83ae1d36460b5554aaccd3f677dafa47da7f1de7d6327bf37442aa5154cb7be1", + "oracle_sha256": "9b9e044befef1f432c94abf18882d89d54b264497f38b8fcd86cfbaf069d5f02", + "runner_sha256": "ca3894897968f7bc45aa8fbcf6581a0eb77994590293b5f0d23c7d540ad130fa", + "preflight_sha256": "5e4d1455be7895d607304ebf82e4466731bb11eb0a95808ffc91260105a1e01c", + "binary_sha256": "ed29b6d91a2cf4dddfb71e51a06daa618e178054fc78fbbf8851dbaa0559a5d2", + "attempts": 2, + "timeout_seconds": 240, + "task_count": 8, + "status": "blocked_model_configuration", + "live_model_calls": 0, + "check_codes": [ + "authentication_rejected" + ] + }, + "preflight": [ + { + "case": "pagination", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "config-precedence", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "csv-export", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "cli-delimiter", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "multi-file-rename", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "safe-relative-path", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "docs-cli-audit", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + }, + { + "case": "git-worktree-discovery", + "initial_tests_failed": true, + "reference_visible_passed": true, + "reference_build_passed": true, + "reference_hidden_passed": true, + "protected_files_preserved": true, + "pass": true + } + ] +}