Files
EJClaw/groups/global/CLAUDE.md
2026-08-23 19:54:52 +09:00

26 KiB
Raw Blame History

Global Memory

This file is for mutable memory shared across Claude groups.

Use it for durable facts, preferences, and shared context that may change over time.

Do not store platform-wide operating rules here. Those now live in prompts/claude-platform.md.

Host hardware (GPU)

This host has an NVIDIA GPU available for compute:

  • GPU: NVIDIA GeForce RTX 5050 (GB207, Blackwell), 8GB VRAM, compute capability 12.0 (sm_120)
  • Driver 595.71.05, CUDA runtime 13.2; libcuda.so present so GPU compute is usable
  • Ready-to-use PyTorch (CUDA 12.8 / Blackwell-capable) venv at /home/claude/gpu-venv — run with /home/claude/gpu-venv/bin/python. torch.cuda.is_available() is True.
  • For CUDA C compilation, nvcc is not installed; install the CUDA Toolkit only if a task needs it.

Behavioral rule: when a task could meaningfully benefit from the GPU (ML inference/training, heavy parallel compute, large media processing), first ask the user whether they want to use the GPU before doing so. Do not silently run GPU workloads. Once confirmed, use the /home/claude/gpu-venv Python (or install what the task needs) and verify with a real GPU run.

  • 2026-06-18 exception (javis_bot project): the user pre-approved GPU use for the javis_bot project — do NOT ask before using the GPU on javis_bot tasks. Other projects still follow the ask-first rule above.

javis_bot deployment target

  • 2026-06-18: the real production deployment runs on the user's Windows 11 gaming PC (CPU Ryzen 9 9950X3D, GPU RTX 5070Ti — Blackwell, same sm_120 arch as this host's RTX 5050) with Docker. Use docker-compose.yml + docker-compose.gpu-windows.yml (Docker Desktop + WSL2 GPU). This Linux host is only the LAN browser host (JARVIS_ROLE=browser); test changes here as a Blackwell-GPU proxy but remember the bot/brain role actually runs on the Windows box. The 1516396486265405548 value the user pasted on 2026-06-18 was just a Discord message ID in the chat channel, not a guild/voice-channel ID.

Host RAM / disk safety (tmpfs rule)

/tmp (7.2G) and /dev/shm (13G) are RAM-backed tmpfs. Anything large written there consumes RAM, not disk.

  • 2026-06-11 incident: MeloTTS Korean-TTS venv (~2.4GB) was installed into /tmp/melo311 (tmpfs), eating RAM and risking OOM. Fixed by moving it to disk at /home/claude/jarvis-tts/melo311 and updating 43 script shebangs.
  • Recurrence guards (in place, defence-in-depth):
    1. ~/.bashrc exports TMPDIR=$HOME/.tmp, UV_CACHE_DIR=$HOME/.cache/uv, PIP_CACHE_DIR=$HOME/.cache/pip (all ext4 disk), above the interactive-shell guard so non-interactive agent shells inherit it.
    2. The ejclaw.service user unit (~/.config/systemd/user/ejclaw.service) sets the same three Environment= vars, so every child process (agent runners, install/build steps that never source .bashrc) inherits disk temp at the systemd root. Applied via daemon-reload; takes full effect on the next ejclaw.service restart. Do NOT restart it from inside a live agent turn (the agent is its descendant).
    3. Scheduled watchdog (cron */30) alerts the group only if /tmp or /dev/shm > 3GB, available RAM < 4GB, or disk free < 10GB, and auto-deletes $HOME/.tmp files older than 3 days. This catches an explicit /tmp/... write that bypasses TMPDIR.
  • Rule: NEVER install venvs, models, datasets, or any multi-GB artefact into /tmp or /dev/shm. Use disk paths under $HOME (e.g. /home/claude/jarvis-tts). If a tool ignores TMPDIR, pass an explicit disk path. EJClaw runs directly via bun under the user systemd unit (no Docker/compose in use here).
  • As of 2026-06-11 the root filesystem was expanded: / (ext4) is now 353G total with ~214G free (37% used); earlier it was 156G/84%. RAM is 30GB total, usually ~25GB free. Adding RAM is not the real fix for tmpfs blowups; keeping big artefacts on disk is. Disk is no longer tight, but the watchdog still warns if free drops below 10GB.
  • The two TTS scratch scripts (/home/claude/jarvis-tts/gen_melo.py, gen_xtts.py) now route output to disk and hard-refuse any /tmp or /dev/shm write path via a disk_path() guard.
  • 2026-07-26 OOM incident + MemoryHigh change: the ejclaw.service base unit set MemoryHigh=3G (soft cgroup cap; MemoryMax unset). An agent-launched GPU/diffusers job in the anyway2 channel (gen_banner.py, ~2.9GB RSS on its own) ran INSIDE the bot's cgroup, pushed total past 3G, and systemd-oomd oom-killed ejclaw.service ~6× in a row (systemd auto-restarted each time → restart loop; the banner also never finished). Fix: raised the soft cap to MemoryHigh=16G via a documented drop-in ~/.config/systemd/user/ejclaw.service.d/10-memory-high.conf (applied with daemon-reload, no restart needed for a cgroup-property change). Rationale: host has 30G, KAclaw is capped at 3G and other services ~2G, so 16G leaves ~11G+cache headroom while still capping runaways; MemoryMax stays infinity so a burst is throttled, not hard-killed. The base unit still reads 3G — the drop-in overrides it (systemctl --user cat ejclaw shows both). Cleaner long-term alternative (not done): keep the bot cap tight and launch heavy GPU jobs OUTSIDE the bot cgroup via systemd-run --user --scope.

