From ea04c2feb80a0e40ce85c8028d1be479ad392c3b Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 5 Jun 2026 06:34:06 +0900 Subject: [PATCH] =?UTF-8?q?chore(DRIVE):=20move=20drive=20to=20its=20own?= =?UTF-8?q?=20repo=20(dancinlab/drive)=20=E2=80=94=20remove=20from=20monor?= =?UTF-8?q?epo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drive's development now lives in the standalone dancinlab/drive repo (dev = release, single SSOT; installable via `hx install drive`). Removing DRIVE/ + _drivesim/ from the monorepo retires the two-repo sync obligation. Full content (drive.hexa, rag_retrieve.py, fix_recipes.txt, DRIVE.md, DRIVE.log.md, harness) preserved in dancinlab/drive; history preserved here in git. CLAUDE.md cx_drive_minimal governance kept (cross-repo principle; applies wherever drive is developed). Co-Authored-By: Claude Opus 4.8 (1M context) --- DRIVE/DRIVE.log.md | 30 -- DRIVE/DRIVE.md | 50 --- DRIVE/README.md | 162 ---------- DRIVE/drive.hexa | 610 ------------------------------------ DRIVE/fix_recipes.txt | 23 -- DRIVE/hexa.toml | 22 -- DRIVE/rag_retrieve.py | 144 --------- _drivesim/drive_one.exp | 37 --- _drivesim/gen.py | 121 -------- _drivesim/manifest.json | 672 ---------------------------------------- _drivesim/retry.sh | 22 -- _drivesim/run.sh | 80 ----- _drivesim/run100.sh | 37 --- 13 files changed, 2010 deletions(-) delete mode 100644 DRIVE/DRIVE.log.md delete mode 100644 DRIVE/DRIVE.md delete mode 100644 DRIVE/README.md delete mode 100755 DRIVE/drive.hexa delete mode 100644 DRIVE/fix_recipes.txt delete mode 100644 DRIVE/hexa.toml delete mode 100644 DRIVE/rag_retrieve.py delete mode 100644 _drivesim/drive_one.exp delete mode 100644 _drivesim/gen.py delete mode 100644 _drivesim/manifest.json delete mode 100644 _drivesim/retry.sh delete mode 100755 _drivesim/run.sh delete mode 100755 _drivesim/run100.sh diff --git a/DRIVE/DRIVE.log.md b/DRIVE/DRIVE.log.md deleted file mode 100644 index 0d66f74..0000000 --- a/DRIVE/DRIVE.log.md +++ /dev/null @@ -1,30 +0,0 @@ -# DRIVE — log - -`DRIVE.md` 의 append-only 기록 자매 문서. 각 엔트리는 `##
` (최신이 위); 본문 = `- [x]` (완료) / `- [ ]` (대기) 체크박스. - -## 2026-05-28 — git(commit·push·PR) + LLM 응답·완료 메시지 (유저 명시 요청) - -- [x] git 디렉티브 — `@@COMMIT: ` (`git add -A` + commit) · `@@PUSH` (`git push -u origin HEAD`) · `@@PR: ` (`gh pr create`). `run_git` 헬퍼가 rc + 출력 보고. 멀티 디렉티브 한 turn 처리. -- [x] force 차단 (유저 명시 "force 막아줘") — drive 가 고정 플래그로 명령을 빌드, 모델은 shq-quote 된 데이터(메시지·제목)만 → `--force` 주입 불가. `--force` 코드 경로 없음(grep 확인). live: diverged history push → `[rejected] non-fast-forward`, remote SHA 불변. -- [x] LLM 응답 + `✓ 완료` (유저 명시 "완료 메시지 없음 · LLM 응답 필요") — 라우팅 콜은 "디렉티브만"(prose 섞으면 작은 모델이 @@EDIT 중복+플레이스홀더로 파일 망가뜨림 — 실측). 액션 실행 후 system-free 요약 콜로 한국어 한 문장 → `bot >`, 그 뒤 `✓ 완료`. -- [x] 5/5 패턴 회귀 없음 — create·append·replace·no-trigger·multiline 전부 통과, 각 액션에 LLM 응답+완료 동반. - -## 2026-05-28 — 자연어 자동 파일 수정 (유저 명시 요청 · 5/5 패턴) - -- [x] 자연어 라우팅 — `/edit` 없이 평문 지시로 파일 수정. SYS 가 모델에 `@@EDIT <path>: <instr>` 한 줄을 내도록 지시 → `apply_nl_action` 파싱 → 검증된 `edit_file` 2-패스(실제 내용 읽고 전체 새 버전 생성, `.bak`). 단일-패스 대비 기존 내용 보존. -- [x] 버그 발견+우회 — ` ```drive-edit: ` 코드펜스 SYS 지시가 abliterated gemma 3n 에서 `<|channel>` 특수 토큰 emit → llama-server 500 (`Failed to parse input`). **평문 구분자 `@@EDIT`** 로 전환해 해결. 일반 질문은 평문 답(오작동 0). -- [x] `edit_file` 프롬프트에 리터럴 형식 보존 절 추가 — "config.txt" 를 JSON 으로 변환하던 P5 실패 → `key=value` 리터럴 유지로 수정. -- [x] 5-패턴 시뮬레이션 5/5 통과 — P1 create · P2 append(기존보존) · P3 replace · P4 no-trigger(질문은 파일 안 건드림) · P5 multiline literal. - -## 2026-05-28 — `/edit` 파일 수정 (유저 명시 요청) - -- [x] `/edit <파일> <지시>` 추가 — 채팅이 파일을 못 고치는 한계("UNCENSORED.md 안 바뀜" 실증)를 유저가 "fix" 지시 → `cx_drive_minimal` 부합 (유저 명시분). -- [x] 안전 설계 — 모델 셸 명령 실행 금지 (abliterated `rm -rf` 방어). 모델은 새 전체 내용만 반환, `.bak` 백업 후 덮어씀. `/build` 와 같은 명시적 디스크 명령 패턴. -- [x] live 검증 — `note.txt` "ORIGINAL LINE" → `/edit ... append EDITED-BY-MODEL, keep original` → 원본 보존 + 새 줄 추가 (diff 1a2), `.bak` 생성. - -## 2026-05-28 — 도메인 등록 + 최소기능 법칙 - -- [x] DRIVE 도메인 등록 — `DOMAINS.tape` 로스터 row + `DRIVE/DRIVE.md` 스냅샷 + 이 로그. -- [x] project.tape `@D cx_drive_minimal` 추가 — 기능은 유저 명시분만, 에이전트 제안·자동추가·feature-creep 금지. -- [x] 구현 기능 사실 기록 (PR #135~#140 · hexa-lang #1898) — 제안이 아니라 이미 들어간 것. -- [ ] open = 비어 있음 — 유저가 명시할 때만 새 기능 추가. diff --git a/DRIVE/DRIVE.md b/DRIVE/DRIVE.md deleted file mode 100644 index 6f40a48..0000000 --- a/DRIVE/DRIVE.md +++ /dev/null @@ -1,50 +0,0 @@ -# DRIVE — 모델 시승 카트 - -@title: 🛺 DRIVE — "모델 시승 카트" -@goal: **로컬 GGUF 모델을 손으로 직접 몰아보는 최소 시승 REPL.** 멀티턴 채팅 + `/build` 단일파일 자가완성 + 진입 폴더(cwd) 인식. **최소기능 유지 — 기능은 유저가 명시할 때만 추가** (project.tape `@D cx_drive_minimal`). 에이전트의 기능 제안·자동 추가·feature-creep 금지. - -> 이 문서는 DRIVE **CLI 패키지**(`DRIVE/drive.hexa`)의 진행 기록부다. 코드 자체가 아니라 "무엇이 들어갔고, 무엇을 더 넣을지(=유저 명시분만)"를 추적한다. - -## 최소기능 유지 법칙 (`cx_drive_minimal`) - -> DRIVE 는 풀 에이전트(Claude/Codex TUI)가 아니라 **가벼운 시승 카트**로 유지한다. -> 기능은 **유저가 명시적으로 이름 댄 것만** 추가. "X 얹을까요?" 식 제안·자동 추가·feature-creep 금지. - -``` -DRIVE 자체 (자동차) 이 문서 (정비 기록부) -┌──────────────────┐ ┌────────────────────────┐ -│ drive.hexa 코드 │ ⊥ │ @goal · 구현됨 · open │ -│ → 실제로 굴러감 │ │ → 무엇을 더 넣을지 추적 │ -└──────────────────┘ └────────────────────────┘ - 기능 = 유저 명시분만 (에이전트 제안 ✗) -``` - -## 구현된 기능 (shipped — 사실 기록, 제안 아님) - -- [x] 멀티턴 채팅 REPL — `messages[]` 누적, 직전 대화 컨텍스트 이어짐 (7→17 검증) · PR #135 -- [x] `/build <목표>` 단일파일 자가완성 — 생성→실행→자가수정 (최대 5회), 결과는 cwd `program.py` · PR #135 -- [x] `/edit <파일> <지시>` 파일 실제 수정 — 모델이 새 전체 내용 반환, `.bak` 백업 후 덮어씀 (셸 exec 안 함, 안전). 유저 명시 요청 · live 검증 (note.txt 1a2) -- [x] 자연어 자동 처리 — `/edit` 없이 평문 지시만으로 파일 수정 (모델이 `@@EDIT <path>: <instr>` 라우팅 → `edit_file` 2-패스, 기존 내용 보존). 유저 명시 요청 · 5/5 패턴 통과 (create·append·replace·no-trigger·multiline) -- [x] git 자연어 — `@@COMMIT`/`@@PUSH`/`@@PR`(gh) 디렉티브로 커밋·푸시·PR. **force 구조적 차단** (drive 가 고정 플래그로 명령 빌드 · `--force` 코드 경로 없음 · 모델은 데이터만). 유저 명시 요청 · live 검증 (commit/push 성공 · diverged push rejected) -- [x] git 브랜치 자연어 — `@@BRANCH: <name>` 디렉티브로 `git checkout -b <name>` (새 브랜치 생성+전환). `@@BRANCH`→`@@COMMIT`→`@@PUSH` 순으로 조합되어 "별도 브랜치 만들어 커밋·푸시"가 자연어만으로 가능. force 차단 동일 (고정 플래그 · 모델은 브랜치명 데이터만). 유저 명시 요청 · 100-sim 샌드박스(로컬 bare origin) 검증 -- [x] 액션 후 LLM 한 문장 응답 + `✓ 완료` 마커 — 라우팅 콜은 디렉티브만(안정), 실행 후 system-free 요약 콜로 한국어 응답 (5/5 회귀 없음) -- [x] 모델 선택 — `drive <name>` · `--model` · `--list` · 런타임 `/model` 전환 · PR #137 -- [x] 진입 폴더(cwd) 인식 — 파일목록을 system 메시지로 주입 · PR #138 -- [x] 기본 모델 `supergemma4-e4b-abliterated-Q4_K_M` + 색상 연결 문구 · PR #139 -- [x] 인터랙티브 stdout flush 근본 수정 — hexa-lang `exec_replace` (fd 상속) upstream · PR #140 / hexa-lang #1898 -- [x] 자연어 디렉티브 컴플라이언스 강화 — system few-shot + 산문답 시 "디렉티브만(else NONE)" 폴백 콜 1회 → 작은 로컬모델의 @@EDIT/@@git 라우팅 신뢰도↑. 유저 명시 요청 · 100-sim 관찰 기반 -- [x] no-op 편집 자동 재시도 — 재작성이 원본 그대로면(모델이 변경 누락) 강조 프롬프트로 1회 재시도. 유저 명시 요청 · 100-sim 관찰 기반 -- [x] fix-recipe RAG — `fix_recipes.txt` 지식베이스에서 지시문 키워드로 레시피 검색 → edit 프롬프트에 grounding 주입(reverse→`s[::-1]`·`a*b+1`·factorial range n+1·return 누락 등). 누락 파일 graceful. 유저 명시 요청 · 하드 archetype 6/6 PASS 검증 - -## 열린 기능 (open) - -> **비어 있음.** 새 기능은 유저가 명시할 때만 추가한다 (`cx_drive_minimal`). 에이전트 자가 제안 금지 — 진행바 "현재 명시 기능 전부 구현" 상태이며, 종료가 아니라 유저 명시 대기다. - -## 산출물 - -| 항목 | 위치 | -|---|---| -| 코드 | `DRIVE/drive.hexa` | -| 패키지 manifest | `DRIVE/hexa.toml` (`hx install ./DRIVE` → `drive` 셸) | -| 사용 문서 | `DRIVE/README.md` | -| 설치본 | `~/.hx/bin/drive` | diff --git a/DRIVE/README.md b/DRIVE/README.md deleted file mode 100644 index 14fe3b4..0000000 --- a/DRIVE/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# 🛺 DRIVE — 모델 시승 카트 - -로컬 GGUF 모델을 `llama-server`에 올려 **대화가 이어지는(멀티턴)** 채팅으로 손맛을 보는 -경량 REPL. "이 코드 모델 코딩 잘하나?"를 띄워놓고 바로 물어보는 용도. - -## 무겁지 않은 이유 - -| 축 | Claude / Codex TUI | DRIVE | -|---|---|---| -| 목적 | 실제 작업 수행 | 모델 감 잡기 | -| 구성 | 툴 · 파일편집 · 세션 · 권한 · MCP | 묻고 → 답 | -| 무게 | 풀 에이전트 | 채팅 한 줄 | - -비유: 저쪽은 완성차 공장 라인, 이건 주차장에서 한 바퀴 몰아보는 시승 카트. - -## 설치 - -```sh -hx install ./DRIVE # drive 셸이 ~/.hx/bin 에 생성됨 -``` - -## 실행 - -```sh -drive # 기본 모델 (supergemma4-e4b-abliterated-Q4_K_M) -drive Qwen2.5-7B-Instruct-Q4_K_M # ~/Models/gguf 의 다른 GGUF (위치 인자) -drive --model Qwen2.5-3B-Instruct-Q4_K_M # --model 플래그로도 지정 -drive /abs/path/to/model.gguf # 절대경로도 가능 -drive --list # 사용 가능 GGUF 목록 출력 후 종료 -``` - -- 처음 실행하면 `llama-server`를 8099 포트에 자동 기동(준비될 때까지 대기). -- 이미 떠 있으면 재사용. 실행 중 다른 모델로 바꾸려면 REPL 안에서 **`/model <이름>`** - (서버를 자동 재기동). CLI 재실행으로 바꿀 땐 떠 있는 서버를 먼저 내려야 함: - `pkill -f 'llama-server.*8099'`. - -## REPL 명령 - -| 입력 | 동작 | -|---|---| -| (아무 말) | 모델에 질문 — 직전 대화가 컨텍스트로 이어짐. **파일 수정 요청이면 자동으로 그 파일을 고침** (자연어 자동 처리) | -| `/model <이름>` | 모델 전환 — 서버를 재기동하고 같은 대화를 이어감 | -| `/build <목표>` | 한 파일짜리 프로그램을 **스스로 완성** (생성→실행→자가수정 루프) | -| `/edit <파일> <지시>` | 지정한 파일을 **실제로 수정** — 모델이 새 전체 내용을 반환, `.bak` 백업 후 덮어씀 | -| `/reset` | 대화 기록 초기화 (새 컨텍스트) | -| `/quit` `/exit` `/q` | 종료 (빈 줄 두 번 또는 Ctrl-D 도 종료) | - -## 혼자 완성 — `/build` - -목표를 주면 모델이 단일 Python 파일을 **스스로 짜고 → 돌려보고 → 틀리면 고쳐서** -통과할 때까지 반복합니다(최대 5회). 결과물은 **실행한 폴더(cwd)** 의 `program.py`. - -``` -/build print the numbers 1 to 5 - -[build 1/5] 생성 중... - ok 통과 (exit 0) — /tmp/drive-build/program.py - --- 출력 --- - 1 - 2 - 3 - 4 - 5 -``` - -- 자가수정 루프라 **모델이 똑똑할수록** 잘 돕니다. 1.5B는 약하니 - `drive Qwen2.5-7B-Instruct-Q4_K_M` 등 큰 모델 권장. -- 실행은 `timeout 15s`(있으면) 안에서 — 무한루프 방어. - -> 이 기능을 켜는 순간 DRIVE는 "순수 채팅"을 넘어 **미니 코딩 에이전트**가 됩니다 -> (단일 파일 한정 · 도구는 쓰기/실행 2개). 멀티파일 프로젝트는 풀 에이전트 영역. - -## 파일 수정 — `/edit` - -파일을 고치는 길은 두 가지예요 — **명시적 `/edit`** 와 **자연어 자동 처리**. - -`/edit <파일> <지시>` 는 지정한 파일을 **실제로 고칩니다**. 명시적 디스크 명령이에요. - -``` -/edit note.txt append a line saying DONE, keep the rest - · 편집 생성 중... (/abs/path/note.txt) - ✓ 수정됨 — /abs/path/note.txt (백업: /abs/path/note.txt.bak) - --- diff --- - 1a2 - > DONE -``` - -- 동작: 그 파일 내용을 모델에 주고 → **새 전체 내용**을 받아 → `.bak` 백업 후 덮어씀. -- 안전: 모델이 뱉은 셸 명령을 실행하지 **않습니다** (abliterated 모델 `rm -rf` 방어). - 오직 명시한 파일 하나만, 항상 `.bak` 백업을 남겨 되돌릴 수 있어요. -- 파일이 없으면 새로 생성합니다(이때는 `.bak` 없음). - -## 자연어 자동 처리 - -`/edit` 를 안 쳐도 됩니다. 그냥 평소처럼 말하면 모델이 **파일 수정 요청인지 스스로 판단**해서 자동으로 고쳐요. - -``` -you > doc.txt 맨 아래에 line three 추가해줘 - · 편집 생성 중... (/abs/doc.txt) - ✓ 수정됨 — /abs/doc.txt (백업: …/doc.txt.bak) -``` - -- **동작(2-패스)**: 모델이 먼저 `@@EDIT <파일>: <지시>` 한 줄을 내면(라우팅), drive 가 그 파일의 실제 내용을 읽어 전체 새 버전을 다시 받아 씁니다(`/edit` 와 같은 경로 → 기존 내용 보존). -- 파일 요청이 **아닌** 일반 질문은 그냥 답만 합니다(파일 안 건드림). -- 액션이 끝나면 **LLM 한 문장 응답 + `✓ 완료`** 가 같이 출력됩니다 (무엇을 했는지 + 끝났다는 신호). -- ⚠ 라우팅 지시는 **평문 구분자**(`@@EDIT`)예요 — ` ``` ` 코드펜스를 쓰면 일부 abliterated GGUF 가 `<|channel>` 특수 토큰을 뱉어 서버가 500 으로 죽습니다(실측 후 평문으로 전환). - -## git — 커밋 · 푸시 · PR - -자연어로 git 작업도 시킬 수 있어요. 모델이 아래 디렉티브를 내면 drive 가 실행합니다. - -``` -you > 변경사항 "fix typo" 메시지로 커밋하고 origin 에 푸시해줘 - · 커밋... ✓ 커밋 - · 푸시 (force 차단)... ✓ 푸시 -bot > 커밋하고 origin 에 푸시 완료했습니다. - ✓ 완료 -``` - -| 디렉티브 | 동작 | -|---|---| -| `@@COMMIT: <메시지>` | `git add -A` + `git commit -m <메시지>` | -| `@@PUSH` | `git push -u origin HEAD` — **절대 force 안 함** | -| `@@PR: <제목>` | `gh pr create --title <제목>` | - -- 🔒 **force 차단**: 푸시는 항상 비강제(`git push`)예요. drive 코드에 `--force` 경로가 **아예 없어서**, 갈라진 히스토리는 그냥 거부(`[rejected] non-fast-forward`)됩니다 — 원격을 덮어쓰지 못해요. -- 🔒 모델은 커밋 메시지·PR 제목 같은 **데이터만** 줍니다(셸 인용). 플래그(`--force` 등)를 주입할 수 없어요. - -## 멀티턴이 이어지는 원리 - -단발 호출이 아니라, 매 턴 전체 `messages[]`를 다시 보냅니다(누적). - -``` -턴1 you: 피보나치 짜줘 → [user] → bot: def fib... -턴2 you: 반복문으로 바꿔 → [user, bot, user] → bot: (앞 코드 기억하고 수정) -``` - -## 진입 시 폴더 인식 - -`drive`를 실행한 폴더가 곧 작업 폴더예요. 진입할 때 그 폴더 경로 + 파일 목록을 -**system 메시지로 모델에 주입**하고, `/build` 결과도 그 폴더에 저장합니다. - -``` -~/myproj$ drive - 작업 폴더: /Users/me/myproj (진입 시 인식) -you > 이 폴더에 무슨 파일 있어? -bot > app.py, README.md, ... ← 진입 시 인식한 목록 -you > /build app.py 에 헬스체크 엔드포인트 추가 - → program.py 를 ~/myproj 에 생성 -``` - -`/reset` 해도 폴더 컨텍스트는 유지됩니다(대화 기록만 비움). - -## 요구 사항 - -- `llama-server` · `curl` · `jq` (PATH 에 존재) -- `~/Models/gguf/*.gguf` (Qwen2.5 0.5B / 1.5B / 3B / 7B 등) - -## 범위 - -현재는 **(a) 순수 채팅**. 도메인 스모크 자동채점(코드 즉시 실행 ✓/✗ · 수학 정답 대조)은 -다음 단계로 얹는다. diff --git a/DRIVE/drive.hexa b/DRIVE/drive.hexa deleted file mode 100755 index 550d025..0000000 --- a/DRIVE/drive.hexa +++ /dev/null @@ -1,610 +0,0 @@ -#!hexa strict -// DRIVE/drive.hexa — multi-turn chat REPL for test-driving a vertical model. -// -// "Model test-drive cart" — load a local GGUF on llama-server and chat with it -// turn-by-turn. Conversation history is accumulated in messages[] and re-sent -// every turn, so the model sees the full dialogue (NOT single-shot). The -// lightweight counterpart to a full agent TUI: chat is plain ask -> answer (no -// hidden tools), plus two EXPLICIT disk commands — /build (single-file program) -// and /edit (rewrite a named file). To feel whether the model can code, do -// math, etc. Per cx_drive_minimal, features are added only on explicit request. -// -// Usage: -// drive [model] model = GGUF basename under ~/Models/gguf (.gguf -// suffix optional), or an absolute /path.gguf. -// default: supergemma4-e4b-abliterated-Q4_K_M -// in-REPL commands: -// /model <name> switch model (restarts the server) -// /build <goal> autocomplete a single-file program in cwd -// /edit <file> <instr> rewrite a named file in cwd (keeps a .bak) -// /reset clear conversation history (fresh context) -// /quit | /exit | /q end the session (or two blank Enters / EOF) -// -// Requires: llama-server + curl + jq on PATH; a *.gguf under ~/Models/gguf. -// -// @resolver-bypass(reason="External HTTP subprocess (curl -> llama-server) — no atlas atoms touched.") - -let HOME = env("HOME") -let MODEL_DIR = HOME + "/Models/gguf" -let PORT = "8099" -let SERVER = "http://127.0.0.1:" + PORT -let DEFAULT_MODEL = "supergemma4-e4b-abliterated-Q4_K_M" -let MAX_TOK = 512 -let CTX = "4096" -let SERVER_LOG = "/tmp/drive-llama-server.log" - -// ANSI colors — ESC built once via printf (hexa string literals avoid raw ESC). -let ESC = exec("printf '\\033'") -let C_GREEN = ESC + "[32m" -let C_CYAN = ESC + "[36m" -let C_RED = ESC + "[31m" -let C_DIM = ESC + "[2m" -let C_RST = ESC + "[0m" - -// shell-quote a string for safe embedding in /bin/sh -c. -fn shq(s: str) -> str { - return "'" + s.replace("'", "'\\''") + "'" -} - -// JSON-string-escape for embedding user/assistant content in a JSON body. -fn json_escape(s: str) -> str { - let mut t = s.replace("\\", "\\\\") - t = t.replace("\"", "\\\"") - t = t.replace("\n", "\\n") - t = t.replace("\r", "\\r") - t = t.replace("\t", "\\t") - return t -} - -// Resolve a model arg -> filesystem path. Absolute (/...) passes through; else -// a basename under MODEL_DIR with a .gguf suffix appended if absent. -fn resolve_model(name: str) -> str { - if name.len() > 0 && name.substr(0, 1) == "/" { return name } - let mut n = name - if n.len() < 5 || n.substr(n.len() - 5, 5) != ".gguf" { n = n + ".gguf" } - return MODEL_DIR + "/" + n -} - -// Is a llama-server already healthy on our port? -fn server_ready() -> bool { - let out = exec("curl -s --max-time 2 " + SERVER + "/health 2>/dev/null").trim() - return out.contains("\"status\":\"ok\"") -} - -// Spawn llama-server (nohup, detached) and poll /health until ok or timeout. -fn boot_server(model_path: str) -> bool { - let launch = "nohup llama-server -m " + shq(model_path) + - " --host 127.0.0.1 --port " + PORT + " -c " + CTX + - " > " + shq(SERVER_LOG) + " 2>&1 &" - exec(launch) - // Poll up to 90s — larger GGUFs load slowly on first boot. - let poll = "for i in $(seq 1 90); do " + - "if curl -s --max-time 2 " + SERVER + "/health 2>/dev/null " + - "| grep -q '\"status\":\"ok\"'; then echo READY; exit 0; fi; " + - "sleep 1; done; echo TIMEOUT" - let res = exec(poll).trim() - return res.contains("READY") -} - -// One chat round: POST the full message history, return the assistant content. -fn chat(msgs_inner: str, max_tok: int) -> str { - let body = "{\"messages\":[" + msgs_inner + "]," + - "\"temperature\":0,\"max_tokens\":" + str(max_tok) + "}" - let cmd = "curl -s --max-time 180 -X POST " + SERVER + "/v1/chat/completions " + - "-H 'Content-Type: application/json' -d " + shq(body) + - " | jq -r '.choices[0].message.content // \"(no response)\"'" - return exec(cmd).trim() -} - -// ── /build agentic loop (single-file program autocompletion) ── - -// Extract the first ```...``` code block from a model response. The leading -// language-tag line (e.g. "python") is dropped. No fence -> whole text. -fn extract_code(s: str) -> str { - let parts = s.split("```") - if len(parts) < 2 { return s.trim() } - let block = parts[1] - let lines = block.split("\n") - let mut body = "" - let mut i = 1 // skip lines[0] = lang tag (or "") - while i < len(lines) { - if i > 1 { body = body + "\n" } - body = body + lines[i] - i = i + 1 - } - return body -} - -// Run a python file with an optional timeout prefix. Returns raw combined -// stdout+stderr with a trailing "__RC=<code>" marker for exit-code parsing. -fn run_program(path: str, tmo: str) -> str { - let cmd = tmo + "python3 " + shq(path) + " 2>&1; echo \"__RC=$?\"" - return exec(cmd) -} - -// Agentic loop: generate -> save -> run -> feed error back -> fix, until the -// program exits 0 or max_iter is reached. Single self-contained Python file. -fn build_loop(goal: str, tmo: str, workdir: str) { - let path = workdir + "/program.py" - let max_iter = 5 - - let task = "Working directory: " + workdir + ". Write a single self-contained " + - "Python file that satisfies this goal: " + goal + - ". Output ONLY one python code block (```python ... ```), no prose. " + - "It must run with python3 and exit 0 on success; print results to stdout." - let mut msgs = "{\"role\":\"user\",\"content\":\"" + json_escape(task) + "\"}" - - let mut i = 1 - let mut done = false - while i <= max_iter { - println("") - println("[build " + str(i) + "/" + str(max_iter) + "] 생성 중...") - let resp = chat(msgs, 1024) - let code = extract_code(resp) - write_file(path, code) - - let raw = run_program(path, tmo) - let segs = raw.split("__RC=") - let prog_out = segs[0].trim() - let mut rc = -1 - if len(segs) >= 2 { rc = int(segs[1].trim()) } - - if rc == 0 { - println(" ok 통과 (exit 0) — " + path) - println(" --- 출력 ---") - println(prog_out) - done = true - i = max_iter + 1 // stop the loop - } else { - println(" x 실패 (exit " + str(rc) + ")") - println(prog_out) - msgs = msgs + - ",{\"role\":\"assistant\",\"content\":\"" + json_escape(resp) + "\"}" - let fb = "It failed with exit code " + str(rc) + ". Error/output:\n" + - prog_out + "\nFix it and output the COMPLETE corrected python " + - "code block again, code only." - msgs = msgs + - ",{\"role\":\"user\",\"content\":\"" + json_escape(fb) + "\"}" - i = i + 1 - } - } - if done == false { - println("") - println("미완성 — " + str(max_iter) + "회 시도 후에도 통과 못함. 마지막 파일: " + path) - } -} - -// Write `content` to `path` with a .bak backup (when the file already existed) -// and print a confirmation + diff. Shared by /edit and natural-language actions. -fn apply_file_write(path: str, content: str) { - let present = exec("test -f " + shq(path) + " && echo Y || echo N").trim() - if present == "Y" { exec("cp " + shq(path) + " " + shq(path + ".bak")) } - write_file(path, content) - if present == "Y" { - println(C_GREEN + " ✓ 수정됨" + C_RST + C_DIM + - " — " + path + " (백업: " + path + ".bak)" + C_RST) - let d = exec("diff " + shq(path + ".bak") + " " + shq(path) + " 2>/dev/null | head -20").trim() - if d.len() > 0 { - println(C_DIM + " --- diff ---" + C_RST) - println(d) - } - } else { - println(C_GREEN + " ✓ 생성됨" + C_RST + C_DIM + " — " + path + C_RST) - } -} - -// ── RAG: retrieve fix-recipe grounding for an edit instruction ── -// HYBRID retrieval, grounding a small local model on the correct fix (reverse -// -> s[::-1], factorial range n+1, etc.) — curated from observed failure cases: -// 1. PROPER RAG via rag_retrieve.py (venv python) — keyword-precision STAGE 1 -// + embedding-recall STAGE 2 (fastembed, in-process). Preferred when the -// ~/.drive-rag-venv is present. -// 2. inline keyword scan (below) — ultimate fallback when python/venv is -// unavailable, so grounding never hard-depends on the embedding stack. -// Missing KB / no backend -> "" (graceful: edit still works, just unguided). -fn retrieve_recipes(instr: str) -> str { - // Resolve the package dir portably: DRIVE_PKG_DIR (exported by the bin/drive - // launcher of the released package) wins, else the in-repo/legacy install - // path. This keeps the KB + retriever findable regardless of install name - // (e.g. ~/.hx/packages/drive vs .../DRIVE). - let mut pkgdir = env("DRIVE_PKG_DIR") - if pkgdir.len() == 0 { pkgdir = env("HOME") + "/.hx/packages/DRIVE" } - let path = pkgdir + "/fix_recipes.txt" - let present = exec("test -f " + shq(path) + " && echo Y || echo N").trim() - if present != "Y" { return "" } - // STAGE 1+2 — proper hybrid retriever (graceful: empty/err -> inline scan) - let vpy = env("HOME") + "/.drive-rag-venv/bin/python" - let rag = pkgdir + "/rag_retrieve.py" - let has_rag = exec("test -x " + shq(vpy) + " && test -f " + shq(rag) + - " && echo Y || echo N").trim() - if has_rag == "Y" { - let hits = exec(shq(vpy) + " " + shq(rag) + " " + shq(instr) + - " 2>/dev/null").trim() - if hits.len() > 0 { return hits } - } - let raw = read_file(path) - let lines = raw.split("\n") - let mut out = "" - let mut i = 0 - while i < len(lines) { - let ln = lines[i].trim() - if ln.len() > 0 && ln.substr(0, 1) != "#" { - let sep = ln.index_of("::") - if sep >= 0 { - let keys = ln.substr(0, sep) - let recipe = ln.substr(sep + 2, ln.len() - sep - 2).trim() - let kw = keys.split(" ") - let mut hit = false - let mut k = 0 - while k < len(kw) { - let key = kw[k].trim() - if key.len() > 0 && instr.index_of(key) >= 0 { hit = true } - k = k + 1 - } - if hit { - if out.len() > 0 { out = out + "\n" } - out = out + "- " + recipe - } - } - } - i = i + 1 - } - return out -} - -// ── /edit <file> <instruction> — model rewrites a named file, we write it back ── -// Safe by design: only the explicitly named file is touched (NO arbitrary shell -// exec from the model — an abliterated model could otherwise emit `rm -rf`), a -// .bak backup is kept for reversibility, and the model must return the COMPLETE -// new content as one code block (same extract_code path as /build). Mirrors the -// /build pattern: chat stays text-only, disk writes happen only via a command. -fn edit_file(rest: str, workdir: str) { - let sp = rest.index_of(" ") - if sp < 0 { - println(C_RED + " ✗ 사용법: /edit <파일> <지시>" + C_RST) - return - } - let fname = rest.substr(0, sp).trim() - let instr = rest.substr(sp + 1, rest.len() - sp - 1).trim() - let mut path = fname - if fname.len() == 0 || fname.substr(0, 1) != "/" { path = workdir + "/" + fname } - - let present = exec("test -f " + shq(path) + " && echo Y || echo N").trim() - let mut cur = "" - if present == "Y" { cur = read_file(path) } - - let recipes = retrieve_recipes(instr) - let mut ground = "" - if recipes.len() > 0 { - ground = "Reference fix patterns (apply the matching one EXACTLY):\n" + - recipes + "\n\n" - } - let task = ground + "File path: " + path + ". Current content:\n```\n" + cur + - "\n```\nApply this change: " + instr + - ". Write EXACTLY the content and format requested — keep the literal " + - "syntax (e.g. `key=value` lines, one per line) and do NOT convert to " + - "JSON/YAML or otherwise reformat unless explicitly asked. Output ONLY " + - "the COMPLETE new file content in one code block (``` ... ```), no prose." - let body = "{\"role\":\"user\",\"content\":\"" + json_escape(task) + "\"}" - println(C_DIM + " · 편집 생성 중... (" + path + ")" + C_RST) - let resp = chat(body, 1024) - let mut newc = extract_code(resp) - if newc.len() == 0 { - println(C_RED + " ✗ 모델이 빈 내용 반환 — 파일 유지" + C_RST) - return - } - // Robustness: if the rewrite came back unchanged (the model echoed the file - // without applying the change — common on a small model with a vague ask), - // retry ONCE with an emphatic instruction before writing the no-op. - if present == "Y" && newc.trim() == cur.trim() { - println(C_DIM + " · 변경 없음 — 재시도..." + C_RST) - let task2 = task + " WARNING: your previous answer left the file UNCHANGED. " + - "You MUST actually modify the code to satisfy: " + instr + - ". Output the COMPLETE new file content (one code block), with the change applied." - let body2 = "{\"role\":\"user\",\"content\":\"" + json_escape(task2) + "\"}" - let resp2 = chat(body2, 1024) - let newc2 = extract_code(resp2) - if newc2.len() > 0 { newc = newc2 } - } - apply_file_write(path, newc) -} - -// Run a git/gh command (built by US with fixed flags) and report rc + output. -// The model only ever supplies DATA (commit message, PR title) which we -// shell-quote, so it can NEVER inject a flag like --force. Pushes are hardcoded -// non-force; there is no code path that adds --force/-f. -fn run_git(desc: str, full_cmd: str) { - println(C_DIM + " · " + desc + "..." + C_RST) - let out = exec(full_cmd + " 2>&1; echo \"__RC=$?\"") - let segs = out.split("__RC=") - let body = segs[0].trim() - let mut rc = -1 - if len(segs) >= 2 { rc = int(segs[1].trim()) } - if rc == 0 { - println(C_GREEN + " ✓ " + desc + C_RST) - } else { - println(C_RED + " ✗ " + desc + " 실패 (rc=" + str(rc) + ")" + C_RST) - } - if body.len() > 0 { println(C_DIM + body + C_RST) } -} - -// Detect model-emitted routing directives in a chat reply and carry them out in -// order. Returns true if ANY directive ran (caller then skips printing the raw -// reply). Plain-delimiter directives (NO ``` fence — that made this abliterated -// GGUF emit a <|channel> token and 500 the server): -// @@EDIT <path>: <change> file create/modify (via edit_file, .bak kept) -// @@BRANCH: <name> git checkout -b <name> (create + switch branch) -// @@COMMIT: <message> git add -A + git commit -m <message> -// @@PUSH git push -u origin HEAD (NEVER --force) -// @@PR: <title> gh pr create --title <title> -// We build every git/gh command ourselves with fixed flags; the model supplies -// only shq-quoted DATA, so it cannot inject --force or any destructive flag. -fn apply_nl_action(reply: str, workdir: str) -> bool { - if reply.index_of("@@EDIT ") < 0 && reply.index_of("@@COMMIT") < 0 && - reply.index_of("@@PUSH") < 0 && reply.index_of("@@PR") < 0 && - reply.index_of("@@BRANCH") < 0 { - return false - } - let lines = reply.split("\n") - let mut i = 0 - let mut did = false - while i < len(lines) { - let ln = lines[i].trim() - if ln.len() >= 7 && ln.substr(0, 7) == "@@EDIT " { - let rest = ln.substr(7, ln.len() - 7) - let colon = rest.index_of(":") - if colon >= 0 { - let path = rest.substr(0, colon).trim() - let instr = rest.substr(colon + 1, rest.len() - colon - 1).trim() - if path.len() > 0 { edit_file(path + " " + instr, workdir); did = true } - } - } else if ln.len() >= 8 && ln.substr(0, 8) == "@@BRANCH" { - let mut name = ln.substr(8, ln.len() - 8).trim() - if name.len() > 0 && name.substr(0, 1) == ":" { name = name.substr(1, name.len() - 1).trim() } - if name.len() > 0 { - run_git("브랜치 생성 (force 차단)", "git checkout -b " + shq(name)) - did = true - } - } else if ln.len() >= 8 && ln.substr(0, 8) == "@@COMMIT" { - let mut msg = ln.substr(8, ln.len() - 8).trim() - if msg.len() > 0 && msg.substr(0, 1) == ":" { msg = msg.substr(1, msg.len() - 1).trim() } - if msg.len() == 0 { msg = "update via DRIVE" } - run_git("커밋", "git add -A && git commit -m " + shq(msg)) - did = true - } else if ln.len() >= 6 && ln.substr(0, 6) == "@@PUSH" { - run_git("푸시 (force 차단)", "git push -u origin HEAD") - did = true - } else if ln.len() >= 4 && ln.substr(0, 4) == "@@PR" { - let mut title = ln.substr(4, ln.len() - 4).trim() - if title.len() > 0 && title.substr(0, 1) == ":" { title = title.substr(1, title.len() - 1).trim() } - if title.len() == 0 { title = "DRIVE PR" } - run_git("PR 생성 (gh)", "gh pr create --title " + shq(title) + " --body " + shq("created via DRIVE")) - did = true - } - i = i + 1 - } - return did -} - -// Does a reply carry any action directive? -fn has_directive(reply: str) -> bool { - return reply.index_of("@@EDIT ") >= 0 || reply.index_of("@@COMMIT") >= 0 || - reply.index_of("@@PUSH") >= 0 || reply.index_of("@@PR") >= 0 || - reply.index_of("@@BRANCH") >= 0 -} - -fn main() { - // ── arg parse: positional model · --model <name> / --model=<name> · --list ── - let a = args() - let mut model = DEFAULT_MODEL - let mut do_list = false - let mut ai = 1 - while ai < len(a) { - let arg = a[ai] - if arg == "--list" { - do_list = true - } else if arg == "--model" { - if ai + 1 < len(a) { model = a[ai + 1]; ai = ai + 1 } - } else if arg.len() >= 8 && arg.substr(0, 8) == "--model=" { - model = arg.substr(8, arg.len() - 8) - } else if arg.len() >= 2 && arg.substr(0, 2) == "--" { - // unknown flag — ignore - } else if arg.len() > 0 { - model = arg - } - ai = ai + 1 - } - - // ── --list: print available GGUFs under MODEL_DIR and exit ── - if do_list { - println("사용 가능 모델 (~/Models/gguf):") - println(exec("ls -1 " + shq(MODEL_DIR) + - "/*.gguf 2>/dev/null | xargs -n1 basename 2>/dev/null").trim()) - exit(0) - } - - let model_path = resolve_model(model) - - // ── model file must exist ── - let exists = exec("test -f " + shq(model_path) + " && echo Y || echo N").trim() - if exists != "Y" { - println("x 모델 파일 없음: " + model_path) - println(" 사용 가능 (~/Models/gguf):") - println(exec("ls -1 " + shq(MODEL_DIR) + - "/*.gguf 2>/dev/null | xargs -n1 basename 2>/dev/null").trim()) - exit(1) - } - - // ── ensure a server is up ── - if server_ready() { - println(C_GREEN + "✓ 연결됨" + C_RST + C_DIM + - " — 재사용 @ 127.0.0.1:" + PORT + C_RST) - } else { - println(C_DIM + "· llama-server 기동 중... (" + model + ")" + C_RST) - let ok = boot_server(model_path) - if ok { - println(C_GREEN + "✓ 연결됨" + C_RST + C_DIM + - " — " + model + " @ 127.0.0.1:" + PORT + C_RST) - } else { - println(C_RED + "✗ 서버 기동 실패 — 로그: " + SERVER_LOG + C_RST) - exit(1) - } - } - - // ── timeout prefix for /build sandbox runs (no-op if `timeout` absent) ── - let tmo = exec("command -v timeout >/dev/null 2>&1 && printf 'timeout 15 ' || printf ''") - - // ── entry-time working-directory awareness ── - // The folder drive is launched from becomes the project context: its path + - // file listing are injected as a system message so the model knows where it - // is, and /build writes its program here (not /tmp). - let workdir = cwd() - let filelist = exec("ls -1p " + shq(workdir) + " 2>/dev/null | head -40").trim() - let ctx = "You are a concise coding assistant in directory " + workdir + - ". Answer briefly and directly; do NOT enumerate or summarize these " + - "files unless explicitly asked. When the user wants a FILE or GIT " + - "action, reply with ONLY the matching directive line(s), nothing else " + - "(one directive per line, at most one @@EDIT per file):\n" + - "@@EDIT <path>: <change> (create or modify a file)\n" + - "@@BRANCH: <name> (create and switch to a new branch)\n" + - "@@COMMIT: <message> (stage all and commit)\n" + - "@@PUSH (push to origin; never force)\n" + - "@@PR: <title> (open a pull request via gh)\n" + - "Emit several lines in order when needed (e.g. @@BRANCH then @@COMMIT then @@PUSH). " + - "Use the path the user names; if the user does NOT name a file, use " + - "the program file shown in the directory listing below. A request to " + - "fix/implement/change code is ALWAYS an @@EDIT (describe the change in " + - "the directive, do not paste code in chat). A request to save/commit/" + - "push/branch/PR is ALWAYS the matching git directive — never answer such " + - "a request in prose. Examples (reply is ONLY the directive line(s)):\n" + - "user: 곱셈 함수가 없어서 죽어. 곱을 반환하게 고쳐줘\n" + - "assistant: @@EDIT calc.py: implement multiply(a, b) returning a * b\n" + - "user: 문법 오류로 실행이 안 돼. 고쳐줘\n" + - "assistant: @@EDIT run.py: fix the syntax error so it runs\n" + - "user: 방금 바뀐 내용을 커밋해줘\n" + - "assistant: @@COMMIT: update files\n" + - "user: 변경을 커밋하고 원격에 푸시해줘\n" + - "assistant: @@COMMIT: update files\n@@PUSH\n" + - "user: feature-x 브랜치를 만들어서 커밋하고 푸시해줘\n" + - "assistant: @@BRANCH: feature-x\n@@COMMIT: update files\n@@PUSH\n" + - "For anything that is NOT a file/git action, answer normally in plain " + - "text. The directory contains:\n" + filelist - let SYS = "{\"role\":\"system\",\"content\":\"" + json_escape(ctx) + "\"}" - - // ── banner ── - println("") - println(C_CYAN + "[DRIVE] 모델 시승 — " + model + C_RST) - println(C_DIM + " 작업 폴더: " + workdir + " (진입 시 인식)" + C_RST) - println(" 대화가 이어집니다. /reset 초기화 · /quit 종료") - println(" /model <이름> 모델 전환 · /build <목표> 한 파일 자가완성 · /edit <파일> <지시> 파일 수정") - println(C_DIM + " 자연어로 파일 수정·커밋·푸시·PR 자동 처리 (force 차단 · .bak 백업)" + C_RST) - println("") - - // ── REPL loop (history starts with the folder-context system message) ── - let mut msgs_inner = SYS - let mut empty_streak = 0 - let mut running = true - while running { - let line = input("you > ") - if line == "/quit" || line == "/exit" || line == "/q" { - running = false - } else if line.len() == 0 { - empty_streak = empty_streak + 1 - if empty_streak >= 2 { - running = false - } else { - println(" (빈 입력 — 한 번 더 Enter 또는 /quit 로 종료)") - } - } else if line == "/reset" { - empty_streak = 0 - msgs_inner = SYS - println(" (대화 초기화됨 — 폴더 컨텍스트 유지)") - println("") - } else if line.len() > 7 && line.substr(0, 7) == "/model " { - empty_streak = 0 - let newmodel = line.substr(7, line.len() - 7).trim() - let newpath = resolve_model(newmodel) - let exok = exec("test -f " + shq(newpath) + " && echo Y || echo N").trim() - if exok != "Y" { - println(C_RED + " ✗ 모델 파일 없음: " + newpath + C_RST) - } else { - println(C_DIM + " · 모델 전환: " + newmodel + " (서버 재기동...)" + C_RST) - exec("pkill -f 'llama-server.*" + PORT + "' 2>/dev/null; sleep 1") - let ok = boot_server(newpath) - if ok { - model = newmodel - println(C_GREEN + " ✓ 연결됨" + C_RST + C_DIM + " — " + newmodel + C_RST) - } else { - println(C_RED + " ✗ 기동 실패 — 로그: " + SERVER_LOG + C_RST) - } - } - println("") - } else if line.len() > 7 && line.substr(0, 7) == "/build " { - empty_streak = 0 - let goal = line.substr(7, line.len() - 7) - build_loop(goal, tmo, workdir) - println("") - } else if line.len() > 6 && line.substr(0, 6) == "/edit " { - empty_streak = 0 - let rest = line.substr(6, line.len() - 6) - edit_file(rest, workdir) - println("") - } else { - empty_streak = 0 - // append user turn - if msgs_inner.len() > 0 { msgs_inner = msgs_inner + "," } - msgs_inner = msgs_inner + - "{\"role\":\"user\",\"content\":\"" + json_escape(line) + "\"}" - // call model with full history (show progress — generation can take - // many seconds on a local CPU/Metal backend) - println(C_DIM + " · 생성 중..." + C_RST) - let mut reply = chat(msgs_inner, MAX_TOK) - // Directive-compliance fallback: a small model often answers a - // file/git request in PROSE instead of emitting a directive. If the - // first reply has no directive, re-ask ONCE for directive-only (giving - // it the folder listing for path inference). It must reply NONE for a - // pure question/chat, so normal conversation is never hijacked. - if has_directive(reply) == false { - let fb = ctx + "\n\nThe user just said: \"" + line + "\". " + - "If this requires creating/modifying a file or a git action, " + - "reply with ONLY the matching directive line(s) (@@EDIT <path>: " + - "<change> / @@BRANCH: <name> / @@COMMIT: <msg> / @@PUSH / @@PR: " + - "<title>) and NOTHING else. If it is just a question or normal " + - "chat, reply with exactly: NONE" - let fbbody = "{\"role\":\"user\",\"content\":\"" + json_escape(fb) + "\"}" - let fbreply = chat(fbbody, 256) - if has_directive(fbreply) { reply = fbreply } - } - // natural-language autonomy. The routing call returns ONLY directives - // (reliable — mixing prose into it destabilizes a small model). So when - // an action ran, get the LLM's plain-language reply from a SECOND focused - // (system-free) call, print it, then a clear done marker — the user sees - // both the LLM response AND that the work completed. - if has_directive(reply) { - let did = apply_nl_action(reply, workdir) - if did { - let sp = "{\"role\":\"user\",\"content\":\"" + - json_escape("You just carried out this user request by " + - "editing/committing files: \"" + line + "\". Reply to the " + - "user in ONE short Korean sentence confirming it is done. " + - "Plain text only, no code.") + "\"}" - let say = chat(sp, 128) - println("bot > " + say) - println(C_GREEN + " ✓ 완료" + C_RST) - } else { - println("bot > " + reply) - } - } else { - println("bot > " + reply) - } - println("") - // append assistant turn so the next round has context - msgs_inner = msgs_inner + - ",{\"role\":\"assistant\",\"content\":\"" + json_escape(reply) + "\"}" - } - } - - println("") - println("bye.") - exit(0) -} diff --git a/DRIVE/fix_recipes.txt b/DRIVE/fix_recipes.txt deleted file mode 100644 index 066e3c8..0000000 --- a/DRIVE/fix_recipes.txt +++ /dev/null @@ -1,23 +0,0 @@ -# DRIVE fix-recipe knowledge base (RAG store for edit_file). -# -# Each non-comment line: <keyword> <keyword> ... :: <recipe text> -# At edit time drive scans the user's instruction for any keyword on a line -# and injects that line's recipe text into the rewrite prompt as grounding. -# Keys are bilingual (the test-drive NL is Korean) — match is case-sensitive -# substring on the raw instruction. Keep recipes ONE line, concrete, correct. -# Curated from observed small-model failure cases (reverse / formula / return -# / max / undefined-var / factorial). Extend freely — no rebuild needed to add -# rows (drive reads this file at runtime). - -뒤집 reverse 역순 거꾸로 :: To reverse a string in Python return s[::-1] (slice with step -1); for a list use list(reversed(x)) or x[::-1]. -곱 multiply 곱셈 product :: To multiply two numbers return a * b. Define the function and actually return the product. -더하 add 합 sum 덧셈 :: To add two numbers return a + b (use +, not -). -제곱 square 스퀘어 :: To square a number return n * n. The function MUST use the return keyword, otherwise it yields None. -return 반환 돌려 None :: Every function that should produce a value MUST end with `return <value>`; a bare expression returns None. -큰 max 최대 bigger 큰값 :: To return the larger of two values return a if a > b else b. -작은 min 최소 :: To return the smaller of two values return a if a < b else b. -팩토리얼 factorial 계승 :: factorial(n): r=1; for k in range(1, n+1): r*=k; return r — range upper bound is n+1 (inclusive of n). -넓이 area 면적 가로 세로 :: Area = width * height. Multiply the two sides (use *, not /). -정의 undefined 미정의 변수 :: Do NOT reference a name that was never defined; compute and return the value directly from the function arguments. -문법 syntax 콜론 :: A Python function definition needs a trailing colon, e.g. `def f(x):`; fix missing colons / indentation so it parses. -a*b+1 공식 formula 식 :: For the formula a*b + 1 return a * b + 1 (multiply first, then add one). diff --git a/DRIVE/hexa.toml b/DRIVE/hexa.toml deleted file mode 100644 index 62d83ba..0000000 --- a/DRIVE/hexa.toml +++ /dev/null @@ -1,22 +0,0 @@ -# hexa.toml — DRIVE standalone package manifest -# -# Distribution target: hx install ./DRIVE (local directory) -# Produces a `drive` shim on PATH (~/.hx/bin) that runs drive.hexa. - -[package] -name = "drive" -version = "0.1.0" -entry = "drive.hexa" -description = "Model test-drive cart — load a local GGUF on llama-server and chat with it turn-by-turn (multi-turn history). The lightweight counterpart to a full agent TUI: no tools, no file edits, just ask -> answer to feel whether a vertical model can code, do math, etc." -license = "MIT" -authors = ["박민우 <nerve011235@gmail.com>"] -repository = "https://github.com/dancinlab/hexa-codex" -keywords = ["llm", "repl", "chat", "llama-server", "test-drive", "vertical-model", "hexa-family"] -categories = ["ai-research", "cli-tool"] - -[[bin]] -name = "drive" -path = "drive.hexa" - -[dependencies] -hexa-lang = ">=1.0.0" diff --git a/DRIVE/rag_retrieve.py b/DRIVE/rag_retrieve.py deleted file mode 100644 index 07cf5a7..0000000 --- a/DRIVE/rag_retrieve.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -"""DRIVE RAG retriever — HYBRID retrieval over the fix-recipe KB. - -Two-stage, precision-first (this beats embedding-only on this KB — the curated -keys are bilingual and queries quote them verbatim, so an exact substring hit is -100% correct, whereas MiniLM cosine mis-ranks short technical Korean phrases): - - STAGE 1 — keyword: scan the instruction for any key on a recipe line; if any - line's keys substring-match, emit those recipes (exact, high precision). - STAGE 2 — embedding (only when STAGE 1 is empty — the paraphrase long-tail): - embed the query + every recipe KEY via fastembed (in-process, ONNX/CPU, - ~120MB MiniLM), cosine-rank, emit top-k above a CONSERVATIVE threshold - (≥0.55 — high, because embedding mis-fires here and a wrong recipe - actively misleads the small model). Recipe-key vectors are cached to - fix_recipes.emb.json keyed by an MD5 of the KB so the cache - auto-invalidates when recipes change (no manual rebuild). - -Embedding backend (first that works wins): - - in-process fastembed (default — no network, no daemon, most robust) - - HTTP server (only if DRIVE_EMBED_URL is set — e.g. a pool host) - -Usage: rag_retrieve.py "<instruction>" -> prints "- <recipe>" lines -Exit 0 with empty stdout when neither stage clears (caller then proceeds with no -grounding). NEVER raises to the caller — every failure degrades to "". -""" -import sys, os, json, hashlib - -_HERE = os.path.dirname(os.path.abspath(__file__)) # this script's dir = pkg root -KB = os.path.join(_HERE, "fix_recipes.txt") # KB ships beside this script -CACHE = os.path.join(_HERE, "fix_recipes.emb.json") # embedding cache (regenerable) -MODEL = os.environ.get("DRIVE_EMBED_MODEL", "intfloat/multilingual-e5-large") -EMBURL = os.environ.get("DRIVE_EMBED_URL", "").strip() # optional HTTP fallback -TOPK = int(os.environ.get("DRIVE_RAG_TOPK", "3")) -THRESH = float(os.environ.get("DRIVE_RAG_THRESH", "0.80")) # e5 sims cluster high; reject weak -# e5 REQUIRES instruction prefixes — "query: " for the query, "passage: " for docs -QPREF = os.environ.get("DRIVE_EMBED_QPREFIX", "query: ") -DPREF = os.environ.get("DRIVE_EMBED_DPREFIX", "passage: ") - -_MODEL = None # lazy fastembed handle (loaded once per process) - - -def _embed_local(texts): - global _MODEL - from fastembed import TextEmbedding - if _MODEL is None: - _MODEL = TextEmbedding(model_name=MODEL) - return [v.tolist() for v in _MODEL.embed(list(texts))] - - -def _embed_http(texts): - import urllib.request - out = [] - for t in texts: - body = json.dumps({"input": t, "model": "e"}).encode() - req = urllib.request.Request(EMBURL, data=body, - headers={"Content-Type": "application/json"}) - with urllib.request.urlopen(req, timeout=30) as r: - out.append(json.load(r)["data"][0]["embedding"]) - return out - - -def embed(texts): - """Embed a list of strings -> list of vectors. Prefer in-process fastembed; - fall back to an HTTP server only when DRIVE_EMBED_URL is set and local fails.""" - try: - return _embed_local(texts) - except Exception: - if EMBURL: - return _embed_http(texts) - raise - - -def load_recipes(): - rows = [] - with open(KB, encoding="utf-8") as f: - for ln in f: - ln = ln.strip() - if not ln or ln.startswith("#") or "::" not in ln: - continue - keys, recipe = ln.split("::", 1) - rows.append((keys.strip(), recipe.strip())) - return rows - - -def keyword_hits(q, rows): - """STAGE 1 — exact substring match on a recipe line's keys (high precision).""" - out = [] - for keys, recipe in rows: - for key in keys.split(): - if key and key in q: - out.append("- " + recipe) - break - return out - - -def embedding_hits(q, rows): - """STAGE 2 — cosine top-k over recipe KEYS (paraphrase long-tail fallback).""" - import numpy as np - kbhash = hashlib.md5(open(KB, "rb").read()).hexdigest() - cache = {} - if os.path.exists(CACHE): - try: - cache = json.load(open(CACHE)) - except Exception: - cache = {} - if cache.get("hash") != kbhash or len(cache.get("vecs", [])) != len(rows) \ - or cache.get("model") != MODEL: - vecs = embed([DPREF + k for k, _ in rows]) # embed KEYS only (queries quote keys) - cache = {"hash": kbhash, "model": MODEL, "vecs": vecs} - try: - json.dump(cache, open(CACHE, "w")) - except Exception: - pass - V = np.asarray(cache["vecs"], dtype=np.float32) - qv = np.asarray(embed([QPREF + q])[0], dtype=np.float32) - qn = qv / (np.linalg.norm(qv) + 1e-9) - Vn = V / (np.linalg.norm(V, axis=1, keepdims=True) + 1e-9) - sims = Vn @ qn - out = [] - for i in np.argsort(-sims)[:TOPK]: - if float(sims[i]) >= THRESH: - out.append("- " + rows[int(i)][1]) - return out - - -def main(): - q = sys.argv[1] if len(sys.argv) > 1 else "" - if not q.strip() or not os.path.exists(KB): - return - rows = load_recipes() - if not rows: - return - out = keyword_hits(q, rows) # precision-first - if not out: - out = embedding_hits(q, rows) # paraphrase fallback (may raise -> caught) - if out: - sys.stdout.write("\n".join(out) + "\n") - - -if __name__ == "__main__": - try: - main() - except Exception: - pass # graceful: any failure -> empty output -> caller uses keyword fallback diff --git a/_drivesim/drive_one.exp b/_drivesim/drive_one.exp deleted file mode 100644 index d1f1b26..0000000 --- a/_drivesim/drive_one.exp +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/expect -f -# args: <dir> <instruction> — drive one NL turn, exit 0 on a completed action. -# KEY: send "\n" (NOT "\r") — drive's input() on the PTY only treats \n as EOL; -# a \r leaves the line uncommitted and drive hangs. Short gate on "생성 중" -# (resend if the boot race ate the line); long timeout for the actual generation. -set dir [lindex $argv 0] -set instr [lindex $argv 1] -log_user 0 -cd $dir -spawn drive -set timeout 60 -expect { "대화가 이어집니다" {} timeout { puts "TIMEOUT-BOOT"; exit 2 } } -expect { "you > " {} timeout { puts "TIMEOUT-PROMPT"; exit 2 } } -sleep 2 -set timeout 15 -set ok 0 -for {set t 0} {$t < 4} {incr t} { - send -- "$instr\n" - expect { - "생성 중" { set ok 1 } - "사용법" { puts "USAGE-ERR"; exit 7 } - timeout {} - } - if {$ok} break -} -if {!$ok} { puts "NO-GEN"; exit 6 } -set timeout 220 -expect { - "완료" {} - "빈 내용 반환" { puts "MODEL-EMPTY"; exit 4 } - "you > " { puts "ENDED-NO-DIRECTIVE"; exit 5 } - timeout { puts "TIMEOUT-EDIT"; exit 3 } -} -set timeout 15 -expect { "you > " {} timeout {} } -send -- "/quit\n" -expect eof diff --git a/_drivesim/gen.py b/_drivesim/gen.py deleted file mode 100644 index 3971723..0000000 --- a/_drivesim/gen.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -"""Generate 100 scenarios for the DRIVE smoke test — natural-language ONLY, the -target file is NEVER named (drive injects the folder listing as a system msg, so -a single-file folder is unambiguous). - - 70 code-fix : one broken python file; NL describes the symptom/fix. - 30 git-NL : a sandboxed git repo (LOCAL bare origin — never a real remote) - driven by NL to commit / push / branch+push. - -Manifest rows carry "kind"; the runner sets up git repos fresh each run. -""" -import json, math, pathlib - -ROOT = pathlib.Path(__file__).resolve().parent -SC = ROOT / "scenarios" - - -def make_code(i, prog, content, instr, expect): - d = SC / f"s{i:03d}" - d.mkdir(parents=True, exist_ok=True) - (d / prog).write_text(content) - return {"kind": "code", "dir": str(d), "prog": prog, "instr": instr, "expect": expect} - - -rows = [] -i = 0 -variants = [(2, 3), (4, 6), (7, 5), (9, 8), (6, 7), (3, 9), (8, 4)] # 7 -> 70 code - -for (x, y) in variants: - i += 1; rows.append(make_code(i, "calc.py", - "def add(a, b):\n return a + b\n\n# TODO: multiply is not implemented\n\n" - f'if __name__ == "__main__":\n print("R:", multiply({x}, {y}))\n', - "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - f"R: {x*y}")) - - i += 1; rows.append(make_code(i, "sum_tool.py", - "def total(a, b):\n return a - b # BUG: should add\n\n" - f'if __name__ == "__main__":\n print("R:", total({x}, {y}))\n', - "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - f"R: {x+y}")) - - i += 1; rows.append(make_code(i, "run.py", - "def sq(n)\n return n * n\n\n" - f'if __name__ == "__main__":\n print("R:", sq({x}))\n', - "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - f"R: {x*x}")) - - i += 1; rows.append(make_code(i, "square.py", - "def square(n):\n n * n # BUG: forgot to return\n\n" - f'if __name__ == "__main__":\n print("R:", square({x}))\n', - "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - f"R: {x*x}")) - - i += 1; rows.append(make_code(i, "bigger.py", - "def bigger(a, b):\n if a > b:\n return b\n return a # BUG: returns smaller\n\n" - f'if __name__ == "__main__":\n print("R:", bigger({x}, {y}))\n', - "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - f"R: {max(x,y)}")) - - i += 1; rows.append(make_code(i, "acc.py", - "def run(a, b):\n return acc # BUG: acc is undefined\n\n" - f'if __name__ == "__main__":\n print("R:", run({x}, {y}))\n', - "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - f"R: {x+y}")) - - i += 1; rows.append(make_code(i, "fact.py", - "def fact(n):\n r = 1\n for k in range(1, n): # BUG: should be n+1\n r *= k\n return r\n\n" - f'if __name__ == "__main__":\n print("R:", fact({x}))\n', - "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - f"R: {math.factorial(x)}")) - - i += 1; rows.append(make_code(i, "area.py", - "def area(w, h):\n return w / h # BUG: should multiply\n\n" - f'if __name__ == "__main__":\n print("R:", area({x}, {y}))\n', - "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - f"R: {x*y}")) - - i += 1; s = "hex" + str(x) - rows.append(make_code(i, "rev.py", - "def rev(s):\n return s # BUG: not reversed\n\n" - f'if __name__ == "__main__":\n print("R:", rev({s!r}))\n', - "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - f"R: {s[::-1]}")) - - i += 1; rows.append(make_code(i, "formula.py", - "def f(a, b):\n return a + b # BUG: should be a*b + 1\n\n" - f'if __name__ == "__main__":\n print("R:", f({x}, {y}))\n', - "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - f"R: {x*y+1}")) - -# ---- 30 git-NL scenarios (sandboxed: runner builds a repo + LOCAL bare origin) ---- -GIT_ROOT = pathlib.Path("/tmp/drivesim_git") # off-repo: bare origins never pollute the repo -def git_row(i, kind, instr, branch=None): - d = GIT_ROOT / f"s{i:03d}" - return {"kind": kind, "dir": str(d), "instr": instr, "branch": branch} - -# 12 commit-only (1 directive) -for j in range(12): - i += 1 - rows.append(git_row(i, "git_commit", - "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.")) - -# 10 commit+push (2 directives) -for j in range(10): - i += 1 - rows.append(git_row(i, "git_push", - "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.")) - -# 8 branch+commit+push (3 directives) -for j in range(8): - i += 1 - bn = f"feature-{i}" - rows.append(git_row(i, "git_branch", - f"방금 파일을 수정했어. '{bn}' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - branch=bn)) - -(ROOT / "manifest.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2)) -kinds = {} -for r in rows: - kinds[r["kind"]] = kinds.get(r["kind"], 0) + 1 -print(f"generated {len(rows)} scenarios: {kinds}") diff --git a/_drivesim/manifest.json b/_drivesim/manifest.json deleted file mode 100644 index 2dae722..0000000 --- a/_drivesim/manifest.json +++ /dev/null @@ -1,672 +0,0 @@ -[ - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s001", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 6" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s002", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 5" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s003", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 4" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s004", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 4" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s005", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 3" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s006", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 5" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s007", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 2" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s008", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 6" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s009", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 2xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s010", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 7" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s011", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 24" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s012", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 10" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s013", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 16" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s014", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 16" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s015", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 6" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s016", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 10" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s017", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 24" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s018", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 24" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s019", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 4xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s020", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 25" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s021", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 35" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s022", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 12" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s023", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 49" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s024", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 49" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s025", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 7" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s026", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 12" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s027", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 5040" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s028", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 35" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s029", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 7xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s030", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 36" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s031", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 72" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s032", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 17" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s033", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 81" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s034", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 81" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s035", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 9" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s036", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 17" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s037", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 362880" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s038", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 72" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s039", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 9xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s040", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 73" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s041", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 42" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s042", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 13" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s043", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 36" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s044", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 36" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s045", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 7" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s046", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 13" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s047", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 720" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s048", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 42" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s049", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 6xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s050", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 43" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s051", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 27" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s052", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 12" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s053", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 9" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s054", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 9" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s055", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 9" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s056", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 12" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s057", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 6" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s058", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 27" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s059", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 3xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s060", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 28" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s061", - "prog": "calc.py", - "instr": "이 폴더의 프로그램이 곱셈 함수가 없어서 죽어. 두 수의 곱을 반환하는 함수를 구현해서 실행되게 고쳐줘.", - "expect": "R: 32" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s062", - "prog": "sum_tool.py", - "instr": "이 폴더 프로그램이 두 수를 더해야 하는데 뺀 값이 나와. 더하기로 고쳐줘.", - "expect": "R: 12" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s063", - "prog": "run.py", - "instr": "이 폴더 프로그램이 문법 오류로 실행이 안 돼. 문법을 고쳐서 정상 실행되게 해줘.", - "expect": "R: 64" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s064", - "prog": "square.py", - "instr": "이 폴더 프로그램이 제곱값을 돌려줘야 하는데 None이 나와. 제곱값을 반환하도록 고쳐줘.", - "expect": "R: 64" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s065", - "prog": "bigger.py", - "instr": "이 폴더 프로그램이 두 수 중 큰 값을 반환해야 하는데 작은 값이 나와. 큰 값을 반환하도록 고쳐줘.", - "expect": "R: 8" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s066", - "prog": "acc.py", - "instr": "이 폴더 프로그램이 정의되지 않은 변수 때문에 죽어. 두 인자의 합을 반환하도록 고쳐줘.", - "expect": "R: 12" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s067", - "prog": "fact.py", - "instr": "이 폴더 프로그램이 팩토리얼을 계산해야 하는데 값이 하나 모자라게 나와. 올바른 팩토리얼이 나오도록 고쳐줘.", - "expect": "R: 40320" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s068", - "prog": "area.py", - "instr": "이 폴더 프로그램이 넓이를 가로*세로로 구해야 하는데 나눗셈을 해. 곱하기로 고쳐줘.", - "expect": "R: 32" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s069", - "prog": "rev.py", - "instr": "이 폴더 프로그램이 문자열을 뒤집어서 반환해야 하는데 원본 그대로 나와. 뒤집어서 반환하도록 고쳐줘.", - "expect": "R: 8xeh" - }, - { - "kind": "code", - "dir": "/Users/mini/dancinlab/hexa-codex/_drivesim/scenarios/s070", - "prog": "formula.py", - "instr": "이 폴더 프로그램의 함수가 a*b + 1 을 반환해야 하는데 a+b 를 반환해. 올바른 식으로 고쳐줘.", - "expect": "R: 33" - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s071", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s072", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s073", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s074", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s075", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s076", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s077", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s078", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s079", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s080", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s081", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_commit", - "dir": "/tmp/drivesim_git/s082", - "instr": "방금 파일을 수정했어. 지금까지 바뀐 내용을 적당한 메시지로 커밋해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s083", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s084", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s085", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s086", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s087", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s088", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s089", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s090", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s091", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_push", - "dir": "/tmp/drivesim_git/s092", - "instr": "방금 파일을 수정했어. 변경사항을 커밋한 다음 원격(origin)에 푸시해줘.", - "branch": null - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s093", - "instr": "방금 파일을 수정했어. 'feature-93' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-93" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s094", - "instr": "방금 파일을 수정했어. 'feature-94' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-94" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s095", - "instr": "방금 파일을 수정했어. 'feature-95' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-95" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s096", - "instr": "방금 파일을 수정했어. 'feature-96' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-96" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s097", - "instr": "방금 파일을 수정했어. 'feature-97' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-97" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s098", - "instr": "방금 파일을 수정했어. 'feature-98' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-98" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s099", - "instr": "방금 파일을 수정했어. 'feature-99' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-99" - }, - { - "kind": "git_branch", - "dir": "/tmp/drivesim_git/s100", - "instr": "방금 파일을 수정했어. 'feature-100' 라는 별도의 새 브랜치를 만들어서, 변경을 커밋하고 그 브랜치를 원격에 푸시해줘.", - "branch": "feature-100" - } -] \ No newline at end of file diff --git a/_drivesim/retry.sh b/_drivesim/retry.sh deleted file mode 100644 index 8a2c283..0000000 --- a/_drivesim/retry.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -# Re-run specific scenarios by id (sNNN ...) up to N attempts each, until each -# passes. Usage: retry.sh <attempts> s001 s015 ... -set -u -ROOT="/Users/mini/dancinlab/hexa-codex/_drivesim" -ATTEMPTS=${1:-3}; shift -STILL=() -for id in "$@"; do - idx=$((10#${id#s})) # s001 -> 1 - ok=0 - for ((a=1; a<=ATTEMPTS; a++)); do - out=$(bash "$ROOT/run.sh" "$idx" "$idx" 2>&1 | sed 's/\x1b\[[0-9;]*m//g') - if printf '%s' "$out" | grep -q "PASS"; then - echo "$id PASS (attempt $a)"; ok=1; break - else - echo "$id fail (attempt $a) :: $(printf '%s' "$out" | grep -E 'FAIL' | sed 's/.*FAIL//' | cut -c1-70)" - fi - done - [ $ok -eq 0 ] && STILL+=("$id") -done -echo "------" -if [ ${#STILL[@]} -eq 0 ]; then echo "ALL RETRIED SCENARIOS PASS"; else echo "STILL FAILING: ${STILL[*]}"; fi diff --git a/_drivesim/run.sh b/_drivesim/run.sh deleted file mode 100755 index 5157a73..0000000 --- a/_drivesim/run.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash -# Drive every manifest scenario through `drive` (natural language ONLY, file/branch -# never named beyond the NL ask) and verify the real effect. -# code -> the program now runs to exit 0 with the expected stdout substring -# git_* -> a sandboxed repo (LOCAL bare origin) received the commit/push/branch -# Usage: run.sh [START END] (1-based inclusive subset; default = all) -set -u -ROOT="/Users/mini/dancinlab/hexa-codex/_drivesim" -MAN="$ROOT/manifest.json" -EXP="$ROOT/drive_one.exp" -PASS=0; FAIL=0; FAILED_IDS=() - -n=$(jq length "$MAN") -START=${1:-1}; END=${2:-$n} - -drive_it(){ expect -f "$EXP" "$1" "$2" >/dev/null 2>&1; return $?; } - -setup_git(){ # <dir> <kind> - local dir="$1" bare="$1.origin.git" - rm -rf "$dir" "$bare"; mkdir -p "$dir" - git init -q -b main "$dir" - printf 'build/\n*.bin\n__pycache__/\n*.bak\n' > "$dir/.gitignore" # keep drive's build/ out of commits - printf 'x = 1\nprint("app", x)\n' > "$dir/app.py" - git -C "$dir" add -A; git -C "$dir" -c user.name=t -c user.email=t@t commit -q -m "init" - git init --bare -q "$bare" - git -C "$dir" remote add origin "file://$bare" - git -C "$dir" push -q -u origin main - # simulate a user edit so there is something to commit - printf 'y = 2\nprint("more", y)\n' >> "$dir/app.py" -} - -printf "%-6s %-12s %-7s %s\n" "id" "kind" "result" "detail" -printf '%s\n' "------------------------------------------------------------------------" -for ((k=START-1; k<END; k++)); do - kind=$(jq -r ".[$k].kind" "$MAN") - dir=$(jq -r ".[$k].dir" "$MAN") - instr=$(jq -r ".[$k].instr" "$MAN") - id=$(basename "$dir") - - if [ "$kind" = "code" ]; then - prog=$(jq -r ".[$k].prog" "$MAN"); want=$(jq -r ".[$k].expect" "$MAN") - if [ -f "$dir/$prog.orig" ]; then cp "$dir/$prog.orig" "$dir/$prog"; else cp "$dir/$prog" "$dir/$prog.orig"; fi - rm -f "$dir/$prog.bak" - drive_it "$dir" "$instr"; drc=$? - out=$(cd "$dir" && python3 "$prog" 2>&1); prc=$? - if [ $prc -eq 0 ] && printf '%s' "$out" | grep -qF -- "$want"; then - PASS=$((PASS+1)); printf "%-6s %-12s %-7s %s\n" "$id" "$kind" "PASS" "got[$want]" - else - FAIL=$((FAIL+1)); FAILED_IDS+=("$id") - printf "%-6s %-12s %-7s %s\n" "$id" "$kind" "FAIL" "rc=$prc drv=$drc want[$want] out=$(printf '%s' "$out"|tr '\n' ' '|cut -c1-50)" - fi - else - bare="$dir.origin.git" - setup_git "$dir" "$kind" - drive_it "$dir" "$instr"; drc=$? - ncommits=$(git -C "$dir" rev-list --count HEAD 2>/dev/null) - clean=$(git -C "$dir" status --porcelain 2>/dev/null) - ok=0; detail="" - case "$kind" in - git_commit) - if [ "$ncommits" = "2" ] && [ -z "$clean" ]; then ok=1; detail="commit ok (n=2 clean)"; else detail="commits=$ncommits dirty='$clean'"; fi ;; - git_push) - lh=$(git -C "$dir" rev-parse HEAD 2>/dev/null); rh=$(git -C "$bare" rev-parse main 2>/dev/null) - if [ "$ncommits" = "2" ] && [ -n "$rh" ] && [ "$lh" = "$rh" ]; then ok=1; detail="pushed main->origin"; else detail="commits=$ncommits local=$lh origin=$rh"; fi ;; - git_branch) - bn=$(jq -r ".[$k].branch" "$MAN") - if git -C "$bare" show-ref --verify --quiet "refs/heads/$bn"; then ok=1; detail="origin has $bn"; else detail="no origin/$bn (refs: $(git -C "$bare" for-each-ref --format='%(refname:short)' | tr '\n' ',' ))"; fi ;; - esac - if [ $ok -eq 1 ]; then - PASS=$((PASS+1)); printf "%-6s %-12s %-7s %s\n" "$id" "$kind" "PASS" "$detail" - else - FAIL=$((FAIL+1)); FAILED_IDS+=("$id") - printf "%-6s %-12s %-7s %s\n" "$id" "$kind" "FAIL" "drv=$drc $detail" - fi - fi -done -printf '%s\n' "------------------------------------------------------------------------" -echo "TALLY: PASS=$PASS FAIL=$FAIL / $((END-START+1))" -[ $FAIL -gt 0 ] && echo "FAILED: ${FAILED_IDS[*]}" -exit 0 diff --git a/_drivesim/run100.sh b/_drivesim/run100.sh deleted file mode 100755 index d3987d2..0000000 --- a/_drivesim/run100.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -# Full 100-scenario orchestrator for the DRIVE RAG test. -# 1. (re)boot llama-server on :8099 with 4 slots (-np 4) so 4 drives run parallel -# 2. fan out 4 shards (1-25 / 26-50 / 51-75 / 76-100) of run.sh -# 3. aggregate pass/fail; retry failures up to 2x (independent re-draws absorb -# the local-model + boot-race flakiness) -# Usage: run100.sh -set -u -ROOT="/Users/mini/dancinlab/hexa-codex/_drivesim" -MODEL="$HOME/Models/gguf/supergemma4-e4b-abliterated-Q4_K_M.gguf" -cd "$ROOT" - -echo "=== 1. reboot llama-server with -np 4 ===" -pkill -f 'llama-server.*8099' 2>/dev/null; sleep 2 -nohup llama-server -m "$MODEL" --host 127.0.0.1 --port 8099 -np 4 -c 16384 \ - > /tmp/drive-llama-server.log 2>&1 & -for i in $(seq 1 60); do - curl -s -m2 http://127.0.0.1:8099/health 2>/dev/null | grep -q '"status":"ok"' && { echo " server READY (4 slots) after ${i}x2s"; break; } - [ $i -eq 60 ] && { echo " server FAILED to boot"; exit 1; } - sleep 2 -done - -echo "=== 2. fan out 4 shards (parallel) ===" -for sh in "1 25" "26 50" "51 75" "76 100"; do - set -- $sh - nohup bash run.sh $1 $2 > "/tmp/shard_${1}.log" 2>&1 & - echo " shard [$1-$2] PID=$!" -done -wait -echo "=== 3. shards done — aggregate ===" -cat /tmp/shard_1.log /tmp/shard_26.log /tmp/shard_51.log /tmp/shard_76.log 2>/dev/null \ - | sed 's/\x1b\[[0-9;]*m//g' > /tmp/run100_all.log -P=$(grep -cE ' PASS ' /tmp/run100_all.log); F=$(grep -cE ' FAIL ' /tmp/run100_all.log) -echo " PASS=$P FAIL=$F / 100" -grep -E ' FAIL ' /tmp/run100_all.log | awk '{print $1}' | tr '\n' ' ' > /tmp/run100_fails.txt -echo " fails: $(cat /tmp/run100_fails.txt)" -echo "RUN100-COMPLETE PASS=$P FAIL=$F"