Outbound attachment allowed folders

Discord/outbound attachments are only delivered if the file lives inside an allowlisted directory; files elsewhere are rejected as outside-allowed-dirs and the bot now appends a visible "전송 실패" notice (it no longer drops silently).

Standing rule (2026-06-12 user decision): the single canonical outbound attachment folder is /home/claude/EJClaw/data/attachments (the EJClaw DATA_DIR attachments dir). It is already allow-listed by default — no .env registration is needed. To send ANY file, first write or copy it into that folder (a subfolder is fine, e.g. data/attachments/generated/), then reference that path in the MEDIA: directive. Do NOT widen the allowlist to other folders (the earlier idea of allow-listing /home/claude/jarvis-tts was reverted at the user's request, and auto-copying arbitrary out-of-allowlist files in the delivery layer was also reverted because it bypasses cross-room / sensitive-file protection).

  • Concretely: when attaching generated media (TTS audio, images, etc.), cp it into /home/claude/EJClaw/data/attachments/ (or a subfolder) and emit MEDIA:/home/claude/EJClaw/data/attachments/.... This needs no restart, no env change, no code change.
  • The TTS scripts under /home/claude/jarvis-tts still write their working output there; copy the resulting .wav into data/attachments before attaching (jarvis-tts itself is NOT allow-listed).
  • System defaults still allowed for internal use: the codex generated_images dir and the system temp dir (used for auto-generated screenshots). Only data/attachments is the place WE deliberately stage files to send.
  • Safety net: if a file is ever referenced from outside an allowed folder, delivery now appends a visible "전송 실패 (사유)" notice instead of silently dropping it.
  • Auto-cleanup (2026-06-12 user decision): prefer /home/claude/EJClaw/data/attachments/generated/ for files we send. A user crontab entry sweeps that subfolder every 15 min and deletes files older than 60 min (find .../data/attachments/generated -type f -mmin +60 -delete). The sweep is scoped to generated/ only, so system-managed attachments under data/attachments (e.g. paired-turn-outputs/, dashboard copies) are never touched. If a sent file must persist longer than ~1h, put it directly under data/attachments/ (not generated/).

Stored credentials

Shared credentials live at /home/claude/.config/ejclaw/secrets.json (chmod 600, owner-only). Read with the Read tool when a session in any channel asks to "저장해둔 계정토큰으로 로그인" or otherwise needs a registered token.

Schema: credentials.<host>.{type, host, token, note, added_at}.

Currently stored:

  • git.tkrmagid.kr — Gitea personal access token. Use via Authorization: token <value> header, or embed in HTTPS URL as https://<user>:<token>@git.tkrmagid.kr/.... For git clone/push, prefer the URL form or git -c http.extraHeader="Authorization: token <value>" clone .... Do not paste the raw token into chat replies.
  • sudo — local sudo password for the claude user on this host. Use via echo "$PW" | sudo -S <cmd> (read the password from credentials.sudo.password with the Read tool, then pipe). Do not paste the raw password into chat replies.
  • discord.com/bot/testbot — Discord test bot (테스트봇, client/app ID 1538122882528321536), stored 2026-08-15 for use across all chats. Read the token from credentials["discord.com/bot/testbot"].token in secrets.json with the Read tool (token is NOT written here). Use as Authorization: Bot <token> against https://discord.com/api/v10, or as the bot token for a discord.js/gateway login. Do not paste the raw token into chat replies. Token was chat-exposed on save; if used for anything sensitive/long-term, rotate it in the Discord Developer Portal first. Re-ask the user if Discord returns 401., edit secrets.json and append a new entry under credentials; update this list with the host and intended use.

User communication preferences

  • 2026-05-27: 모든 채팅에서 사용자에게 답변할 때 기본적으로 존댓말을 사용한다. 특별히 반말을 요청받지 않는 한 반말로 답변하지 않는다.

Room mode policy

All rooms default to tribunal (paired) mode. Owner runs the work, reviewer/arbiter (claude-code) verifies. New rooms registered via bun setup/index.ts --step register are also tribunal by default.

If the user in a channel says any of these — "클로드 사용하지 말자", "paired 모드 끄자", "리뷰어 끄자", "이 방은 single 로 바꿔줘" — switch that channel back to single mode. Use:

bun -e "import { initDatabase, setExplicitRoomMode } from './src/db.js'; initDatabase(); setExplicitRoomMode('<chatJid>', 'single');"

The reverse phrase ("paired 켜자", "리뷰어 다시 켜자") flips it back to 'tribunal'. Acknowledge the change in chat and confirm the new mode.

Room deregistration (채팅 등록 해제 + 데이터 정리)

When the user says any of — "채팅 아이디 등록해제 해줘 ", "이 채널 등록 해제해줘", "채널 등록 해제" (with a channel id / JID) — the standing behavior (user decision 2026-08-18) is: fully unregister the room AND purge all managed data, keeping ONLY the chat channel record and its message history, then hot-reload (SIGHUP, NOT a full restart — see below) so the channel goes unregistered in the live process.

Use the canonical script (keeps chats + messages, removes everything else — room_settings/role/skill overrides, paired tasks/turns/attempts/outputs/reservations/leases/projects/handoffs, work_items, scheduled_tasks, task_run_logs, sessions, router cursor, and the on-disk groups/<folder>, data/workspaces/<folder>, data/sessions/<folder>, data/ipc/<folder> including any git worktree):

cd /home/claude/EJClaw
bun scripts/deregister-room.ts --dry-run <channelId...>   # preview what will be removed
bun scripts/deregister-room.ts <channelId...>             # execute the purge

Accepts bare Discord channel IDs (auto-prefixed to dc:) or full JIDs; multiple ids allowed. Safety built in: refuses a is_main room without --force; a group folder still used by another room is NOT deleted on disk (only that chat's DB rows); --dry-run writes nothing.

Applying to the live process is now automatic: after the purge the script calls signalEjclawReload(), which sends SIGHUP to the running service's main PID → runtimeState.reloadRoomBindings(), so the room drops from live bindings with NO full restart. Do NOT run a plain systemctl restart from inside a turn (it kills the agent mid-command and can't be confirmed). Verify by the Room bindings reloaded log or getRegisteredGroup(jid) → undefined. Always take a quick DB backup first for irreversible purges: cp store/messages.db /home/claude/ejclaw-db-backup-$(date +%Y%m%d-%H%M%S).db.

Channel registration + making it live (no false restarts) — MANDATORY

The agent runs INSIDE ejclaw.service, so a plain systemctl --user restart ejclaw.service from within a turn kills the agent mid-command: the restart is unreliable and you CANNOT confirm it in the same turn. Never claim a restart/reload happened without evidence — the repeated "재시작 됐다고 했는데 안 됨" complaints came from claiming success without verifying.

When you register (or deregister) a channel via a DB write (bun script / setup --step register) from a non-main room, the running process does NOT see it until its in-memory room bindings are reloaded. Preferred way — hot-reload, NO full restart, agent survives so you can verify in the same turn:

kill -HUP "$(systemctl --user show -p MainPID --value ejclaw.service)"

src/index.ts handles SIGHUP → runtimeState.reloadRoomBindings() (re-reads ONLY room bindings from the DB; message cursors/sessions untouched, so no re-processing). Sending HUP to the MainPID only (not the whole cgroup) leaves agent subprocesses alive. After it, VERIFY before reporting: look for the Room bindings reloaded log with the new groupCount, or check getRegisteredGroup(jid) resolves. Standing user rule: registering a channel must be followed immediately by this live-reload (this is the "등록하면 자동 재시작/반영" the user asked for) — do it once, verify, then report.

Only when a change truly needs a full process restart (e.g. code/env changes), use the detached form so it survives the agent's death, and verify in a SEPARATE scheduled task (new PID / start time) — do it exactly once:

systemd-run --user --collect --unit="ejclaw-restart-$$" systemctl --user restart ejclaw.service

washing_machine_app (EasyAppliance Android) deployment

  • 2026-08-06 user standing rule: for the washing_machine_app project, ALWAYS deploy after a change and deliver via in-app update — do not just commit. Every meaningful change ends with a published Gitea release so the in-app updater offers it.
  • Build env: default JDK on this host is Java 25, which the Gradle Kotlin compiler can't parse ("IllegalArgumentException: 25.0.3"). Build with export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64. Android SDK at /home/claude/android-sdk (build-tools 35.0.0 has aapt/apksigner).
  • Release process: bump versionCode/versionName defaults in app/build.gradle.kts (also passable via -PverCode -PverName), ./gradlew assembleRelease (auto-signed via keystore.properties → keystore at /home/claude/.config/ejclaw/washing_machine_app.keystore, release cert SHA-256 56246bd2…; every release MUST use this same key or in-place update breaks). Then create a Gitea release + upload the APK asset named EasyAppliance-v<ver>.apk.
  • Release host: Gitea git.tkrmagid.kr, repo tkrmagid/washing_machine_app. Use the stored git.tkrmagid.kr token (secrets.json). API: POST /api/v1/repos/tkrmagid/washing_machine_app/releases then POST /releases/<id>/assets?name=... with the APK. The app's UpdateRepository reads releases via the public API and offers any release whose tag (minus v) is newer than installed. Latest published: v0.3.9 (verCode 12) as of 2026-08-06. Token now stored encrypted (AndroidKeyStore AES/GCM via TokenCrypto); allowBackup=false, usesCleartextTraffic=false.
  • No git remote is configured in the owner worktree; the Gitea release (APK) is the delivery mechanism, separate from any source push.
  • Real-device tuning: the user's SmartThings Personal Access Token is stored in secrets.json under api.smartthings.com (Bearer, https://api.smartthings.com/v1). Use it to read actual device capabilities/supported values so preset/status mappings stay accurate. Their real devices: 세탁기 b9b0b550-… (water temp enum none/cold/20/30/40/60/90; spin custom.washerSpinLevel; rinse custom.washerRinseCycles; NO manual water level — auto), 벽걸이/스탠드 에어컨 (mode cool/dry/wind/aIComfort; fan auto/1/2/3/4/max; setpoint 16–30). SmartThings commands are type-strict: appliance enum args (washer temp "40", fan "1", rinse "3") are STRINGS; only setpoint-style commands are NUMBERS — SmartThingsDeviceRepository.encodeArg handles this via a NUMERIC_COMMANDS allowlist.

Git backups

  • 2026-05-27 10:53 KST: EJClaw 정상 동작 상태를 git commit 1509108 (backup current stable ejclaw state)로 백업했다. "이번 요청만 리뷰어 사용" 기능 작업은 이 백업 커밋 이후에 시작한 변경이다.

Per-service Claude credential isolation (user changes, verified 2026-06-23)

The user reworked credential handling so each service reads its OWN Claude credentials file instead of all sharing ~/.claude/.credentials.json. Goal: run each service on a different Claude account.

  • Resolver: src/claude-credentials-path.ts — getClaudeCredentialsPath(accountIndex, {allowHomeFallback}) and hasExplicitClaudeCredentialsPath(). Order: account 0 → CLAUDE_CREDENTIALS_PATH; account 1+ → CLAUDE_ACCOUNTS_DIR/<n>/.credentials.json; else home fallback (~/.claude or ~/.claude-accounts/<n>). The same module exists in both EJClaw and KAclaw.
  • Wired into: setup/login.ts (writes to CLAUDE_CREDENTIALS_PATH; PKCE state file is now per-path-hashed ejclaw-claude-login-<hash>.json so different-account logins don't collide), src/token-refresh.ts, src/claude-usage.ts, src/agent-runner-environment.ts (pre-syncs creds into each session .claude/.credentials.json), src/runtime-inventory.ts.
  • Credential paths in use:
    • claude CLI: ~/.claude/.credentials.json
    • EJClaw: /home/claude/EJClaw/data/claude/.credentials.json (set via CLAUDE_CREDENTIALS_PATH in EJClaw/.env)
    • KAclaw: /home/claude/KAclaw/data/claude/.credentials.json (set via CLAUDE_CREDENTIALS_PATH in KAclaw/.env)
  • Relevant env flags (EJClaw and KAclaw both): CLAUDE_USAGE_USE_HOME_CREDENTIALS=false, CLAUDE_TOKEN_REFRESH_USE_CREDENTIALS=true, CLAUDE_TOKEN_REFRESH_USE_HOME_CREDENTIALS=false. The token-refresh loop only runs when CLAUDE_TOKEN_REFRESH_USE_CREDENTIALS=true OR *_USE_HOME_CREDENTIALS=true.
  • To re-auth ONE service: run its login flow with that service's CLAUDE_CREDENTIALS_PATH exported, then send the new code#state. Do NOT copy one service's .credentials.json to another path — that collapses them onto one token + one shared refresh token, and when both refresh independently the refresh token rotates and the other side breaks with invalid_grant.
  • Account-identity reality (verified via https://api.anthropic.com/api/oauth/profile, 2026-06-23 ~19:35 KST): despite the intent, all three paths still resolved to the SAME account tkrmagid@gmail.com (Max). CLI and KAclaw even held byte-identical access+refresh tokens (from an earlier ~/.claude→KAclaw copy); EJClaw held a different token of the same account. Before assuming the services are on distinct accounts, verify the actual account by calling the profile API for each path's accessToken (compare account.email), not just by checking that the file paths differ.
  • KAclaw is NOT a git repo (no .git), so its source changes can't be diffed/version-controlled. 2026-06-23 dashboard usage-row fix (utilization of exactly 1 rendering as 100%, > 1 → >= 1 in dashboard-usage-rows.ts) lives only in the KAclaw working tree. EJClaw's copy of that file may carry the same > 1 pattern — worth checking if the EJClaw dashboard ever shows a window at exactly 1%.
  • UPDATE 2026-06-23 ~21:26 KST: KAclaw was re-logged onto a DIFFERENT account at the user's request. Now EJClaw = tkrmagid@gmail.com (Max) and KAclaw = hjj100411@gmail.com (NOT Max — has_claude_max=false, so Pro/Free, lower limits). Done via KAclaw's own setup/index.ts --step login with CLAUDE_CREDENTIALS_PATH=/home/claude/KAclaw/data/claude/.credentials.json exported, then updated KAclaw .env CLAUDE_CODE_OAUTH_TOKEN(S) to the new access token and restarted kaclaw.service. claude CLI (~/.claude) is still tkrmagid@gmail.com. So EJClaw and KAclaw are now genuinely different accounts; verify with the profile-API email check if in doubt.

Codex (reviewer) credential isolation (2026-07-22)

EJClaw's paired-room reviewer runs as Codex (gpt-5.5) with ChatGPT-OAuth auth. It shared ~/.codex/auth.json with other consumers (manual codex CLI, KAclaw same-home), so refreshes rotated the token and the stale copy died with "access token could not be refreshed because your refresh token was already used." Every reviewer turn then failed → task ended reviewer_codex_unavailable and escalated straight to the arbiter, so chats showed owner + arbiter but the reviewer never posted. Diagnose via paired_turn_attempts.last_error in store/messages.db.

Fix (mirrors the Claude per-service isolation above): EJClaw now uses a dedicated Codex account dir ~/.codex-accounts/0/ instead of shared ~/.codex. src/codex-token-rotation.ts initCodexTokenRotation() loads numbered dirs under ~/.codex-accounts/ as the rotation pool and ignores the ~/.codex fallback when the dir has entries; EJClaw leases + writes-back refreshes to ~/.codex-accounts/0/auth.json (syncCodexSessionAuthBack), isolated from the CLI and KAclaw.

  • Activation needs an ejclaw restart — initCodexTokenRotation() runs once at process start (src/index.ts), so a running ejclaw keeps the old cached account until systemctl --user restart ejclaw. Do NOT restart from inside a live agent turn (the agent is a descendant).
  • Re-auth runbook (the procedure CHANGED): refresh the reviewer's Codex login into the isolated dir, NOT default ~/.codex: CODEX_HOME=/home/claude/.codex-accounts/0 /home/claude/EJClaw/node_modules/.bin/codex login --device-auth then open the printed https://auth.openai.com/codex/device URL and enter the one-time code. A plain codex login writes to ~/.codex and will NOT update the account EJClaw uses. Account: ChatGPT account_id 7e40ecd8-461b-49bd-bbe4-cdc6c7dc68cf.
  • Verify: cd /home/claude/EJClaw && bun -e 'import {initCodexTokenRotation,getActiveCodexAuthPath} from "./src/codex-token-rotation.ts"; initCodexTokenRotation(); console.log(getActiveCodexAuthPath())' → should print /home/claude/.codex-accounts/0/auth.json.

.5 Docker host + remote docker context (2026-07-21)

A second LAN machine runs Docker workloads; any Claude channel on .9 can drive it.

  • Identity: 192.168.10.5 — a Proxmox LXC container hostnamed docker, Debian 13, Docker 29.3.0, root-only shell, no GPU. This host (.9, 192.168.10.9, "claude") is a QEMU/KVM VM with the RTX 5050 passed through — so .9 has the GPU, .5 does not.
  • SSH: passwordless key auth is installed (claude@.9 pubkey in root@.5:~/.ssh/authorized_keys) → ssh root@192.168.10.5 needs no password. Fallback root password in secrets.json under docker5.ssh.
  • Docker context docker5 (ssh://root@192.168.10.5) exists on .9 and is the active default context, so a plain docker .../docker compose ... on .9 runs on .5. Use docker --context default ... (or docker context use default) for .9's own local docker (where the GPU Ollama service for B would run); switch back with docker context use docker5.
  • .5 runs ~19 production containers — do NOT disturb: projects bot (mamil, memil, music1/2/3, random), site (make_video_site, minecraft_launcher_site, signature), virtual-stock-site-bot (api/web/worker), mc_domain_proxy (mc-filter api/frontend/nginx/proxy), act_runner, portainer, spotify-tokener. Compose files under root@.5:/root/other/*/docker-compose.yml.
  • GPU-from-.5: since .5 has no GPU device, GPU work for .5 containers must go over the network to .9 (e.g. Ollama on .9 --gpus all bound to 0.0.0.0:11434, .5 calls http://192.168.10.9:11434). In-container CUDA (e.g. faster-whisper device=cuda) will NOT work on .5.

CLAUDE.md

Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.

Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.

1. Think Before Coding

Don't assume. Don't hide confusion. Surface tradeoffs.

Before implementing:

  • State your assumptions explicitly. If uncertain, ask.
  • If multiple interpretations exist, present them - don't pick silently.
  • If a simpler approach exists, say so. Push back when warranted.
  • If something is unclear, stop. Name what's confusing. Ask.

2. Simplicity First

Minimum code that solves the problem. Nothing speculative.

  • No features beyond what was asked.
  • No abstractions for single-use code.
  • No "flexibility" or "configurability" that wasn't requested.
  • No error handling for impossible scenarios.
  • If you write 200 lines and it could be 50, rewrite it.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

3. Surgical Changes

Touch only what you must. Clean up only your own mess.

When editing existing code:

  • Don't "improve" adjacent code, comments, or formatting.
  • Don't refactor things that aren't broken.
  • Match existing style, even if you'd do it differently.
  • If you notice unrelated dead code, mention it - don't delete it.

When your changes create orphans:

  • Remove imports/variables/functions that YOUR changes made unused.
  • Don't remove pre-existing dead code unless asked.

The test: Every changed line should trace directly to the user's request.

4. Goal-Driven Execution

Define success criteria. Loop until verified.

Transform tasks into verifiable goals:

  • "Add validation" → "Write tests for invalid inputs, then make them pass"
  • "Fix the bug" → "Write a test that reproduces it, then make it pass"
  • "Refactor X" → "Ensure tests pass before and after"

For multi-step tasks, state a brief plan:

1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.


These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.