diff --git a/FEATURE_LIST.md b/FEATURE_LIST.md index d7413919c..bfe2b9eaa 100644 --- a/FEATURE_LIST.md +++ b/FEATURE_LIST.md @@ -41,7 +41,7 @@ | Session message management | ✅ | Supports session history maintenance and serialization | | Error recovery / re-login | 🟡 | Basic auth error handling and reconfiguration flow exists | | Token / Cost tracking | 🚫 | Chat CLI does not yet have a complete statistics view | -| Context building | 🟡 | Basic `context_system` supports workspace / git / `CLAUDE.md` injection; still missing README summary, memory, compact | +| Context building | 🟡 | Basic `context_system` supports workspace / git / `CLAWCODEX.md` injection; still missing README summary, memory, compact | | Claude Code Agent Loop | ✅ | Implemented in agent_loop.py, supports tool call loops | | `/resume` session recovery | 🚫 | No standalone recovery flow or UI yet | | `/compact` conversation compaction | 🚫 | No automatic/manual compaction capability yet | @@ -114,7 +114,7 @@ | Tool Execution Engine | ✅ | Tool loading, execution, and result feedback loop implemented | | Output Styles | ✅ | Output style loading system implemented | | Session Persistence | ✅ | Session save/load capability exists | -| Context Engine | 🟡 | Basic context building pipeline connected, supports workspace, git, `CLAUDE.md` prompt injection | +| Context Engine | 🟡 | Basic context building pipeline connected, supports workspace, git, `CLAWCODEX.md` prompt injection | | Permission Engine | 🟡 | Framework exists, not fully integrated into tool execution flow | | Compaction Engine | 🚫 | No conversation compaction or token management capability | | Hook Runtime | 🚫 | No settings-driven hook execution mechanism | @@ -201,7 +201,7 @@ Goal: Move from "mirrored tool catalog" to "genuinely executable Python Agent". Goal: Complete Claude Code's engineering capabilities. - [ ] Workspace context building improvements -- [x] git status / file tree / `CLAUDE.md` injection (basic version) +- [x] git status / file tree / `CLAWCODEX.md` injection (basic version) - [ ] README / entry file summary injection - [ ] Memory and historical context management - [ ] Full permission system integration @@ -351,7 +351,7 @@ You can introduce the project like this: | 会话消息管理 | ✅ | 支持会话历史维护与序列化 | | 错误恢复 / 重新登录 | 🟡 | 已有基础认证错误处理与重新配置流程 | | Token / Cost 跟踪 | 🚫 | 当前聊天 CLI 尚未形成完整统计视图 | -| 上下文构建 | 🟡 | 已有 `context_system` 基础版,支持 workspace / git / `CLAUDE.md` 注入,仍缺 README 摘要、memory、compact | +| 上下文构建 | 🟡 | 已有 `context_system` 基础版,支持 workspace / git / `CLAWCODEX.md` 注入,仍缺 README 摘要、memory、compact | | Claude Code Agent Loop | ✅ | 已实现 agent_loop.py,支持工具调用循环 | | `/resume` 会话恢复体验 | 🚫 | 暂无独立恢复流程与 UI | | `/compact` 对话压缩 | 🚫 | 暂无自动/手动压缩能力 | @@ -424,7 +424,7 @@ You can introduce the project like this: | Tool Execution Engine | ✅ | 已实现工具加载、执行、结果回填闭环 | | Output Styles | ✅ | 已实现输出样式加载系统 | | Session Persistence | ✅ | 已有会话保存/加载能力 | -| Context Engine | 🟡 | 已接入基础上下文构建链路,支持 workspace、git、`CLAUDE.md` prompt 注入 | +| Context Engine | 🟡 | 已接入基础上下文构建链路,支持 workspace、git、`CLAWCODEX.md` prompt 注入 | | Permission Engine | 🟡 | 已有框架,未完全集成到工具执行流程 | | Compaction Engine | 🚫 | 未形成对话压缩与 token 管理能力 | | Hook Runtime | 🚫 | 未接入设置驱动的 hook 执行机制 | @@ -511,7 +511,7 @@ You can introduce the project like this: 目标:补齐 Claude Code 的工程化能力。 - [ ] 工作区上下文构建完善 -- [x] git status / 文件树 / `CLAUDE.md` 注入基础版 +- [x] git status / 文件树 / `CLAWCODEX.md` 注入基础版 - [ ] README / 入口文件摘要注入 - [ ] memory 与历史上下文管理 - [ ] 权限系统完全集成 diff --git a/README.md b/README.md index f6f98d46e..8c1a35638 100644 --- a/README.md +++ b/README.md @@ -457,7 +457,7 @@ Use `clawcodex config` to check connection status and | Skill System | ✅ | SKILL.md-based slash-command skills with args + tool limits | | Cancellation / Abort | ✅ | ESC closes in-flight Bash, Grep/Glob, and streaming HTTP within ~50ms across every provider; subagents get isolated `AbortController`s; `Bash` `tool_result` distinguishes timeout from ESC-abort | | Image Handling | ✅ | TS-parity Read pipeline (magic-byte sniff, resize/compress to API limits); `@image.png` @-mentions inline as `ImageBlock`; pre-API base64 size validation in `BaseProvider._prepare_messages`; binary @-mentions (PDF/zip/docx/...) routed to a Read-tool hint instead of mojibake | -| Context Building | 🟡 | Workspace / git / `CLAUDE.md` injection; richer summaries and memory still evolving | +| Context Building | 🟡 | Workspace / git / `CLAWCODEX.md` injection; richer summaries and memory still evolving | | Permission System | 🟡 | Framework and checks; full integration still in progress | | MCP | 🟡 | MCP-oriented tools and wiring; full protocol/runtime polish ongoing | @@ -782,7 +782,7 @@ clawcodex/ │ ├── tool_system/ # Agent loop, tools, schemas │ ├── skills/ # SKILL.md loading and skill tool │ ├── services/ # MCP, compact, IDE bridge, tool execution, … -│ ├── context_system/ # Workspace / git / CLAUDE.md context +│ ├── context_system/ # Workspace / git / CLAWCODEX.md context │ ├── permissions/ # Permission modes and bash parsing │ ├── hooks/ # Hook types and execution helpers │ └── command_system/ # Slash commands and substitution diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bed324171..047b12ccf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -110,7 +110,7 @@ subsumed under the bash tool path. Three tiers, matching the book chapter: -1. **Project-level** — `CLAUDE.md` files in the repo (loaded by +1. **Project-level** — `CLAWCODEX.md` files in the repo (loaded by `src/memdir/memory_scan.py`). 2. **User-level** — `~/.clawcodex/projects//memory/MEMORY.md` (loaded by `src/memdir/paths.py`). diff --git a/docs/i18n/README_ZH.md b/docs/i18n/README_ZH.md index 9f8a7b58a..50947584b 100644 --- a/docs/i18n/README_ZH.md +++ b/docs/i18n/README_ZH.md @@ -326,7 +326,7 @@ clawcodex --allow-dangerously-skip-permissions # 允许之后通过 /per | Skill 系统 | ✅ | 基于 SKILL.md 的斜杠技能:参数与工具白名单 | | 取消 / 中止 | ✅ | ESC 可在约 50ms 内中止进行中的 Bash、Grep/Glob 以及所有 provider 的流式 HTTP;子 agent 拥有隔离的 `AbortController`;`Bash` 的 `tool_result` 区分超时与 ESC 中止 | | 图像处理 | ✅ | 与 TS 对齐的 Read 管线(魔数嗅探、按 API 限制缩放/压缩);`@image.png` @-提及内联为 `ImageBlock`;`BaseProvider._prepare_messages` 中调用 API 前的 base64 大小校验;二进制 @-提及(PDF/zip/docx/…)转为 Read 工具提示而非乱码 | -| 上下文构建 | 🟡 | workspace / git / `CLAUDE.md` 注入;更丰富的摘要与 memory 仍在演进 | +| 上下文构建 | 🟡 | workspace / git / `CLAWCODEX.md` 注入;更丰富的摘要与 memory 仍在演进 | | 权限系统 | 🟡 | 框架与检查逻辑已有;全面集成进行中 | | MCP | 🟡 | MCP 相关工具与接线已有;协议层/运行时仍在完善 | @@ -621,7 +621,7 @@ clawcodex/ │ ├── tool_system/ # Agent loop、工具与 schema │ ├── skills/ # SKILL.md 加载与 Skill 工具 │ ├── services/ # MCP、compact、IDE 桥、工具执行等 -│ ├── context_system/ # workspace / git / CLAUDE.md 上下文 +│ ├── context_system/ # workspace / git / CLAWCODEX.md 上下文 │ ├── permissions/ # 权限模式与 bash 解析 │ ├── hooks/ # Hook 类型与执行辅助 │ └── command_system/ # 斜杠命令与参数替换 diff --git a/src/agent/agent_definitions.py b/src/agent/agent_definitions.py index c34bdcdfc..c1be5543d 100644 --- a/src/agent/agent_definitions.py +++ b/src/agent/agent_definitions.py @@ -30,7 +30,7 @@ class AgentDefinition: background: bool = False color: str | None = None memory: str | None = None - omit_claude_md: bool = False + omit_clawcodex_md: bool = False disallowed_tools: list[str] | None = None hooks: dict[str, Any] | None = None skills: list[str] | None = None @@ -157,7 +157,7 @@ def _explore_system_prompt(**_kwargs: Any) -> str: disallowed_tools=["Agent", "ExitPlanMode", "Edit", "Write", "NotebookEdit"], source="built-in", base_dir="built-in", - omit_claude_md=True, + omit_clawcodex_md=True, # ch08 round-4 (critic M1) — Explore is the fast/cheap read-only agent; # TS exploreAgent.ts:77 runs it on Haiku. get_agent_model resolves this # against the session provider and inherits on providers that don't @@ -228,7 +228,7 @@ def _plan_system_prompt(**_kwargs: Any) -> str: source="built-in", base_dir="built-in", model="inherit", - omit_claude_md=True, + omit_clawcodex_md=True, get_system_prompt=_plan_system_prompt, ) diff --git a/src/agent/parse_agent_markdown.py b/src/agent/parse_agent_markdown.py index 1a9320a30..16a90f02c 100644 --- a/src/agent/parse_agent_markdown.py +++ b/src/agent/parse_agent_markdown.py @@ -16,8 +16,8 @@ background → background color → color memory → memory - omit-claude-md → omit_claude_md - omitClaudeMd → omit_claude_md (camelCase alias) + omit-clawcodex-md → omit_clawcodex_md + omitClawcodexMd → omit_clawcodex_md (camelCase alias) hooks → hooks skills → skills isolation → isolation @@ -182,7 +182,9 @@ def parse_agent_from_markdown( background = _parse_bool(_first(frontmatter, "background")) color = _parse_color(_first(frontmatter, "color")) memory = _parse_memory(_first(frontmatter, "memory"), file_path=file_path) - omit_claude_md = _parse_bool(_first(frontmatter, "omit-claude-md", "omitClaudeMd")) + omit_clawcodex_md = _parse_bool( + _first(frontmatter, "omit-clawcodex-md", "omitClawcodexMd") + ) hooks = parse_hooks(_first(frontmatter, "hooks"), owner_name=f"agent {agent_type}") skills = parse_string_list(_first(frontmatter, "skills")) isolation = _parse_isolation( @@ -214,7 +216,7 @@ def _get_system_prompt(**_kwargs: Any) -> str: background=background, color=color, memory=memory, - omit_claude_md=omit_claude_md, + omit_clawcodex_md=omit_clawcodex_md, disallowed_tools=disallowed_tools, hooks=hooks, skills=skills or None, diff --git a/src/bootstrap/state.py b/src/bootstrap/state.py index 874975d8a..2445cc617 100644 --- a/src/bootstrap/state.py +++ b/src/bootstrap/state.py @@ -203,10 +203,10 @@ class _BootstrapState: invoked_skills: dict[str, InvokedSkillInfo] = field(default_factory=dict) # --- Cache optimization (TS: lines 122-123, 202-205, 207, 256) --------- - cached_claude_md_content: str | None = None + cached_clawcodex_md_content: str | None = None system_prompt_section_cache: dict[str, str | None] = field(default_factory=dict) pending_post_compaction: bool = False - additional_directories_for_claude_md: list[str] = field(default_factory=list) + additional_directories_for_clawcodex_md: list[str] = field(default_factory=list) # --- Model (TS: lines 68-70) ------------------------------------------- main_loop_model_override: str | None = None @@ -772,12 +772,12 @@ def set_cost_state_for_restore( # =========================================================================== -def get_cached_claude_md_content() -> str | None: - return _STATE.cached_claude_md_content +def get_cached_clawcodex_md_content() -> str | None: + return _STATE.cached_clawcodex_md_content -def set_cached_claude_md_content(content: str | None) -> None: - _STATE.cached_claude_md_content = content +def set_cached_clawcodex_md_content(content: str | None) -> None: + _STATE.cached_clawcodex_md_content = content def get_system_prompt_section_cache() -> dict[str, str | None]: @@ -806,12 +806,12 @@ def consume_post_compaction() -> bool: return was -def get_additional_directories_for_claude_md() -> list[str]: - return _STATE.additional_directories_for_claude_md +def get_additional_directories_for_clawcodex_md() -> list[str]: + return _STATE.additional_directories_for_clawcodex_md -def set_additional_directories_for_claude_md(directories: list[str]) -> None: - _STATE.additional_directories_for_claude_md = list(directories) +def set_additional_directories_for_clawcodex_md(directories: list[str]) -> None: + _STATE.additional_directories_for_clawcodex_md = list(directories) # =========================================================================== @@ -1266,15 +1266,15 @@ def reset_state_for_tests() -> None: "is_repl_bridge_active", "get_repl_bridge_handle", # Cache optimization - "get_cached_claude_md_content", - "set_cached_claude_md_content", + "get_cached_clawcodex_md_content", + "set_cached_clawcodex_md_content", "get_system_prompt_section_cache", "set_system_prompt_section_cache_entry", "clear_system_prompt_section_state", "mark_post_compaction", "consume_post_compaction", - "get_additional_directories_for_claude_md", - "set_additional_directories_for_claude_md", + "get_additional_directories_for_clawcodex_md", + "set_additional_directories_for_clawcodex_md", # Model "get_main_loop_model_override", "set_main_loop_model_override", diff --git a/src/command_system/builtins.py b/src/command_system/builtins.py index 001c0ccb6..6aa39972e 100644 --- a/src/command_system/builtins.py +++ b/src/command_system/builtins.py @@ -48,20 +48,20 @@ # Official Claude Code /init prompts (Simplified) -NEW_INIT_PROMPT = """Set up a CLAUDE.md file for this repo. CLAUDE.md is loaded into every Claude Code session, so it must be concise — only include what Claude would get wrong without it. +NEW_INIT_PROMPT = """Set up a CLAWCODEX.md file for this repo. CLAWCODEX.md is loaded into every clawcodex session, so it must be concise — only include what the agent would get wrong without it. ## Step 1: Ask what to set up Use AskUserQuestion to ask the user: -- "Which CLAUDE.md files should /init set up?" with options: "Project CLAUDE.md" | "Personal CLAUDE.local.md" | "Both project + personal" +- "Which CLAWCODEX.md files should /init set up?" with options: "Project CLAWCODEX.md" | "Personal CLAWCODEX.local.md" | "Both project + personal" Use AskUserQuestion to ask: -- "Also set up skills and hooks?" with options: "Skills + hooks" | "Skills only" | "Hooks only" | "Neither, just CLAUDE.md" +- "Also set up skills and hooks?" with options: "Skills + hooks" | "Skills only" | "Hooks only" | "Neither, just CLAWCODEX.md" ## Step 2: Explore the codebase Use tools to understand the project: -- Read key files: README, package.json, pyproject.toml, Cargo.toml, Makefile, existing CLAUDE.md +- Read key files: README, package.json, pyproject.toml, Cargo.toml, Makefile, existing CLAWCODEX.md - Detect: build/test/lint commands, languages, frameworks, project structure - Detect: code style rules, required env vars, gotchas - Check for formatter config (ruff, black, prettier, etc.) @@ -73,9 +73,9 @@ - Non-obvious workflows or commands - Communication preferences (terse vs detailed) -## Step 4: Write CLAUDE.md +## Step 4: Write CLAWCODEX.md -Write a minimal CLAUDE.md at the project root. +Write a minimal CLAWCODEX.md at the project root. Include: - Build/test/lint commands that aren't standard (e.g., "uv run pytest" not just "pytest") @@ -84,22 +84,22 @@ - Non-obvious gotchas Exclude: -- File structure (Claude can discover this) -- Standard conventions Claude already knows +- File structure (the agent can discover this) +- Standard conventions the agent already knows - Generic advice Prefix with: ``` -# CLAUDE.md +# CLAWCODEX.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to clawcodex when working with code in this repository. ``` -If CLAUDE.md exists: read it, propose specific improvements. +If CLAWCODEX.md exists: read it, propose specific improvements. -## Step 5: Write CLAUDE.local.md (if user chose personal or both) +## Step 5: Write CLAWCODEX.local.md (if user chose personal or both) -Write CLAUDE.local.md at project root. Add it to .gitignore. +Write CLAWCODEX.local.md at project root. Add it to .gitignore. Include: - User's role and familiarity with codebase @@ -123,15 +123,15 @@ Tell the user what was set up and suggest any additional optimizations.""" # Fallback prompt for simpler initialization -OLD_INIT_PROMPT = """Please analyze this codebase and create a CLAUDE.md file, which will be given to future instances of Claude Code to operate in this repository. +OLD_INIT_PROMPT = """Please analyze this codebase and create a CLAWCODEX.md file, which will be given to future clawcodex sessions operating in this repository. What to add: 1. Commands that will be commonly used, such as how to build, lint, and run tests. Include the necessary commands to develop in this codebase, such as how to run a single test. 2. High-level code architecture and structure so that future instances can be productive more quickly. Focus on the "big picture" architecture that requires reading multiple files to understand. Usage notes: -- If there's already a CLAUDE.md, suggest improvements to it. -- When you make the initial CLAUDE.md, do not repeat yourself and do not include obvious instructions like "Provide helpful error messages to users", "Write unit tests for all new utilities", "Never include sensitive information (API keys, tokens) in code or commits". +- If there's already a CLAWCODEX.md, suggest improvements to it. +- When you make the initial CLAWCODEX.md, do not repeat yourself and do not include obvious instructions like "Provide helpful error messages to users", "Write unit tests for all new utilities", "Never include sensitive information (API keys, tokens) in code or commits". - Avoid listing every component or file structure that can be easily discovered. - Don't include generic development practices. - If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include the important parts. @@ -140,9 +140,9 @@ - Be sure to prefix the file with the following text: ``` -# CLAUDE.md +# CLAWCODEX.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to clawcodex when working with code in this repository. ```""" @@ -363,15 +363,15 @@ def context_command_call(args: str, context: CommandContext) -> LocalCommandResu # Get custom agents info from config custom_agents = context.config.get("custom_agents", []) - # Get CLAUDE.md content - claude_md_content = "" + # Get CLAWCODEX.md content + clawcodex_md_content = "" try: import asyncio - from ..context_system.claude_md import get_claude_mds, get_memory_files + from ..context_system.clawcodex_md import get_clawcodex_mds, get_memory_files async def _load(): files = await get_memory_files(cwd=str(context.cwd or context.workspace_root)) - return get_claude_mds(files) + return get_clawcodex_mds(files) try: loop = asyncio.get_running_loop() @@ -380,9 +380,9 @@ async def _load(): if loop and loop.is_running(): import concurrent.futures with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: - claude_md_content = pool.submit(asyncio.run, _load()).result(timeout=10) + clawcodex_md_content = pool.submit(asyncio.run, _load()).result(timeout=10) else: - claude_md_content = asyncio.run(_load()) + clawcodex_md_content = asyncio.run(_load()) except Exception: pass @@ -407,7 +407,7 @@ async def _load(): model=model, system_prompt=system_prompt, tool_schemas=tool_schemas, - claude_md_content=claude_md_content, + clawcodex_md_content=clawcodex_md_content, skills_frontmatter_tokens=skills_frontmatter_tokens, skills_count=skills_count, api_usage=api_usage, @@ -1188,7 +1188,7 @@ def _sync_compact_fallback(context: CommandContext) -> LocalCommandResult: INIT_COMMAND = PromptCommand( name="init", - description="Initialize new CLAUDE.md file(s) and optional skills/hooks with codebase documentation", + description="Initialize new CLAWCODEX.md file(s) and optional skills/hooks with codebase documentation", markdown_content=NEW_INIT_PROMPT, progress_message="analyzing your codebase", content_length=0, diff --git a/src/command_system/memory_command.py b/src/command_system/memory_command.py index e2ca36168..1ca06832d 100644 --- a/src/command_system/memory_command.py +++ b/src/command_system/memory_command.py @@ -1,11 +1,11 @@ """memory — ``/memory`` memory-file picker (port of TS local-jsx). Port of ``typescript/src/commands/memory/`` + the core of ``MemoryFileSelector``. -Presents the CLAUDE.md memory hierarchy — the synthetic **User memory** -(``~/.clawcodex/CLAUDE.md``) and **Project memory** (nearest loaded ancestor -``CLAUDE.md``, falling back to ``{cwd}/CLAUDE.md``) candidates, differentiated via +Presents the CLAWCODEX.md memory hierarchy — the synthetic **User memory** +(``~/.clawcodex/CLAWCODEX.md``) and **Project memory** (nearest loaded ancestor +``CLAWCODEX.md``, falling back to ``{cwd}/CLAWCODEX.md``) candidates, differentiated via option *descriptions* (the TS selector's real channel), plus the existing files -enumerated by the ``claude_md`` port — ensure-creates the selected file +enumerated by the ``clawcodex_md`` port — ensure-creates the selected file (exclusive-create; existing content preserved), and reports its path with an editor hint. @@ -59,24 +59,26 @@ def _display_path(path: str, cwd: str) -> str: def _resolve_project_memory_path(files: list, cwd: str) -> str: """TS ``getProjectMemoryPathForSelector``: prefer the **nearest** already-loaded - root-level Project CLAUDE.md (TS walks cwd upward; ``get_memory_files`` enumerates - root→cwd, so iterate reversed — last match = nearest); fall back to - ``{cwd}/CLAUDE.md`` only when none exists — so a repo subdirectory still points at - the real project memory.""" + root-level Project CLAWCODEX.md (TS walks cwd upward; ``get_memory_files`` + enumerates root→cwd, so iterate reversed — last match = nearest); fall back to + ``{cwd}/CLAWCODEX.md`` only when none exists — so a repo subdirectory still + points at the real project memory.""" + from src.context_system.clawcodex_md import CONTEXT_MD + for info in reversed(files): if ( getattr(info, "parent", None) is None and getattr(info, "type", None) == "Project" - and os.path.basename(info.path) == "CLAUDE.md" + and os.path.basename(info.path) == CONTEXT_MD ): return info.path - return str(Path(cwd) / "CLAUDE.md") + return str(Path(cwd) / CONTEXT_MD) async def build_memory_options(cwd: str) -> list[UIOption]: """Public: the memory-target hierarchy, shared by ``/memory`` and the C9 ``#`` shortcut so the two pickers can never drift.""" - from src.context_system.claude_md import ( + from src.context_system.clawcodex_md import ( clear_memory_file_caches, get_memory_files, ) @@ -88,7 +90,7 @@ async def build_memory_options(cwd: str) -> list[UIOption]: files = [] home = Path.home() - user_path = str(home / ".clawcodex" / "CLAUDE.md") + user_path = str(home / ".clawcodex" / "CLAWCODEX.md") project_path = _resolve_project_memory_path(files, cwd) # Git-aware project description (TS: `${isGit ? 'Checked in at' : 'Saved in'} ./…`). @@ -109,7 +111,7 @@ async def build_memory_options(cwd: str) -> list[UIOption]: UIOption( value=user_path, label="User memory", - description="Saved in ~/.clawcodex/CLAUDE.md", # hardcoded tilde (TS shape) + description="Saved in ~/.clawcodex/CLAWCODEX.md", # hardcoded tilde (TS shape) ), UIOption(value=project_path, label="Project memory", description=project_desc), ] diff --git a/src/compact_service/service.py b/src/compact_service/service.py index 676d028f3..173c9d67d 100644 --- a/src/compact_service/service.py +++ b/src/compact_service/service.py @@ -63,7 +63,7 @@ async def compact_conversation( re-inject recently accessed files after compaction. plan_file_path: Path to the plan file to restore (and exclude from file restoration). - memory_paths: Set of memory/CLAUDE.md paths to exclude from file + memory_paths: Set of memory/CLAWCODEX.md paths to exclude from file restoration (they're loaded separately). Returns: diff --git a/src/context_system/__init__.py b/src/context_system/__init__.py index 712d453f4..d19215fcb 100644 --- a/src/context_system/__init__.py +++ b/src/context_system/__init__.py @@ -9,9 +9,9 @@ get_user_context, prepend_user_context, ) -from .claude_md import ( +from .clawcodex_md import ( clear_memory_file_caches, - get_claude_mds, + get_clawcodex_mds, get_memory_files, reset_get_memory_files_cache, ) @@ -38,9 +38,9 @@ "get_system_context", "get_user_context", "prepend_user_context", - # CLAUDE.md + # CLAWCODEX.md "clear_memory_file_caches", - "get_claude_mds", + "get_clawcodex_mds", "get_memory_files", "reset_get_memory_files_cache", # Git context diff --git a/src/context_system/builder.py b/src/context_system/builder.py index d6270bedd..9e2661718 100644 --- a/src/context_system/builder.py +++ b/src/context_system/builder.py @@ -37,8 +37,8 @@ def build_context_prompt( if git_section: sections.append(git_section) - # CLAUDE.md context (sync wrapper around async get_memory_files) - claude_section = _build_claude_md_section(str(current), root) + # CLAWCODEX.md context (sync wrapper around async get_memory_files) + claude_section = _build_clawcodex_md_section(str(current), root) if claude_section: sections.append(claude_section) @@ -98,11 +98,11 @@ def _build_git_section(cwd: str) -> str: return "" -def _build_claude_md_section(cwd: str, root: Path) -> str: - from .claude_md import get_claude_mds, get_memory_files +def _build_clawcodex_md_section(cwd: str, root: Path) -> str: + from .clawcodex_md import get_clawcodex_mds, get_memory_files try: memory_files = _run_async(get_memory_files(cwd=cwd)) - content = get_claude_mds(memory_files) + content = get_clawcodex_mds(memory_files) if content: return f"## Project Instructions\n{content}" except Exception: diff --git a/src/context_system/claude_md.py b/src/context_system/clawcodex_md.py similarity index 89% rename from src/context_system/claude_md.py rename to src/context_system/clawcodex_md.py index fcea8859b..2a1c1d80c 100644 --- a/src/context_system/claude_md.py +++ b/src/context_system/clawcodex_md.py @@ -1,14 +1,21 @@ """ -Multi-level CLAUDE.md loading — aligned with typescript/src/utils/claudemd.ts. +Multi-level CLAWCODEX.md loading — engineering aligned with +typescript/src/utils/claudemd.ts, file naming clawcodex-branded. Loading order (reverse priority — later entries take precedence): - 1. Managed memory (/etc/clawcodex/CLAUDE.md by default) - 2. User memory (~/.clawcodex/CLAUDE.md) - 3. Project memory (CLAUDE.md, .clawcodex/CLAUDE.md, .clawcodex/rules/*.md) - 4. Local memory (CLAUDE.local.md) + 1. Managed memory (/etc/clawcodex/CLAWCODEX.md by default) + 2. User memory (~/.clawcodex/CLAWCODEX.md) + 3. Project memory (CLAWCODEX.md, .clawcodex/CLAWCODEX.md, + .clawcodex/rules/*.md) + 4. Local memory (CLAWCODEX.local.md) Files closer to CWD have higher priority (loaded later in the list). +``CLAWCODEX.md`` / ``CLAWCODEX.local.md`` are the only names read — the +pre-rebrand ``CLAUDE.md`` naming is not consulted (the one-time +``~/.claude`` import migration copies that harness's file in under the +canonical name; see ``src/utils/legacy_migration.py``). + The @include directive allows memory files to reference other files: @path, @./relative, @~/home, @/absolute Included files are added after the including file. Circular references @@ -35,6 +42,11 @@ logger = logging.getLogger(__name__) +# The app's context-file names. +CONTEXT_MD = "CLAWCODEX.md" +CONTEXT_LOCAL_MD = "CLAWCODEX.local.md" + + # --------------------------------------------------------------------------- # Module-level memoize cache for get_memory_files # --------------------------------------------------------------------------- @@ -79,16 +91,16 @@ def _is_bare_mode() -> bool: def _get_additional_directories() -> list[str]: - """Get additional directories for CLAUDE.md discovery (--add-dir).""" + """Get additional directories for CLAWCODEX.md discovery (--add-dir).""" val = os.environ.get("CLAUDE_CODE_ADDITIONAL_DIRECTORIES", "") if not val: return [] return [d.strip() for d in val.split(os.pathsep) if d.strip()] -def _should_disable_claude_md() -> bool: - """Mirrors TS shouldDisableClaudeMd logic.""" - if os.environ.get("CLAUDE_CODE_DISABLE_CLAUDE_MDS", "").lower() in ("1", "true", "yes"): +def _should_disable_context_mds() -> bool: + """Mirrors TS shouldDisableClaudeMd logic (clawcodex-branded switch).""" + if os.environ.get("CLAWCODEX_DISABLE_CLAWCODEX_MDS", "").lower() in ("1", "true", "yes"): return True if _is_bare_mode() and len(_get_additional_directories()) == 0: return True @@ -371,7 +383,7 @@ def is_external_memory_file( """TS getExternalClaudeMdIncludes predicate (claudemd.ts:1427-1437): a non-User-tier INCLUDED file (has a parent) living outside the original cwd. The User tier is excluded — the user's own - ~/CLAUDE.md may include anything without triggering the project + ~/CLAWCODEX.md may include anything without triggering the project gate.""" if info.type == "User" or not info.parent: @@ -418,13 +430,13 @@ async def get_memory_files( home = str(Path.home()) - # 1. Managed memory (/CLAUDE.md — /etc/clawcodex by + # 1. Managed memory (/CLAWCODEX.md — /etc/clawcodex by # default, CLAWCODEX_MANAGED_CONFIG_DIR override like the managed # skills/MCP tiers) from src.utils.clawcodex_dirs import get_managed_config_dir managed_base = str(get_managed_config_dir()) - managed_path = os.path.join(managed_base, "CLAUDE.md") + managed_path = os.path.join(managed_base, CONTEXT_MD) result.extend(await process_memory_file( managed_path, "Managed", processed_paths, include_external, )) @@ -435,10 +447,10 @@ async def get_memory_files( conditional_rule=False, )) - # 2. User memory (~/.clawcodex/CLAUDE.md) - user_claude_md = os.path.join(home, ".clawcodex", "CLAUDE.md") + # 2. User memory (~/.clawcodex/CLAWCODEX.md) + user_context_md = os.path.join(home, ".clawcodex", CONTEXT_MD) result.extend(await process_memory_file( - user_claude_md, "User", processed_paths, True, # User can always include external + user_context_md, "User", processed_paths, True, # User can always include external )) # User rules (~/.clawcodex/rules/*.md) user_rules_dir = os.path.join(home, ".clawcodex", "rules") @@ -461,15 +473,15 @@ async def get_memory_files( # Process from root downward to CWD (reverse so CWD is last = highest priority) for d in reversed(dirs): - # Project: CLAUDE.md - project_path = os.path.join(d, "CLAUDE.md") + # Project: CLAWCODEX.md + project_path = os.path.join(d, CONTEXT_MD) result.extend(await process_memory_file( project_path, "Project", processed_paths, include_external, )) - # Project: .clawcodex/CLAUDE.md - dot_claude_path = os.path.join(d, ".clawcodex", "CLAUDE.md") + # Project: .clawcodex/CLAWCODEX.md + dot_dir_path = os.path.join(d, ".clawcodex", CONTEXT_MD) result.extend(await process_memory_file( - dot_claude_path, "Project", processed_paths, include_external, + dot_dir_path, "Project", processed_paths, include_external, )) # Project: .clawcodex/rules/*.md rules_dir = os.path.join(d, ".clawcodex", "rules") @@ -477,21 +489,21 @@ async def get_memory_files( rules_dir, "Project", processed_paths, include_external, conditional_rule=False, )) - # Local: CLAUDE.local.md - local_path = os.path.join(d, "CLAUDE.local.md") + # Local: CLAWCODEX.local.md + local_path = os.path.join(d, CONTEXT_LOCAL_MD) result.extend(await process_memory_file( local_path, "Local", processed_paths, include_external, )) # 4. Additional directories (--add-dir) for add_dir in _get_additional_directories(): - project_path = os.path.join(add_dir, "CLAUDE.md") + project_path = os.path.join(add_dir, CONTEXT_MD) result.extend(await process_memory_file( project_path, "Project", processed_paths, include_external, )) - dot_claude_path = os.path.join(add_dir, ".clawcodex", "CLAUDE.md") + dot_dir_path = os.path.join(add_dir, ".clawcodex", CONTEXT_MD) result.extend(await process_memory_file( - dot_claude_path, "Project", processed_paths, include_external, + dot_dir_path, "Project", processed_paths, include_external, )) rules_dir = os.path.join(add_dir, ".clawcodex", "rules") result.extend(await process_md_rules( @@ -505,10 +517,10 @@ async def get_memory_files( # --------------------------------------------------------------------------- -# get_claude_mds — format memory files for injection (mirrors TS getClaudeMds) +# get_clawcodex_mds — format memory files for injection (TS getClaudeMds) # --------------------------------------------------------------------------- -def get_claude_mds(memory_files: list[MemoryFileInfo]) -> str: +def get_clawcodex_mds(memory_files: list[MemoryFileInfo]) -> str: """ Format memory files into the prompt string. @@ -553,7 +565,7 @@ def get_large_memory_files(files: list[MemoryFileInfo]) -> list[MemoryFileInfo]: def is_memory_file_path(file_path: str) -> bool: """Check if a path looks like a memory file.""" name = os.path.basename(file_path) - if name in ("CLAUDE.md", "CLAUDE.local.md"): + if name in (CONTEXT_MD, CONTEXT_LOCAL_MD): return True if name.endswith(".md") and (os.sep + ".clawcodex" + os.sep + "rules" + os.sep) in file_path: return True diff --git a/src/context_system/context_analyzer.py b/src/context_system/context_analyzer.py index 82a9e0d9f..0698d5933 100644 --- a/src/context_system/context_analyzer.py +++ b/src/context_system/context_analyzer.py @@ -124,9 +124,9 @@ def count_system_prompt_tokens(system_prompt: str) -> int: return count_tokens(system_prompt) -def count_claude_md_tokens(claude_md_content: str) -> int: - """Count tokens in CLAUDE.md content.""" - return count_tokens(claude_md_content) +def count_clawcodex_md_tokens(clawcodex_md_content: str) -> int: + """Count tokens in CLAWCODEX.md content.""" + return count_tokens(clawcodex_md_content) def count_message_breakdown_tokens( @@ -187,7 +187,7 @@ def analyze_context( model: str, system_prompt: str, tool_schemas: list[dict[str, Any]], - claude_md_content: str, + clawcodex_md_content: str, skills_frontmatter_tokens: int = 0, skills_count: int = 0, api_usage: Optional[dict[str, int]] = None, @@ -204,7 +204,7 @@ def analyze_context( model: Model name (e.g., "claude-sonnet-4-6") system_prompt: The active system prompt string tool_schemas: List of tool schema dicts (from tool registry) - claude_md_content: Concatenated CLAUDE.md content + clawcodex_md_content: Concatenated CLAWCODEX.md content skills_frontmatter_tokens: Tokens from skills frontmatter skills_count: Number of skills loaded api_usage: Actual usage from last API response (optional) @@ -240,10 +240,10 @@ def analyze_context( if agent_total > 0: categories.append(ContextCategory(name="Custom agents", tokens=agent_total)) - # Memory files (CLAUDE.md) - claude_md_tokens = count_claude_md_tokens(claude_md_content) - if claude_md_tokens > 0: - categories.append(ContextCategory(name="Memory files", tokens=claude_md_tokens)) + # Memory files (CLAWCODEX.md) + clawcodex_md_tokens = count_clawcodex_md_tokens(clawcodex_md_content) + if clawcodex_md_tokens > 0: + categories.append(ContextCategory(name="Memory files", tokens=clawcodex_md_tokens)) # Skills if skills_frontmatter_tokens > 0: @@ -274,8 +274,8 @@ def analyze_context( # Build memory files list memory_files: list[dict[str, Any]] = [] - if claude_md_tokens > 0: - memory_files.append({"path": "CLAUDE.md", "tokens": claude_md_tokens}) + if clawcodex_md_tokens > 0: + memory_files.append({"path": "CLAWCODEX.md", "tokens": clawcodex_md_tokens}) return ContextData( categories=categories, diff --git a/src/context_system/models.py b/src/context_system/models.py index 2b6667d97..27b70f33a 100644 --- a/src/context_system/models.py +++ b/src/context_system/models.py @@ -26,7 +26,7 @@ @dataclass class MemoryFileInfo: - """A loaded memory/instruction file (CLAUDE.md, rules, etc.).""" + """A loaded memory/instruction file (CLAWCODEX.md, rules, etc.).""" path: str type: MemoryType @@ -47,7 +47,7 @@ class SystemPromptParts: """Assembled pieces for the API cache-key prefix.""" default_system_prompt: list[str] - user_context: dict[str, str] # e.g. {"claudeMd": "...", "currentDate": "..."} + user_context: dict[str, str] # e.g. {"clawcodexMd": "...", "currentDate": "..."} system_context: dict[str, str] # e.g. {"gitStatus": "..."} diff --git a/src/context_system/prompt_assembly.py b/src/context_system/prompt_assembly.py index f96154f71..5c5420b09 100644 --- a/src/context_system/prompt_assembly.py +++ b/src/context_system/prompt_assembly.py @@ -3,7 +3,7 @@ Provides fetch_system_prompt_parts() which concurrently fetches: - default system prompt sections - - user context (CLAUDE.md + date) + - user context (CLAWCODEX.md + date) - system context (git status) Also provides append_system_context() and prepend_user_context() matching @@ -19,10 +19,10 @@ from ..types.messages import Message, UserMessage from .cache_boundary import SYSTEM_PROMPT_DYNAMIC_BOUNDARY -from .claude_md import ( - _should_disable_claude_md, +from .clawcodex_md import ( + _should_disable_context_mds, clear_memory_file_caches, - get_claude_mds, + get_clawcodex_mds, get_memory_files, ) from .git_context import ( @@ -64,10 +64,10 @@ async def get_user_context( cwd: str | None = None, ) -> dict[str, str]: """ - Get memoized user context: CLAUDE.md content + current date. + Get memoized user context: CLAWCODEX.md content + current date. Mirrors TS getUserContext from context.ts. - Returns dict with keys: claudeMd, currentDate. + Returns dict with keys: clawcodexMd, currentDate. """ global _user_context_cache if _user_context_cache is not None: @@ -83,26 +83,26 @@ async def get_user_context( # would bust the cache on every turn. context["currentDate"] = _get_session_start_date_iso() - # CLAUDE.md content (skip in --bare mode unless --add-dir used) - claude_md_content = "" - if not _should_disable_claude_md(): + # CLAWCODEX.md content (skip in --bare mode unless --add-dir used) + clawcodex_md_content = "" + if not _should_disable_context_mds(): try: memory_files = await get_memory_files(cwd=cwd) - claude_md_content = get_claude_mds(memory_files) - if claude_md_content: - context["claudeMd"] = claude_md_content + clawcodex_md_content = get_clawcodex_mds(memory_files) + if clawcodex_md_content: + context["clawcodexMd"] = clawcodex_md_content except Exception: pass - # Cache CLAUDE.md into the bootstrap singleton (TS context.ts:173-176): + # Cache CLAWCODEX.md into the bootstrap singleton (TS context.ts:173-176): # the DAG-leaf cache exists to break the classifier→filesystem→ # permissions→classifier import cycle. The TS consumer (yoloClassifier, # the auto-mode transcript classifier) is unported — this is forward # provisioning so the cache is real when ch06/ch12 land the consumer. try: - from ..bootstrap.state import set_cached_claude_md_content + from ..bootstrap.state import set_cached_clawcodex_md_content - set_cached_claude_md_content(claude_md_content or None) + set_cached_clawcodex_md_content(clawcodex_md_content or None) except Exception: pass @@ -245,7 +245,7 @@ def prepend_user_context( context: dict[str, str], ) -> list[Message]: """ - Prepend a user message with CLAUDE.md + date. + Prepend a user message with CLAWCODEX.md + date. Mirrors TS prependUserContext from api.ts. """ diff --git a/src/context_system/workspace_snapshot.py b/src/context_system/workspace_snapshot.py index d4e6dd8a0..e3ff6caf7 100644 --- a/src/context_system/workspace_snapshot.py +++ b/src/context_system/workspace_snapshot.py @@ -15,7 +15,7 @@ } _KEY_FILE_CANDIDATES = ( "README.md", - "CLAUDE.md", + "CLAWCODEX.md", "pyproject.toml", "requirements.txt", "uv.lock", diff --git a/src/deferred_init.py b/src/deferred_init.py index e1376e517..5dd46ab49 100644 --- a/src/deferred_init.py +++ b/src/deferred_init.py @@ -3,7 +3,7 @@ Mirrors the minimal core of TS ``startDeferredPrefetches`` (main.tsx:392-439) and the non-interactive early kicks -(main.tsx:1973-1990): warm the memoized user context (CLAUDE.md walk) +(main.tsx:1973-1990): warm the memoized user context (CLAWCODEX.md walk) and — when the session is trusted — the system context (git status probes) so the first turn's ``fetch_system_prompt_parts`` hits warm caches instead of paying the cold filesystem/subprocess cost inside the diff --git a/src/entrypoints/headless.py b/src/entrypoints/headless.py index 619c6f507..44eaf33fe 100644 --- a/src/entrypoints/headless.py +++ b/src/entrypoints/headless.py @@ -136,7 +136,7 @@ def run_headless(options: HeadlessOptions) -> int: stdin = options.stdin or sys.stdin # ch02 round-3 GAP B: warm the user/system context memos now so the - # CLAUDE.md walk and git probes overlap with provider + registry + # CLAWCODEX.md walk and git probes overlap with provider + registry # construction below instead of running inside the first turn. # Mirrors TS main.tsx:1973-1990 (non-interactive early kicks; trust # is implicit in -p mode and was granted by run_pre_action). @@ -464,7 +464,7 @@ def _emit_partial(chunk: str) -> None: # Headless is single-shot per prompt and starts # its own event loop, so ``asyncio.run`` is the # right pattern. Pre-build the effective system - # prompt (CLAUDE.md + git status + style) so the + # prompt (CLAWCODEX.md + git status + style) so the # cold-start context reaches query() unchanged # — the legacy run_agent_loop did this inside # the loop; the adapter doesn't. diff --git a/src/memdir/memory_types.py b/src/memdir/memory_types.py index 01d1bcd0e..68729b18f 100644 --- a/src/memdir/memory_types.py +++ b/src/memdir/memory_types.py @@ -117,7 +117,7 @@ def parse_memory_type(raw: object) -> MemoryType | None: "- Code patterns, conventions, architecture, file paths, or project structure — these can be derived by reading the current project state.", "- Git history, recent changes, or who-changed-what — `git log` / `git blame` are authoritative.", "- Debugging solutions or fix recipes — the fix is in the code; the commit message has the context.", - "- Anything already documented in CLAUDE.md files.", + "- Anything already documented in CLAWCODEX.md files.", "- Ephemeral task details: in-progress work, temporary state, current conversation context.", "", "These exclusions apply even when the user explicitly asks you to save. If they ask you to save a PR list or activity summary, ask what was *surprising* or *non-obvious* about it — that is the part worth keeping.", diff --git a/src/memory/manage.py b/src/memory/manage.py index c2c5e7ea3..d5731588d 100644 --- a/src/memory/manage.py +++ b/src/memory/manage.py @@ -7,7 +7,7 @@ (``hermes_cli`` / ``gateway/slash_commands.py``; doc 07 §1). The no-argument ``/memory`` stays the existing file picker (memdir / -CLAUDE.md editing); these subcommands only run when arguments are given. +CLAWCODEX.md editing); these subcommands only run when arguments are given. """ from __future__ import annotations diff --git a/src/memory/store.py b/src/memory/store.py index 2940a76f8..bfd38e953 100644 --- a/src/memory/store.py +++ b/src/memory/store.py @@ -99,7 +99,7 @@ def _scan_memory_content(content: str) -> str | None: Known tradeoff (design-accepted): strict patterns can false-positive on legitimate config-workflow facts (e.g. anything phrased "update … - CLAUDE.md"). A foreground write surfaces the error so the model can + CLAWCODEX.md"). A foreground write surfaces the error so the model can reword; a background-review write is silently dropped (the fork swallows rejections) — aggressive scanning wins because this content persists into every future system prompt. diff --git a/src/memory/threat_patterns.py b/src/memory/threat_patterns.py index b7ebc95ce..8c7c89f66 100644 --- a/src/memory/threat_patterns.py +++ b/src/memory/threat_patterns.py @@ -35,7 +35,8 @@ hermes-specific persistence targets (``~/.hermes/.env``, ``.hermes/config.yaml``, ``SOUL.md``) are replaced with the clawcodex equivalents (``~/.clawcodex/config.json``, ``.clawcodex/`` settings, -``CLAUDE.md`` kept — it is this product's context file too); the +``CLAWCODEX.md`` — the product's canonical context file — plus the +legacy ``CLAUDE.md`` name the read fallback still honors); the agent-env unset pattern gains the ``CLAWCODEX`` token. """ @@ -100,7 +101,7 @@ (r'authorized_keys', "ssh_backdoor", "strict"), (r'\$HOME/\.ssh|\~/\.ssh', "ssh_access", "strict"), (r'\$HOME/\.clawcodex/config\.json|\~/\.clawcodex/config\.json', "clawcodex_config", "strict"), - (r'(update|modify|edit|write|change|append|add\s+to)\s+.*(?:AGENTS\.md|CLAUDE\.md|\.cursorrules|\.clinerules)', "agent_config_mod", "strict"), + (r'(update|modify|edit|write|change|append|add\s+to)\s+.*(?:AGENTS\.md|CLAWCODEX\.md|CLAUDE\.md|\.cursorrules|\.clinerules)', "agent_config_mod", "strict"), (r'(update|modify|edit|write|change|append|add\s+to)\s+.*\.clawcodex/(config\.json|settings\.json)', "clawcodex_config_mod", "strict"), # ── Hardcoded secrets ──────────────────────────────────────────── diff --git a/src/permissions/yolo_classifier.py b/src/permissions/yolo_classifier.py index 9b8adf310..08dd0cad9 100644 --- a/src/permissions/yolo_classifier.py +++ b/src/permissions/yolo_classifier.py @@ -288,17 +288,17 @@ def classify_action_llm( budget = max(0, MAX_CLASSIFIER_TRANSCRIPT_CHARS - len(action_repr)) transcript = serialize_transcript_for_classifier(messages, tool_lookup, budget) - claude_md = "" + clawcodex_md = "" try: - from src.bootstrap.state import get_cached_claude_md_content + from src.bootstrap.state import get_cached_clawcodex_md_content - claude_md = get_cached_claude_md_content() or "" + clawcodex_md = get_cached_clawcodex_md_content() or "" except Exception: # noqa: BLE001 - claude_md = "" + clawcodex_md = "" user_body = "" - if claude_md: - user_body += f"\n{_truncate(claude_md)}\n\n\n" + if clawcodex_md: + user_body += f"\n{_truncate(clawcodex_md)}\n\n\n" if transcript: user_body += f"\n{transcript}\n\n\n" user_body += f"\n{action_repr}\n" diff --git a/src/plugins/karpathy_guidelines.py b/src/plugins/karpathy_guidelines.py index fab314189..2161fe988 100644 --- a/src/plugins/karpathy_guidelines.py +++ b/src/plugins/karpathy_guidelines.py @@ -14,7 +14,7 @@ from .builtin_plugins import register_builtin_plugin from .types import BuiltinPluginDefinition -KARPATHY_GUIDELINES_PROMPT = '# CLAUDE.md\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 1. Think Before Coding\n\n**Don\'t assume. Don\'t hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don\'t pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what\'s confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No "flexibility" or "configurability" that wasn\'t requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n- Don\'t "improve" adjacent code, comments, or formatting.\n- Don\'t refactor things that aren\'t broken.\n- Match existing style, even if you\'d do it differently.\n- If you notice unrelated dead code, mention it - don\'t delete it.\n\nWhen your changes create orphans:\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don\'t remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user\'s request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n- "Add validation" -> "Write tests for invalid inputs, then make them pass"\n- "Fix the bug" -> "Write a test that reproduces it, then make it pass"\n- "Refactor X" -> "Ensure tests pass before and after"\n\nFor multi-step tasks, state a brief plan:\n```\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.\n\n---\n\n**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.\n' +KARPATHY_GUIDELINES_PROMPT = '# CLAWCODEX.md\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 1. Think Before Coding\n\n**Don\'t assume. Don\'t hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don\'t pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what\'s confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No "flexibility" or "configurability" that wasn\'t requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n- Don\'t "improve" adjacent code, comments, or formatting.\n- Don\'t refactor things that aren\'t broken.\n- Match existing style, even if you\'d do it differently.\n- If you notice unrelated dead code, mention it - don\'t delete it.\n\nWhen your changes create orphans:\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don\'t remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user\'s request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n- "Add validation" -> "Write tests for invalid inputs, then make them pass"\n- "Fix the bug" -> "Write a test that reproduces it, then make it pass"\n- "Refactor X" -> "Ensure tests pass before and after"\n\nFor multi-step tasks, state a brief plan:\n```\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.\n\n---\n\n**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.\n' def register_karpathy_guidelines_plugin() -> None: diff --git a/src/query/agent_loop_compat.py b/src/query/agent_loop_compat.py index 3ee325e0a..53933fc5f 100644 --- a/src/query/agent_loop_compat.py +++ b/src/query/agent_loop_compat.py @@ -20,7 +20,7 @@ ``@work(thread=False)`` — that would put the loop on Textual's main event loop and block UI rendering during model streams. -Also exports ``build_effective_system_prompt`` (CLAUDE.md + style + +Also exports ``build_effective_system_prompt`` (CLAWCODEX.md + style + git status assembly) so the cutover code can pre-build the system prompt before calling the adapter — ``query()`` expects it pre-built. """ @@ -110,7 +110,7 @@ def run_query_as_agent_loop_sync( + on_message persistence boilerplate at every call site. The semantics match legacy ``run_agent_loop``: - * Pre-built effective system prompt (CLAUDE.md + style + git status). + * Pre-built effective system prompt (CLAWCODEX.md + style + git status). * In-place conversation mutation (legacy contract — multi-prompt sessions need this so subsequent turns see prior history). * Returns a legacy ``AgentLoopResult`` shape. @@ -188,7 +188,7 @@ def build_effective_system_prompt( (``build_full_system_prompt_blocks`` — intro / # Doing tasks / # Executing actions / # Using your tools / # Tone / output-efficiency / env / memory / skills / …) with the resolved output-style prompt **appended**, then the - existing workspace + git + CLAUDE.md context preserved as a trailing + existing workspace + git + CLAWCODEX.md context preserved as a trailing (uncached) block. Why this exists: the TUI (``tui/agent_bridge.py``) and headless @@ -201,12 +201,12 @@ def build_effective_system_prompt( them, mirroring the engine's ``build_full_system_prompt_blocks`` + ``append_system_prompt`` shape. - CLAUDE.md note: ``build_full_system_prompt_blocks``' memory section is - *auto-memory* (``MEMORY.md`` via ``load_memory_prompt``), **not** CLAUDE.md. - On the engine path CLAUDE.md is injected into the *messages* via + CLAWCODEX.md note: ``build_full_system_prompt_blocks``' memory section is + *auto-memory* (``MEMORY.md`` via ``load_memory_prompt``), **not** CLAWCODEX.md. + On the engine path CLAWCODEX.md is injected into the *messages* via ``prepend_user_context``; the cutover does not do that, so we keep ``build_context_prompt`` (which emits ``## Project Instructions``) to - preserve CLAUDE.md — option (b) in + preserve CLAWCODEX.md — option (b) in ``my-docs/get-parity-by-folder/live-base-system-prompt-gap-analysis.md``. This overlaps the base ``# Environment`` section on CWD/date (a benign, documented duplication). @@ -246,7 +246,7 @@ def build_effective_system_prompt( # ``utils/systemPrompt.ts:63-75``, where the coordinator prompt swaps # in for defaultSystemPrompt while appendSystemPrompt is preserved). # ``style_prompt`` is this builder's append-channel, so it survives; - # the trailing workspace/git/CLAUDE.md context block below is also + # the trailing workspace/git/CLAWCODEX.md context block below is also # kept — TS coordinator sessions keep userContext/systemContext # (``QueryEngine.ts:300-306`` replaces only the default prompt). from ..coordinator import get_coordinator_system_prompt @@ -288,8 +288,8 @@ def build_effective_system_prompt( skills=skills, ) - # Preserve the existing workspace + git + CLAUDE.md context verbatim as a - # trailing uncached block (CLAUDE.md is NOT in the base blocks above). + # Preserve the existing workspace + git + CLAWCODEX.md context verbatim as a + # trailing uncached block (CLAWCODEX.md is NOT in the base blocks above). # # Tag it REQUEST-scope. This block is a *live workspace snapshot*: it embeds # ``git status`` (and file counts / top-level entries) that mutate the moment @@ -313,7 +313,7 @@ def build_effective_system_prompt( if coordinator: # workerToolsContext — TS merges this into the per-session userContext # (``QueryEngine.ts:300-306``); this port's userContext channel on the - # live path is the trailing context block (same route CLAUDE.md / git + # live path is the trailing context block (same route CLAWCODEX.md / git # status already take), rendered with the ``# {key}\n{value}`` entry # idiom of prepend_user_context (prompt_assembly.py:255-256). MCP # server names come from the ToolContext's connected-client catalog diff --git a/src/query/engine.py b/src/query/engine.py index 84ed66c85..6dede911e 100644 --- a/src/query/engine.py +++ b/src/query/engine.py @@ -217,7 +217,7 @@ async def submit_message( await self._build_system_prompt_parts() ) - # Prepend user context (CLAUDE.md + date) as + # Prepend user context (CLAWCODEX.md + date) as messages_for_query = prepend_user_context( list(self._mutable_messages), user_context, ) diff --git a/src/query/query.py b/src/query/query.py index e3852d978..70cd2777b 100644 --- a/src/query/query.py +++ b/src/query/query.py @@ -117,7 +117,7 @@ class QueryParams: # via provider.model; never persisted to settings. fallback_model: str | None = None # ch05 round-4 note — N/A-by-architecture on the LIVE paths: the - # adapter's build_effective_system_prompt already injects CLAUDE.md + # adapter's build_effective_system_prompt already injects CLAWCODEX.md # ("## Project Instructions") and the date via the SYSTEM prompt, so # wiring TS's prependUserContext there would double-inject. Only the # test-only engine path uses the message-based mechanism diff --git a/src/server/agent_server.py b/src/server/agent_server.py index 3f3616ecd..f20ccc841 100644 --- a/src/server/agent_server.py +++ b/src/server/agent_server.py @@ -475,7 +475,7 @@ async def _handle_control_request(self, msg: dict) -> None: await self._do_mcp_auth(request_id, inner.get("server")) return if subtype == "external_includes": - # External CLAUDE.md @-imports (ClaudeMdExternalIncludesDialog, §6). + # External CLAWCODEX.md @-imports (ClaudeMdExternalIncludesDialog, §6). try: from src.services.startup_gates import get_external_includes_state, list_external_includes @@ -516,7 +516,7 @@ async def _handle_control_request(self, msg: dict) -> None: # (no mtimes), so an external $EDITOR write stays invisible until a # bust — clear so the next turn's prompt assembly re-reads disk. try: - from src.context_system.claude_md import clear_memory_file_caches + from src.context_system.clawcodex_md import clear_memory_file_caches clear_memory_file_caches() self._reply(request_id, {"ok": True}) @@ -2088,7 +2088,7 @@ def _context_usage(self) -> dict: # Coordinator-filtered view: token accounting must reflect # the tool schemas actually sent on the wire. tool_schemas=_tool_schemas(coordinator_main_loop_registry(self.tool_registry)), - claude_md_content="", + clawcodex_md_content="", ) out.update({ "total_tokens": data.total_tokens, @@ -3880,7 +3880,7 @@ def _build_runtime(sess: _AgentSession, perm_mode: str | None) -> None: except Exception: # noqa: BLE001 — env repair is best-effort logger.debug("[agent-server] post-trust env apply failed", exc_info=True) - # ch02 round-4 WI-2 — warm the context caches (CLAUDE.md walk, + # ch02 round-4 WI-2 — warm the context caches (CLAWCODEX.md walk, # git status when trusted) during the user's typing window; the # first turn's build_context_prompt reads the same underlying # caches. Fire-and-forget daemon thread; failures swallowed. diff --git a/src/services/compact/post_compact_attachments.py b/src/services/compact/post_compact_attachments.py index c9776204e..438abdebc 100644 --- a/src/services/compact/post_compact_attachments.py +++ b/src/services/compact/post_compact_attachments.py @@ -68,9 +68,9 @@ def _should_exclude_from_post_compact_restore( return True basename = os.path.basename(filename).lower() - if basename in ("claude.md", ".claude.md", "claude_md"): + if basename in ("clawcodex.md", ".clawcodex.md"): return True - if basename.endswith(".claude.md") or basename.endswith("claude.md"): + if basename.endswith((".clawcodex.md", "clawcodex.md")): return True return False @@ -184,7 +184,7 @@ def create_post_compact_file_attachments( max_files: Maximum number of files to restore. preserved_messages: Messages kept post-compact; Read results here are skipped. plan_file_path: Path to the plan file (excluded from restore). - memory_paths: Paths to memory/claude.md files (excluded from restore). + memory_paths: Paths to memory/context-md files (excluded from restore). Returns: List of UserMessage attachments with file content. diff --git a/src/services/memory_append.py b/src/services/memory_append.py index b16d54f56..84a134e11 100644 --- a/src/services/memory_append.py +++ b/src/services/memory_append.py @@ -14,7 +14,7 @@ a file that doesn't end in ``\\n`` first); * write the note as a ``- `` bullet line unless the user already supplied list/heading punctuation — matching the bulleted style the - product writes to CLAUDE.md memory files. + product writes to CLAWCODEX.md memory files. """ from __future__ import annotations diff --git a/src/services/startup_gates.py b/src/services/startup_gates.py index 7c037f764..5454d2965 100644 --- a/src/services/startup_gates.py +++ b/src/services/startup_gates.py @@ -1,7 +1,7 @@ """Startup security gates (components C8) — UI-neutral state layer. Three gates, ported from the TS startup flow (interactiveHelpers.tsx: -trust :139-143, CLAUDE.md external includes :174-179, bypass +trust :139-143, CLAWCODEX.md external includes :174-179, bypass acceptance :228-232), in that order: 1. **Folder trust** (TS ``TrustDialog`` + ``config.ts @@ -14,11 +14,11 @@ trust for ``$HOME`` would blanket-trust everything under it. Decline exits with code 1. -2. **External CLAUDE.md includes** (TS +2. **External CLAWCODEX.md includes** (TS ``ClaudeMdExternalIncludesDialog`` :120-132): per-project boolean pair — ``hasClaudeMdExternalIncludesApproved`` + ``hasClaudeMdExternalIncludesWarningShown``. Asked once (yes/no both - set WarningShown); the loader (``context_system/claude_md.py``) + set WarningShown); the loader (``context_system/clawcodex_md.py``) includes externals only when approved. Non-interactive sessions never load unapproved externals (TS parity: silent skip — the include flag simply stays false). @@ -211,7 +211,7 @@ def collect_trust_warnings(cwd: str | Path | None = None) -> list[str]: # --------------------------------------------------------------------------- -# Gate 2: external CLAUDE.md includes +# Gate 2: external CLAWCODEX.md includes # --------------------------------------------------------------------------- def get_external_includes_state(cwd: str | Path | None = None) -> str: @@ -248,7 +248,7 @@ def record_external_includes_choice( # The loader memo AND the assembled-context memo both embed the # external-include decision; clear both so an approval recorded # mid-session takes effect on the next prompt, not next launch. - from src.context_system.claude_md import clear_memory_file_caches + from src.context_system.clawcodex_md import clear_memory_file_caches clear_memory_file_caches() try: @@ -266,7 +266,7 @@ async def list_external_includes(cwd: str | Path | None = None) -> list[str]: """Paths of external @includes that WOULD load if approved (TS hasExternalClaudeMdIncludes over ``getMemoryFiles(true)``).""" - from src.context_system.claude_md import ( + from src.context_system.clawcodex_md import ( get_memory_files, is_external_memory_file, ) diff --git a/src/skills/loader.py b/src/skills/loader.py index f00e58913..3d465fa6b 100644 --- a/src/skills/loader.py +++ b/src/skills/loader.py @@ -622,13 +622,13 @@ def _get_project_skills_dirs(cwd: str) -> list[str]: # `isRestrictedToPluginOnly('skills')` gates so the Python loader honors # the same env-driven safety flags. We reuse the existing # `_is_bare_mode` / `_get_additional_directories` semantics already -# established by `src/context_system/claude_md.py` rather than inventing +# established by `src/context_system/clawcodex_md.py` rather than inventing # parallel ones. # ---------------------------------------------------------------------- def _is_bare_mode() -> bool: - """True when ``CLAUDE_CODE_BARE_MODE`` is set (matches CLAUDE.md path). + """True when ``CLAUDE_CODE_BARE_MODE`` is set (matches CLAWCODEX.md path). Bare mode skips autodiscovery entirely; only ``--add-dir`` paths contribute disk skills. Bundled/MCP skills are unaffected (they go diff --git a/src/utils/legacy_migration.py b/src/utils/legacy_migration.py index a3489179c..491a58944 100644 --- a/src/utils/legacy_migration.py +++ b/src/utils/legacy_migration.py @@ -77,6 +77,11 @@ _USER_LEVEL_DIRS = ("agents", "workflows", "outputStyles", "plugins", "rules") _USER_LEVEL_FILES = ("CLAUDE.md",) +#: Migration renames: the legacy harness's context file lands under the +#: app's canonical name (clawcodex reads CLAWCODEX.md first; the legacy +#: name only survives as a read fallback for pre-rename installs). +_MIGRATE_RENAMES = {"CLAUDE.md": "CLAWCODEX.md"} + #: Project-level items for the explicit ``clawcodex migrate`` command. _PROJECT_LEVEL_DIRS = ("skills", "agents", "workflows", "rules") _PROJECT_LEVEL_FILES = ("CLAUDE.md", "config.json", "config.local.json", "loop.md") @@ -281,7 +286,8 @@ def migrate_user_dir_once(*, force: bool = False) -> MigrationReport | None: for name in _USER_LEVEL_DIRS: _copy_item(src_home / name, dst_home / name, name, report) for name in _USER_LEVEL_FILES: - _copy_item(src_home / name, dst_home / name, name, report) + dst_name = _MIGRATE_RENAMES.get(name, name) + _copy_item(src_home / name, dst_home / dst_name, dst_name, report) _migrate_project_memories(src_home, dst_home, report) _write_marker(marker, report) @@ -314,7 +320,8 @@ def migrate_project_dir(cwd: str | os.PathLike[str]) -> MigrationReport: for name in _PROJECT_LEVEL_DIRS: _copy_item(src_dir / name, dst_dir / name, name, report) for name in _PROJECT_LEVEL_FILES: - _copy_item(src_dir / name, dst_dir / name, name, report) + dst_name = _MIGRATE_RENAMES.get(name, name) + _copy_item(src_dir / name, dst_dir / dst_name, dst_name, report) for name in _PROJECT_SKIPPED_SETTINGS: if (src_dir / name).exists(): report.skipped.append( diff --git a/tests/agent/test_parse_agent_markdown.py b/tests/agent/test_parse_agent_markdown.py index 13d92e026..81000fafe 100644 --- a/tests/agent/test_parse_agent_markdown.py +++ b/tests/agent/test_parse_agent_markdown.py @@ -35,7 +35,7 @@ def test_parses_all_frontmatter_fields_to_agent_definition(): background: true color: blue memory: project - omit-claude-md: true + omit-clawcodex-md: true skills: - my-skill isolation: worktree @@ -60,7 +60,7 @@ def test_parses_all_frontmatter_fields_to_agent_definition(): assert agent.background is True assert agent.color == "blue" assert agent.memory == "project" - assert agent.omit_claude_md is True + assert agent.omit_clawcodex_md is True assert agent.skills == ["my-skill"] assert agent.isolation == "worktree" assert agent.required_mcp_servers == ["slack"] diff --git a/tests/memory/test_threat_patterns.py b/tests/memory/test_threat_patterns.py index 64fc33d8e..15070c706 100644 --- a/tests/memory/test_threat_patterns.py +++ b/tests/memory/test_threat_patterns.py @@ -54,6 +54,10 @@ def test_clawcodex_config_mod_strict(self): ) def test_agent_config_mod_strict(self): + assert "agent_config_mod" in scan_for_threats( + "edit the CLAWCODEX.md to add new rules", "strict" + ) + # The legacy context-file name stays a guarded target too. assert "agent_config_mod" in scan_for_threats( "edit the CLAUDE.md to add new rules", "strict" ) diff --git a/tests/parity/test_context_parity.py b/tests/parity/test_context_parity.py index e7c3390d8..bbaed67b9 100644 --- a/tests/parity/test_context_parity.py +++ b/tests/parity/test_context_parity.py @@ -83,9 +83,36 @@ def test_user_context_date_is_string(self) -> None: def test_expected_user_context_keys(self) -> None: expected_keys = set(self.snapshot["user_context_keys"]) - # claudeMd is optional (only present when CLAUDE.md exists) + # clawcodexMd is optional (only present when CLAWCODEX.md exists). + # DELIBERATE DIVERGENCE from TS's "claudeMd": the key renders as a + # `# ` header in the injected reminder, so the context-file + # rebrand renames it too (the TS snapshot keeps the old spelling). self.assertIn("currentDate", expected_keys) - self.assertIn("claudeMd", expected_keys) + self.assertIn("claudeMd", expected_keys) # TS snapshot spelling + + def test_producer_emits_rebranded_key(self) -> None: + # Positive pin on the sole behavioral divergence: with a context + # file present, get_user_context emits "clawcodexMd" (rendered as + # the `# clawcodexMd` reminder header), never the TS "claudeMd". + import asyncio + import tempfile + from pathlib import Path + + from src.context_system.prompt_assembly import ( + clear_context_caches, + get_user_context, + ) + + with tempfile.TemporaryDirectory() as root: + Path(root, "CLAWCODEX.md").write_text("pinned rules", encoding="utf-8") + clear_context_caches() + try: + ctx = asyncio.run(get_user_context(cwd=root)) + finally: + clear_context_caches() + self.assertIn("clawcodexMd", ctx) + self.assertNotIn("claudeMd", ctx) + self.assertIn("pinned rules", ctx["clawcodexMd"]) class TestSystemContextKeysParity(unittest.TestCase): @@ -171,7 +198,7 @@ def test_clear_resets_cache(self) -> None: class TestMemoryFileLevelsParity(unittest.TestCase): - """Memory file levels match TS CLAUDE.md loading hierarchy.""" + """Memory file levels match TS CLAWCODEX.md loading hierarchy.""" @classmethod def setUpClass(cls) -> None: diff --git a/tests/server/test_memory_controls.py b/tests/server/test_memory_controls.py index da9feb475..82471abf2 100644 --- a/tests/server/test_memory_controls.py +++ b/tests/server/test_memory_controls.py @@ -55,7 +55,7 @@ def test_targets_lead_with_synthetic_user_and_project_rows(self) -> None: cwd.mkdir() with ( patch("pathlib.Path.home", classmethod(lambda cls: home)), - patch("src.context_system.claude_md.get_memory_files", _no_files), + patch("src.context_system.clawcodex_md.get_memory_files", _no_files), patch("src.utils.git.get_repo_root", lambda *a, **k: None), ): sess, emitted = _make_session(str(cwd)) @@ -64,11 +64,11 @@ def test_targets_lead_with_synthetic_user_and_project_rows(self) -> None: self.assertTrue(reply["ok"]) targets = reply["targets"] self.assertEqual(targets[0]["label"], "User memory") - self.assertEqual(targets[0]["path"], str(home / ".clawcodex" / "CLAUDE.md")) - self.assertEqual(targets[0]["description"], "Saved in ~/.clawcodex/CLAUDE.md") + self.assertEqual(targets[0]["path"], str(home / ".clawcodex" / "CLAWCODEX.md")) + self.assertEqual(targets[0]["description"], "Saved in ~/.clawcodex/CLAWCODEX.md") self.assertEqual(targets[1]["label"], "Project memory") - self.assertEqual(targets[1]["path"], str(cwd / "CLAUDE.md")) - self.assertEqual(targets[1]["description"], "Saved in ./CLAUDE.md") + self.assertEqual(targets[1]["path"], str(cwd / "CLAWCODEX.md")) + self.assertEqual(targets[1]["description"], "Saved in ./CLAWCODEX.md") def test_targets_error_guard_replies_clean_failure(self) -> None: with tempfile.TemporaryDirectory(prefix="mem_ctl_") as tmp: @@ -84,18 +84,18 @@ def test_targets_error_guard_replies_clean_failure(self) -> None: self.assertEqual(reply["targets"], []) def test_memory_edited_busts_memory_file_cache(self) -> None: - import src.context_system.claude_md as claude_md + import src.context_system.clawcodex_md as clawcodex_md with tempfile.TemporaryDirectory(prefix="mem_ctl_") as tmp: - saved = claude_md._memory_files_cache + saved = clawcodex_md._memory_files_cache try: - claude_md._memory_files_cache = ("stale-key", []) + clawcodex_md._memory_files_cache = ("stale-key", []) sess, emitted = _make_session(tmp) _control(sess, "memory_edited") self.assertTrue(_last_reply(emitted)["ok"]) - self.assertIsNone(claude_md._memory_files_cache) + self.assertIsNone(clawcodex_md._memory_files_cache) finally: - claude_md._memory_files_cache = saved + clawcodex_md._memory_files_cache = saved if __name__ == "__main__": diff --git a/tests/test_bootstrap_state.py b/tests/test_bootstrap_state.py index 58f2b05f8..bdb1174c4 100644 --- a/tests/test_bootstrap_state.py +++ b/tests/test_bootstrap_state.py @@ -32,7 +32,7 @@ add_to_total_duration_state, add_to_total_lines_changed, consume_post_compaction, - get_cached_claude_md_content, + get_cached_clawcodex_md_content, get_client_type, get_cwd_state, get_is_interactive, @@ -54,7 +54,7 @@ reset_cost_state, reset_state_for_tests, run_with_sdk_context, - set_cached_claude_md_content, + set_cached_clawcodex_md_content, set_client_type, set_cost_state_for_restore, set_cwd_state, @@ -314,17 +314,17 @@ def test_clear_main_loop_model_override(self) -> None: class TestCachedClaudeMd(unittest.TestCase): - def test_cached_claude_md_starts_none(self) -> None: - self.assertIsNone(get_cached_claude_md_content()) + def test_cached_clawcodex_md_starts_none(self) -> None: + self.assertIsNone(get_cached_clawcodex_md_content()) - def test_set_get_cached_claude_md(self) -> None: - set_cached_claude_md_content("# Project Notes\nfoo") - self.assertEqual(get_cached_claude_md_content(), "# Project Notes\nfoo") + def test_set_get_cached_clawcodex_md(self) -> None: + set_cached_clawcodex_md_content("# Project Notes\nfoo") + self.assertEqual(get_cached_clawcodex_md_content(), "# Project Notes\nfoo") - def test_clear_cached_claude_md(self) -> None: - set_cached_claude_md_content("foo") - set_cached_claude_md_content(None) - self.assertIsNone(get_cached_claude_md_content()) + def test_clear_cached_clawcodex_md(self) -> None: + set_cached_clawcodex_md_content("foo") + set_cached_clawcodex_md_content(None) + self.assertIsNone(get_cached_clawcodex_md_content()) class TestSdkContext(unittest.TestCase): diff --git a/tests/test_claude_md.py b/tests/test_clawcodex_md.py similarity index 88% rename from tests/test_claude_md.py rename to tests/test_clawcodex_md.py index e64644907..89828518e 100644 --- a/tests/test_claude_md.py +++ b/tests/test_clawcodex_md.py @@ -1,4 +1,4 @@ -"""Tests for src/context_system/claude_md.py — WS-5 multi-level CLAUDE.md.""" +"""Tests for src/context_system/clawcodex_md.py — WS-5 multi-level CLAWCODEX.md.""" from __future__ import annotations @@ -9,13 +9,13 @@ from pathlib import Path from unittest.mock import patch -from src.context_system.claude_md import ( +from src.context_system.clawcodex_md import ( _extract_include_paths, _parse_frontmatter_paths, _parse_memory_file_content, _resolve_include_path, clear_memory_file_caches, - get_claude_mds, + get_clawcodex_mds, get_memory_files, is_memory_file_path, process_md_rules, @@ -122,7 +122,7 @@ def test_wildcard_treated_as_none(self): class TestParseMemoryFileContent(unittest.TestCase): def test_basic_md_file(self): info, includes = _parse_memory_file_content( - "# Rules\nAlways test.", "/project/CLAUDE.md", "Project", + "# Rules\nAlways test.", "/project/CLAWCODEX.md", "Project", ) self.assertIsNotNone(info) self.assertEqual(info.type, "Project") @@ -142,8 +142,8 @@ def test_html_comments_stripped(self): def test_include_paths_extracted(self): info, includes = _parse_memory_file_content( - "See @./extra.md for more", "/project/CLAUDE.md", "Project", - include_base_path="/project/CLAUDE.md", + "See @./extra.md for more", "/project/CLAWCODEX.md", "Project", + include_base_path="/project/CLAWCODEX.md", ) self.assertTrue(len(includes) > 0) @@ -151,7 +151,7 @@ def test_include_paths_extracted(self): class TestProcessMemoryFile(unittest.TestCase): def test_basic_file(self): with tempfile.TemporaryDirectory() as tmp: - f = Path(tmp) / "CLAUDE.md" + f = Path(tmp) / "CLAWCODEX.md" f.write_text("# Rules\nAlways test.", encoding="utf-8") result = _run(process_memory_file(str(f), "Project", set())) self.assertEqual(len(result), 1) @@ -159,12 +159,12 @@ def test_basic_file(self): self.assertIn("Always test", result[0].content) def test_nonexistent_file(self): - result = _run(process_memory_file("/nonexistent/CLAUDE.md", "Project", set())) + result = _run(process_memory_file("/nonexistent/CLAWCODEX.md", "Project", set())) self.assertEqual(len(result), 0) def test_circular_reference_prevention(self): with tempfile.TemporaryDirectory() as tmp: - f = Path(tmp) / "CLAUDE.md" + f = Path(tmp) / "CLAWCODEX.md" f.write_text(f"Include self: @{f}", encoding="utf-8") result = _run(process_memory_file(str(f), "Project", set())) # Should process once, not infinitely loop @@ -172,7 +172,7 @@ def test_circular_reference_prevention(self): def test_include_chain(self): with tempfile.TemporaryDirectory() as tmp: - main = Path(tmp) / "CLAUDE.md" + main = Path(tmp) / "CLAWCODEX.md" included = Path(tmp) / "extra.md" included.write_text("Extra rules.", encoding="utf-8") main.write_text(f"Main rules.\n@{included}", encoding="utf-8") @@ -239,14 +239,14 @@ def test_conditional_rules(self): class TestGetClaudeMds(unittest.TestCase): def test_empty_list(self): - self.assertEqual(get_claude_mds([]), "") + self.assertEqual(get_clawcodex_mds([]), "") def test_formats_correctly(self): files = [ - MemoryFileInfo(path="/p/CLAUDE.md", type="Project", content="Rule 1"), - MemoryFileInfo(path="/home/.clawcodex/CLAUDE.md", type="User", content="Rule 2"), + MemoryFileInfo(path="/p/CLAWCODEX.md", type="Project", content="Rule 1"), + MemoryFileInfo(path="/home/.clawcodex/CLAWCODEX.md", type="User", content="Rule 2"), ] - result = get_claude_mds(files) + result = get_clawcodex_mds(files) self.assertIn("Rule 1", result) self.assertIn("Rule 2", result) self.assertIn("project instructions", result) @@ -255,19 +255,19 @@ def test_formats_correctly(self): def test_empty_content_skipped(self): files = [ - MemoryFileInfo(path="/p/CLAUDE.md", type="Project", content=""), + MemoryFileInfo(path="/p/CLAWCODEX.md", type="Project", content=""), MemoryFileInfo(path="/p/RULES.md", type="Project", content="Real rule"), ] - result = get_claude_mds(files) + result = get_clawcodex_mds(files) self.assertIn("Real rule", result) - self.assertNotIn("CLAUDE.md", result) + self.assertNotIn("CLAWCODEX.md", result) class TestGetMemoryFiles(unittest.TestCase): def test_caching(self): clear_memory_file_caches() with tempfile.TemporaryDirectory() as tmp: - (Path(tmp) / "CLAUDE.md").write_text("test", encoding="utf-8") + (Path(tmp) / "CLAWCODEX.md").write_text("test", encoding="utf-8") with patch.dict(os.environ, {"CLAUDE_CODE_ORIGINAL_CWD": tmp}): result1 = _run(get_memory_files(cwd=tmp)) result2 = _run(get_memory_files(cwd=tmp)) @@ -278,7 +278,7 @@ def test_caching(self): def test_cache_clearing(self): clear_memory_file_caches() with tempfile.TemporaryDirectory() as tmp: - (Path(tmp) / "CLAUDE.md").write_text("test", encoding="utf-8") + (Path(tmp) / "CLAWCODEX.md").write_text("test", encoding="utf-8") with patch.dict(os.environ, {"CLAUDE_CODE_ORIGINAL_CWD": tmp}): _run(get_memory_files(cwd=tmp)) clear_memory_file_caches() @@ -290,7 +290,7 @@ def test_cache_clearing(self): def test_bare_mode_disables(self): clear_memory_file_caches() with tempfile.TemporaryDirectory() as tmp: - (Path(tmp) / "CLAUDE.md").write_text("test", encoding="utf-8") + (Path(tmp) / "CLAWCODEX.md").write_text("test", encoding="utf-8") with patch.dict(os.environ, { "CLAUDE_CODE_BARE_MODE": "true", "CLAUDE_CODE_ADDITIONAL_DIRECTORIES": "", @@ -307,11 +307,11 @@ def test_bare_mode_disables(self): class TestIsMemoryFilePath(unittest.TestCase): - def test_claude_md(self): - self.assertTrue(is_memory_file_path("/project/CLAUDE.md")) + def test_clawcodex_md(self): + self.assertTrue(is_memory_file_path("/project/CLAWCODEX.md")) def test_local_md(self): - self.assertTrue(is_memory_file_path("/project/CLAUDE.local.md")) + self.assertTrue(is_memory_file_path("/project/CLAWCODEX.local.md")) def test_rules_file(self): self.assertTrue(is_memory_file_path("/project/.clawcodex/rules/style.md")) diff --git a/tests/test_command_system.py b/tests/test_command_system.py index 787da20d4..d7f8bec77 100644 --- a/tests/test_command_system.py +++ b/tests/test_command_system.py @@ -413,7 +413,7 @@ def test_init_command_has_correct_description(self): """Test that /init has the correct description.""" init_cmd = self._get_init_command() self.assertIsNotNone(init_cmd) - self.assertIn("CLAUDE.md", init_cmd.description) + self.assertIn("CLAWCODEX.md", init_cmd.description) self.assertIn("skills", init_cmd.description) self.assertIn("hooks", init_cmd.description) @@ -438,13 +438,13 @@ def test_init_command_has_prompt_content(self): self.assertIn("Step 6", content) self.assertIn("Step 7", content) - def test_init_command_includes_claude_md_instructions(self): - """Test that /init prompt includes CLAUDE.md creation instructions.""" + def test_init_command_includes_clawcodex_md_instructions(self): + """Test that /init prompt includes CLAWCODEX.md creation instructions.""" init_cmd = self._get_init_command() self.assertIsNotNone(init_cmd) content = init_cmd.markdown_content - # Verify it includes key CLAUDE.md content requirements - self.assertIn("CLAUDE.md", content) + # Verify it includes key CLAWCODEX.md content requirements + self.assertIn("CLAWCODEX.md", content) self.assertIn("build/test/lint", content.lower()) self.assertIn("code style", content.lower()) diff --git a/tests/test_context_analyzer.py b/tests/test_context_analyzer.py index 71fc39ba6..20d545def 100644 --- a/tests/test_context_analyzer.py +++ b/tests/test_context_analyzer.py @@ -42,7 +42,7 @@ def test_empty_inputs(self): model="claude-sonnet-4-6", system_prompt="", tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", ) self.assertEqual(result.model, "claude-sonnet-4-6") self.assertEqual(result.max_tokens, 200_000) @@ -57,7 +57,7 @@ def test_system_prompt_tokens(self): model="claude-sonnet-4-6", system_prompt="You are a helpful assistant. " * 50, # ~500 chars tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", ) system_cat = next((c for c in result.categories if c.name == "System prompt"), None) self.assertIsNotNone(system_cat) @@ -74,7 +74,7 @@ def test_tool_schemas_tokens(self): {"name": "Read", "description": "Read a file", "input_schema": {"type": "object"}}, {"name": "Write", "description": "Write a file", "input_schema": {"type": "object"}}, ], - claude_md_content="", + clawcodex_md_content="", ) tool_cat = next((c for c in result.categories if c.name == "System tools"), None) self.assertIsNotNone(tool_cat) @@ -91,7 +91,7 @@ def test_message_tokens(self): model="claude-sonnet-4-6", system_prompt="", tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", ) msg_cat = next((c for c in result.categories if c.name == "Messages"), None) self.assertIsNotNone(msg_cat) @@ -105,7 +105,7 @@ def test_free_space_calculation(self): model="claude-sonnet-4-6", system_prompt="x", # minimal tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", ) free_cat = next((c for c in result.categories if c.name == "Free space"), None) self.assertIsNotNone(free_cat) @@ -121,7 +121,7 @@ def test_api_usage_overrides_total(self): model="claude-sonnet-4-6", system_prompt="", tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", api_usage={ "input_tokens": 50000, "output_tokens": 10000, @@ -146,7 +146,7 @@ def test_produces_valid_markdown(self): model="claude-sonnet-4-6", system_prompt="You are helpful.", tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", ) markdown = format_context_as_markdown(result) self.assertIn("## Context Usage", markdown) @@ -162,24 +162,24 @@ def test_shows_system_prompt_category(self): model="claude-sonnet-4-6", system_prompt="You are a helpful assistant. " * 100, tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", ) markdown = format_context_as_markdown(result) self.assertIn("System prompt", markdown) def test_shows_memory_files(self): - """Memory files section appears when CLAUDE.md is present.""" + """Memory files section appears when CLAWCODEX.md is present.""" from src.context_system.context_analyzer import analyze_context, format_context_as_markdown result = analyze_context( conversation_api_messages=[], model="claude-sonnet-4-6", system_prompt="", tool_schemas=[], - claude_md_content="# Project\n\nThis is a project.", + clawcodex_md_content="# Project\n\nThis is a project.", ) markdown = format_context_as_markdown(result) self.assertIn("### Memory Files", markdown) - self.assertIn("CLAUDE.md", markdown) + self.assertIn("CLAWCODEX.md", markdown) def test_shows_api_usage(self): """API usage section appears when usage data is provided.""" @@ -189,7 +189,7 @@ def test_shows_api_usage(self): model="claude-sonnet-4-6", system_prompt="", tool_schemas=[], - claude_md_content="", + clawcodex_md_content="", api_usage={ "input_tokens": 10000, "output_tokens": 5000, diff --git a/tests/test_context_system.py b/tests/test_context_system.py index df47744a9..15a9e7ce8 100644 --- a/tests/test_context_system.py +++ b/tests/test_context_system.py @@ -9,7 +9,7 @@ from src.agent.conversation import Conversation from src.context_system import build_context_prompt -from src.context_system.claude_md import clear_memory_file_caches +from src.context_system.clawcodex_md import clear_memory_file_caches from src.context_system.git_context import clear_git_caches, collect_git_context from src.providers.base import ChatResponse from src.query.agent_loop_compat import run_query_as_agent_loop_sync as run_agent_loop @@ -30,10 +30,10 @@ def tearDown(self): clear_memory_file_caches() clear_git_caches() - def test_build_context_prompt_includes_workspace_and_claude_md(self) -> None: + def test_build_context_prompt_includes_workspace_and_clawcodex_md(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) - (root / "CLAUDE.md").write_text("Project rule: always add tests.", encoding="utf-8") + (root / "CLAWCODEX.md").write_text("Project rule: always add tests.", encoding="utf-8") (root / "README.md").write_text("# Demo\n", encoding="utf-8") (root / "pyproject.toml").write_text("[project]\nname='demo'\n", encoding="utf-8") (root / "src").mkdir() @@ -59,7 +59,7 @@ def test_agent_loop_injects_context_prompt_for_non_anthropic(self) -> None: registry = build_default_registry(include_user_tools=False) with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) - (root / "CLAUDE.md").write_text("Follow the CLAUDE instructions.", encoding="utf-8") + (root / "CLAWCODEX.md").write_text("Follow the CLAUDE instructions.", encoding="utf-8") (root / "README.md").write_text("# Demo\n", encoding="utf-8") ctx = ToolContext(workspace_root=root) @@ -71,7 +71,7 @@ def test_agent_loop_injects_context_prompt_for_non_anthropic(self) -> None: # Model a real *generic* (non-DeepSeek) provider: is_deepseek is a # real False, not a truthy auto-MagicMock. Otherwise the query layer # treats this mock as DeepSeek and relocates the REQUEST-scope - # workspace/git/CLAUDE.md context out of the system message into the + # workspace/git/CLAWCODEX.md context out of the system message into the # trailing prefix-cache tail — which is exactly what this test is # asserting does NOT happen for a non-DeepSeek provider. provider.is_deepseek = False diff --git a/tests/test_deepseek_prefix_cache.py b/tests/test_deepseek_prefix_cache.py index 115631104..7d44e3b49 100644 --- a/tests/test_deepseek_prefix_cache.py +++ b/tests/test_deepseek_prefix_cache.py @@ -396,7 +396,7 @@ def test_effective_prompt_tags_context_block_request_scope(tmp_path): def test_deepseek_relocates_git_context_out_of_prefix(tmp_path): - """The volatile workspace snapshot rides the tail; CLAUDE.md is preserved + """The volatile workspace snapshot rides the tail; CLAWCODEX.md is preserved there (not dropped) and is NOT left in the prefix.""" blocks = _effective_blocks(_CTX_GIT_T1, tmp_path) system, tail = _split_system_prompt_blocks(blocks, relocate_request_scope=True) @@ -432,7 +432,7 @@ def test_non_deepseek_keeps_context_block_in_system(tmp_path): def test_deepseek_end_to_end_relocates_context_after_history(tmp_path): """End-to-end through the exact new-TUI adapter the agent-server uses: build_effective_system_prompt -> run_query_as_agent_loop -> _call_model_sync - (DeepSeek). Pins the full wire composition: the live workspace/git/CLAUDE.md + (DeepSeek). Pins the full wire composition: the live workspace/git/CLAWCODEX.md context must be ABSENT from the system message and instead ride a trailing user that lands AFTER the conversation history.""" import asyncio @@ -480,7 +480,7 @@ def chat_stream_response(self, messages, tools=None, on_text_chunk=None, assert captured, "the DeepSeek provider was never called" wire = captured[0] - # System message (index 0) is free of the volatile snapshot + CLAUDE.md. + # System message (index 0) is free of the volatile snapshot + CLAWCODEX.md. assert wire[0]["role"] == "system" assert "## Git Context" not in wire[0]["content"] assert "CLAUDE_MD_SENTINEL" not in wire[0]["content"] diff --git a/tests/test_live_base_system_prompt.py b/tests/test_live_base_system_prompt.py index 4a12d9a6d..8766eef1d 100644 --- a/tests/test_live_base_system_prompt.py +++ b/tests/test_live_base_system_prompt.py @@ -4,7 +4,7 @@ ``{style}\n\n{context}`` (no base sections), so the live TUI/headless agent received NO operating instructions. The fix makes it return the full base prompt as a block list (mirroring the engine/REPL canonical path), with the -style appended and the workspace/git/CLAUDE.md context preserved. +style appended and the workspace/git/CLAWCODEX.md context preserved. See ``my-docs/get-parity-by-folder/live-base-system-prompt-gap-analysis.md``. """ @@ -96,19 +96,19 @@ def test_does_not_emit_prose_tool_docs(tmp_path: Path): assert "# Available Tools" not in text -def test_preserves_claude_md_project_instructions(tmp_path: Path, monkeypatch): - """CLAUDE.md must NOT be dropped (critic B1): it is kept via the trailing +def test_preserves_clawcodex_md_project_instructions(tmp_path: Path, monkeypatch): + """CLAWCODEX.md must NOT be dropped (critic B1): it is kept via the trailing build_context_prompt block (## Project Instructions), since the base - blocks' memory section is MEMORY.md auto-memory, not CLAUDE.md.""" - # Isolate HOME so global ~/.claude/CLAUDE.md doesn't mask the test file. + blocks' memory section is MEMORY.md auto-memory, not CLAWCODEX.md.""" + # Isolate HOME so global ~/.claude/CLAWCODEX.md doesn't mask the test file. monkeypatch.setenv("HOME", str(tmp_path / "home")) (tmp_path / "home").mkdir() marker = "PROJECT_CLAUDE_MD_SENTINEL_xyz" - (tmp_path / "CLAUDE.md").write_text(f"# Project rules\n{marker}\n", encoding="utf-8") + (tmp_path / "CLAWCODEX.md").write_text(f"# Project rules\n{marker}\n", encoding="utf-8") ctx = ToolContext(workspace_root=tmp_path) text = _joined(build_effective_system_prompt(_SENTINEL_STYLE, ctx)) - assert marker in text, "CLAUDE.md project instructions were dropped" + assert marker in text, "CLAWCODEX.md project instructions were dropped" def test_provider_none_is_safe(tmp_path: Path): diff --git a/tests/test_memory_command.py b/tests/test_memory_command.py index b83989612..1a0d51df0 100644 --- a/tests/test_memory_command.py +++ b/tests/test_memory_command.py @@ -53,7 +53,7 @@ async def _no_files(cwd=None, **kwargs): return [] monkeypatch.setattr( - "src.context_system.claude_md.get_memory_files", _no_files + "src.context_system.clawcodex_md.get_memory_files", _no_files ) cwd = tmp_path / "proj" cwd.mkdir() @@ -82,17 +82,17 @@ async def test_options_synthetic_candidates_with_descriptions(mem_env): call = ui.select_calls[0] assert call["labels"][0] == "User memory" assert call["labels"][1] == "Project memory" - assert call["descriptions"][0] == "Saved in ~/.clawcodex/CLAUDE.md" # verbatim TS - assert call["descriptions"][1] == "Saved in ./CLAUDE.md" # non-git branch - assert call["values"][0] == str(home / ".clawcodex" / "CLAUDE.md") - assert call["values"][1] == str(cwd / "CLAUDE.md") # fallback (no loaded ancestor) + assert call["descriptions"][0] == "Saved in ~/.clawcodex/CLAWCODEX.md" # verbatim TS + assert call["descriptions"][1] == "Saved in ./CLAWCODEX.md" # non-git branch + assert call["values"][0] == str(home / ".clawcodex" / "CLAWCODEX.md") + assert call["values"][1] == str(cwd / "CLAWCODEX.md") # fallback (no loaded ancestor) async def test_project_memory_resolved_from_ancestor_walk(mem_env, monkeypatch): # From a repo SUBDIRECTORY the Project entry must point at the loaded - # root-level CLAUDE.md, not {cwd}/CLAUDE.md (critic M2). + # root-level CLAWCODEX.md, not {cwd}/CLAWCODEX.md (critic M2). home, cwd = mem_env - root_claude = cwd / "CLAUDE.md" + root_claude = cwd / "CLAWCODEX.md" root_claude.write_text("root project mem") subdir = cwd / "sub" subdir.mkdir() @@ -100,35 +100,35 @@ async def test_project_memory_resolved_from_ancestor_walk(mem_env, monkeypatch): async def _files(cwd=None, **kwargs): return [_Info(str(root_claude), type="Project", parent=None)] - monkeypatch.setattr("src.context_system.claude_md.get_memory_files", _files) + monkeypatch.setattr("src.context_system.clawcodex_md.get_memory_files", _files) ui = FakeUIHost(pick=None) await MEMORY_COMMAND.run("", _ctx(subdir, ui=ui)) call = ui.select_calls[0] - assert call["values"][1] == str(root_claude) # ancestor-resolved, not sub/CLAUDE.md + assert call["values"][1] == str(root_claude) # ancestor-resolved, not sub/CLAWCODEX.md assert call["values"].count(str(root_claude)) == 1 # deduped from the enumeration assert "Project memory" in call["labels"] async def test_project_memory_prefers_nearest_ancestor(mem_env, monkeypatch): - # Monorepo: /repo/CLAUDE.md + /repo/sub/CLAUDE.md, cwd=/repo/sub/deep -> + # Monorepo: /repo/CLAWCODEX.md + /repo/sub/CLAWCODEX.md, cwd=/repo/sub/deep -> # the NEAREST loaded ancestor wins (TS walks cwd upward). home, cwd = mem_env - (cwd / "CLAUDE.md").write_text("root") + (cwd / "CLAWCODEX.md").write_text("root") sub = cwd / "sub" deep = sub / "deep" deep.mkdir(parents=True) - (sub / "CLAUDE.md").write_text("nearer") + (sub / "CLAWCODEX.md").write_text("nearer") async def _files(cwd=None, **kwargs): return [ # enumeration order is root -> cwd - _Info(str(mem_env[1] / "CLAUDE.md"), type="Project", parent=None), - _Info(str(sub / "CLAUDE.md"), type="Project", parent=None), + _Info(str(mem_env[1] / "CLAWCODEX.md"), type="Project", parent=None), + _Info(str(sub / "CLAWCODEX.md"), type="Project", parent=None), ] - monkeypatch.setattr("src.context_system.claude_md.get_memory_files", _files) + monkeypatch.setattr("src.context_system.clawcodex_md.get_memory_files", _files) ui = FakeUIHost(pick=None) await MEMORY_COMMAND.run("", _ctx(deep, ui=ui)) - assert ui.select_calls[0]["values"][1] == str(sub / "CLAUDE.md") + assert ui.select_calls[0]["values"][1] == str(sub / "CLAWCODEX.md") async def test_project_desc_git_branch(mem_env, monkeypatch): @@ -136,7 +136,7 @@ async def test_project_desc_git_branch(mem_env, monkeypatch): monkeypatch.setattr("src.utils.git.get_repo_root", lambda *a, **k: str(cwd)) ui = FakeUIHost(pick=None) await MEMORY_COMMAND.run("", _ctx(cwd, ui=ui)) - assert ui.select_calls[0]["descriptions"][1] == "Checked in at ./CLAUDE.md" + assert ui.select_calls[0]["descriptions"][1] == "Checked in at ./CLAWCODEX.md" async def test_options_extra_files_and_imported_desc(mem_env, monkeypatch): @@ -148,7 +148,7 @@ async def _files(cwd=None, **kwargs): _Info(str(home / "imported.md"), type="User", parent=str(home / "extra.md")), ] - monkeypatch.setattr("src.context_system.claude_md.get_memory_files", _files) + monkeypatch.setattr("src.context_system.clawcodex_md.get_memory_files", _files) ui = FakeUIHost(pick=None) await MEMORY_COMMAND.run("", _ctx(cwd, ui=ui)) call = ui.select_calls[0] @@ -164,19 +164,19 @@ async def _files(cwd=None, **kwargs): # --------------------------------------------------------------------------- # async def test_select_user_memory_creates_dir_and_file(mem_env): home, cwd = mem_env - target = str(home / ".clawcodex" / "CLAUDE.md") + target = str(home / ".clawcodex" / "CLAWCODEX.md") ui = FakeUIHost(pick=target) out = await MEMORY_COMMAND.run("", _ctx(cwd, ui=ui)) assert isinstance(out, InteractiveOutcome) - assert (home / ".clawcodex" / "CLAUDE.md").exists() - assert out.message.startswith("Memory file at ~/.clawcodex/CLAUDE.md. Open it in your editor.") + assert (home / ".clawcodex" / "CLAWCODEX.md").exists() + assert out.message.startswith("Memory file at ~/.clawcodex/CLAWCODEX.md. Open it in your editor.") assert "> To choose an editor, set the $EDITOR or $VISUAL environment variable." in out.message assert out.display == "system" async def test_select_preserves_existing_content(mem_env): home, cwd = mem_env - target = cwd / "CLAUDE.md" + target = cwd / "CLAWCODEX.md" target.write_text("precious") ui = FakeUIHost(pick=str(target)) await MEMORY_COMMAND.run("", _ctx(cwd, ui=ui)) @@ -189,7 +189,7 @@ async def test_select_create_failure(mem_env, monkeypatch): "src.command_system.memory_command._ensure_file", lambda path: (_ for _ in ()).throw(OSError("nope")), ) - ui = FakeUIHost(pick=str(cwd / "CLAUDE.md")) + ui = FakeUIHost(pick=str(cwd / "CLAWCODEX.md")) out = await MEMORY_COMMAND.run("", _ctx(cwd, ui=ui)) assert out.message == "Error opening memory file: nope" assert out.display == "user" @@ -212,7 +212,7 @@ def test_display_path(tmp_path, monkeypatch): monkeypatch.setattr(Path, "home", classmethod(lambda cls: home)) cwd = tmp_path / "w" cwd.mkdir() - assert _display_path(str(cwd / "CLAUDE.md"), str(cwd)) == "CLAUDE.md" + assert _display_path(str(cwd / "CLAWCODEX.md"), str(cwd)) == "CLAWCODEX.md" assert _display_path(str(home / "x.md"), str(cwd)) == "~/x.md" diff --git a/tests/test_plugins_builtin_karpathy.py b/tests/test_plugins_builtin_karpathy.py index b6fe37c67..2ca04da70 100644 --- a/tests/test_plugins_builtin_karpathy.py +++ b/tests/test_plugins_builtin_karpathy.py @@ -35,7 +35,9 @@ def test_prompt_verbatim_pin(): from the TS template literal — pin its size and section headers.""" from src.plugins.karpathy_guidelines import KARPATHY_GUIDELINES_PROMPT as P - assert len(P) == 2351 + # 2351 in the TS original; +3 for the deliberate context-file rebrand + # in the header (# CLAUDE.md -> # CLAWCODEX.md). + assert len(P) == 2354 for header in ( "## 1. Think Before Coding", "## 2. Simplicity First", diff --git a/tests/test_post_compact_attachments.py b/tests/test_post_compact_attachments.py index 4885fa88c..dd7412b60 100644 --- a/tests/test_post_compact_attachments.py +++ b/tests/test_post_compact_attachments.py @@ -36,12 +36,12 @@ def test_plan_file_excluded(self): ) ) - def test_claude_md_excluded(self): + def test_clawcodex_md_excluded(self): self.assertTrue( - _should_exclude_from_post_compact_restore("CLAUDE.md") + _should_exclude_from_post_compact_restore("CLAWCODEX.md") ) self.assertTrue( - _should_exclude_from_post_compact_restore(".claude.md") + _should_exclude_from_post_compact_restore(".clawcodex.md") ) def test_memory_path_excluded(self): diff --git a/tests/test_prompt_assembly.py b/tests/test_prompt_assembly.py index 0461954c1..1bf8f5b5d 100644 --- a/tests/test_prompt_assembly.py +++ b/tests/test_prompt_assembly.py @@ -21,7 +21,7 @@ get_user_context, prepend_user_context, ) -from src.context_system.claude_md import clear_memory_file_caches +from src.context_system.clawcodex_md import clear_memory_file_caches from src.context_system.git_context import clear_git_caches from src.context_system.models import SystemPromptParts from src.types.messages import UserMessage @@ -498,7 +498,7 @@ def test_empty_context(self): result = prepend_user_context(msgs, {}) self.assertEqual(len(result), 1) - def test_with_claude_md(self): + def test_with_clawcodex_md(self): msgs = [UserMessage(content="hi")] result = prepend_user_context(msgs, {"claudeMd": "Always test."}) self.assertEqual(len(result), 2) @@ -543,9 +543,9 @@ def test_memoization(self): result2 = _run(get_user_context()) self.assertEqual(result1, result2) - def test_includes_claude_md_when_present(self): + def test_includes_clawcodex_md_when_present(self): with tempfile.TemporaryDirectory() as tmp: - (Path(tmp) / "CLAUDE.md").write_text("Test rule", encoding="utf-8") + (Path(tmp) / "CLAWCODEX.md").write_text("Test rule", encoding="utf-8") with patch.dict(os.environ, { "CLAUDE_CODE_ORIGINAL_CWD": tmp, "CLAUDE_CODE_DISABLE_CLAUDE_MDS": "", @@ -712,7 +712,7 @@ def test_no_crash(self): class TestUserContextBootstrapCache(unittest.TestCase): """ch03 round-3 G3: get_user_context must populate the bootstrap - cached_claude_md_content (TS context.ts:173-176 — the DAG-leaf cache + cached_clawcodex_md_content (TS context.ts:173-176 — the DAG-leaf cache that breaks the classifier->filesystem->permissions->classifier cycle; consumer unported, cache is forward provisioning).""" @@ -727,30 +727,30 @@ def setUp(self): def test_user_context_populates_cache(self): from unittest import mock - from src.bootstrap.state import get_cached_claude_md_content + from src.bootstrap.state import get_cached_clawcodex_md_content from src.context_system import prompt_assembly as pa fake_files = [object()] with mock.patch.object( pa, "get_memory_files", new=mock.AsyncMock(return_value=fake_files) ), mock.patch.object( - pa, "get_claude_mds", return_value="# Project notes" + pa, "get_clawcodex_mds", return_value="# Project notes" ): asyncio.run(pa.get_user_context()) - self.assertEqual(get_cached_claude_md_content(), "# Project notes") + self.assertEqual(get_cached_clawcodex_md_content(), "# Project notes") - def test_absent_claude_md_caches_none_not_stale(self): + def test_absent_clawcodex_md_caches_none_not_stale(self): from unittest import mock from src.bootstrap.state import ( - get_cached_claude_md_content, - set_cached_claude_md_content, + get_cached_clawcodex_md_content, + set_cached_clawcodex_md_content, ) from src.context_system import prompt_assembly as pa - set_cached_claude_md_content("stale") + set_cached_clawcodex_md_content("stale") with mock.patch.object( pa, "get_memory_files", new=mock.AsyncMock(return_value=[]) - ), mock.patch.object(pa, "get_claude_mds", return_value=""): + ), mock.patch.object(pa, "get_clawcodex_mds", return_value=""): asyncio.run(pa.get_user_context()) - self.assertIsNone(get_cached_claude_md_content()) + self.assertIsNone(get_cached_clawcodex_md_content()) diff --git a/tests/test_ws5_integration.py b/tests/test_ws5_integration.py index 2b743b604..b94785e74 100644 --- a/tests/test_ws5_integration.py +++ b/tests/test_ws5_integration.py @@ -2,7 +2,7 @@ WS-5 Context Management — Integration & Smoke Tests. Exercises the full context pipeline end-to-end: - CLAUDE.md loading → git context → prompt assembly → QueryEngine → API call + CLAWCODEX.md loading → git context → prompt assembly → QueryEngine → API call Uses deterministic FakeProvider — no real API calls. """ @@ -28,9 +28,9 @@ get_user_context, prepend_user_context, ) -from src.context_system.claude_md import ( +from src.context_system.clawcodex_md import ( clear_memory_file_caches, - get_claude_mds, + get_clawcodex_mds, get_memory_files, process_memory_file, ) @@ -84,7 +84,7 @@ class TestWS5SmokeImports(unittest.TestCase): def test_context_system_imports(self): import src.context_system import src.context_system.models - import src.context_system.claude_md + import src.context_system.clawcodex_md import src.context_system.git_context import src.context_system.prompt_assembly import src.context_system.memory_prefetch @@ -101,7 +101,7 @@ def test_public_api_exports(self): prepend_user_context, clear_context_caches, get_memory_files, - get_claude_mds, + get_clawcodex_mds, clear_memory_file_caches, collect_git_context, format_git_status, @@ -123,11 +123,11 @@ class TestWS5SmokeTypes(unittest.TestCase): def test_memory_file_info(self): info = MemoryFileInfo( - path="/project/CLAUDE.md", + path="/project/CLAWCODEX.md", type="Project", content="Rule: always test.", ) - self.assertEqual(info.path, "/project/CLAUDE.md") + self.assertEqual(info.path, "/project/CLAWCODEX.md") self.assertEqual(info.type, "Project") self.assertIsNone(info.parent) self.assertIsNone(info.globs) @@ -143,11 +143,11 @@ def test_system_prompt_parts(self): # --------------------------------------------------------------------------- -# Integration: Full CLAUDE.md → prompt pipeline +# Integration: Full CLAWCODEX.md → prompt pipeline # --------------------------------------------------------------------------- class TestWS5IntegrationClaudeMdPipeline(unittest.TestCase): - """CLAUDE.md files are loaded and formatted into the prompt correctly.""" + """CLAWCODEX.md files are loaded and formatted into the prompt correctly.""" def setUp(self): clear_memory_file_caches() @@ -159,10 +159,10 @@ def tearDown(self): clear_git_caches() clear_context_caches() - def test_project_claude_md_appears_in_user_context(self): - """A CLAUDE.md at workspace root appears in get_user_context().""" + def test_project_clawcodex_md_appears_in_user_context(self): + """A CLAWCODEX.md at workspace root appears in get_user_context().""" with tempfile.TemporaryDirectory() as tmp: - (Path(tmp) / "CLAUDE.md").write_text( + (Path(tmp) / "CLAWCODEX.md").write_text( "Always write tests for every change.", encoding="utf-8", ) with patch.dict(os.environ, { @@ -175,15 +175,15 @@ def test_project_claude_md_appears_in_user_context(self): if "claudeMd" in user_ctx: self.assertIn("Always write tests", user_ctx["claudeMd"]) - def test_multi_level_claude_md_priority(self): + def test_multi_level_clawcodex_md_priority(self): """Files closer to CWD have higher priority (loaded later).""" with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) sub = root / "packages" / "core" sub.mkdir(parents=True) - (root / "CLAUDE.md").write_text("Root rule: use Python 3.12.", encoding="utf-8") - (sub / "CLAUDE.md").write_text("Core rule: prefer async.", encoding="utf-8") + (root / "CLAWCODEX.md").write_text("Root rule: use Python 3.12.", encoding="utf-8") + (sub / "CLAWCODEX.md").write_text("Core rule: prefer async.", encoding="utf-8") with patch.dict(os.environ, {"CLAUDE_CODE_ORIGINAL_CWD": str(sub)}): files = _run(get_memory_files(cwd=str(sub))) @@ -226,7 +226,7 @@ def test_include_chain_integration(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) (root / "shared.md").write_text("Shared config.", encoding="utf-8") - (root / "CLAUDE.md").write_text( + (root / "CLAWCODEX.md").write_text( f"Main rules.\n@{root / 'shared.md'}", encoding="utf-8", ) @@ -236,14 +236,14 @@ def test_include_chain_integration(self): self.assertIn("Main rules", all_content) self.assertIn("Shared config", all_content) - def test_get_claude_mds_formatting(self): - """get_claude_mds() produces formatted prompt text with type descriptions.""" + def test_get_clawcodex_mds_formatting(self): + """get_clawcodex_mds() produces formatted prompt text with type descriptions.""" files = [ - MemoryFileInfo(path="/p/CLAUDE.md", type="Project", content="Rule A"), - MemoryFileInfo(path="/home/.clawcodex/CLAUDE.md", type="User", content="Rule B"), - MemoryFileInfo(path="/p/CLAUDE.local.md", type="Local", content="Rule C"), + MemoryFileInfo(path="/p/CLAWCODEX.md", type="Project", content="Rule A"), + MemoryFileInfo(path="/home/.clawcodex/CLAWCODEX.md", type="User", content="Rule B"), + MemoryFileInfo(path="/p/CLAWCODEX.local.md", type="Local", content="Rule C"), ] - result = get_claude_mds(files) + result = get_clawcodex_mds(files) self.assertIn("Rule A", result) self.assertIn("Rule B", result) self.assertIn("Rule C", result) @@ -321,11 +321,11 @@ def tearDown(self): clear_git_caches() clear_context_caches() - def test_full_pipeline_with_git_and_claude_md(self): - """Full context with git repo + CLAUDE.md produces complete parts.""" + def test_full_pipeline_with_git_and_clawcodex_md(self): + """Full context with git repo + CLAWCODEX.md produces complete parts.""" with tempfile.TemporaryDirectory() as tmp: _init_git_repo(tmp) - (Path(tmp) / "CLAUDE.md").write_text("Integration test rule.", encoding="utf-8") + (Path(tmp) / "CLAWCODEX.md").write_text("Integration test rule.", encoding="utf-8") with patch.dict(os.environ, { "CLAUDE_CODE_ORIGINAL_CWD": tmp, @@ -459,8 +459,8 @@ def test_engine_without_system_prompt_uses_fetch(self): tool_uses=None, ) - # Create CLAUDE.md in workspace - (self.workspace / "CLAUDE.md").write_text( + # Create CLAWCODEX.md in workspace + (self.workspace / "CLAWCODEX.md").write_text( "Test rule: always be helpful.", encoding="utf-8", ) @@ -493,12 +493,12 @@ async def run(): self.assertTrue(provider.chat.called) call_args = provider.chat.call_args messages = call_args.args[0] if call_args.args else call_args.kwargs.get("messages", []) - # Messages should include a system-reminder with CLAUDE.md content + # Messages should include a system-reminder with CLAWCODEX.md content all_content = " ".join( m.get("content", "") if isinstance(m.get("content"), str) else "" for m in messages ) - # The system-reminder with CLAUDE.md should be in the messages + # The system-reminder with CLAWCODEX.md should be in the messages reminder_msgs = [ m for m in messages if m.get("role") == "user" @@ -656,7 +656,7 @@ def test_full_cache_clear_cycle(self): clear_context_caches() with tempfile.TemporaryDirectory() as tmp: - (Path(tmp) / "CLAUDE.md").write_text("Rule v1", encoding="utf-8") + (Path(tmp) / "CLAWCODEX.md").write_text("Rule v1", encoding="utf-8") with patch.dict(os.environ, { "CLAUDE_CODE_ORIGINAL_CWD": tmp, "CLAUDE_CODE_DISABLE_CLAUDE_MDS": "", @@ -669,8 +669,8 @@ def test_full_cache_clear_cycle(self): # Clear all caches clear_context_caches() - # Update CLAUDE.md - (Path(tmp) / "CLAUDE.md").write_text("Rule v2", encoding="utf-8") + # Update CLAWCODEX.md + (Path(tmp) / "CLAWCODEX.md").write_text("Rule v2", encoding="utf-8") # Second fetch should see updated content parts2 = _run(fetch_system_prompt_parts(cwd=tmp)) @@ -717,11 +717,11 @@ def tearDown(self): clear_git_caches() clear_context_caches() - def test_build_context_prompt_with_claude_md(self): + def test_build_context_prompt_with_clawcodex_md(self): """Legacy API produces prompt with runtime context + instructions.""" with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) - (root / "CLAUDE.md").write_text("Legacy compat rule.", encoding="utf-8") + (root / "CLAWCODEX.md").write_text("Legacy compat rule.", encoding="utf-8") (root / "README.md").write_text("# Test\n", encoding="utf-8") (root / "src").mkdir() (root / "src" / "app.py").write_text("pass\n", encoding="utf-8") @@ -750,7 +750,7 @@ def test_build_context_prompt_with_git(self): class TestWS5SmokeEndToEnd(unittest.TestCase): """ - End-to-end smoke: workspace with git + CLAUDE.md → QueryEngine → response. + End-to-end smoke: workspace with git + CLAWCODEX.md → QueryEngine → response. Verifies the full chain works without errors. """ @@ -765,10 +765,10 @@ def tearDown(self): clear_context_caches() def test_full_end_to_end(self): - """Complete pipeline: git repo + CLAUDE.md + QueryEngine → assistant response.""" + """Complete pipeline: git repo + CLAWCODEX.md + QueryEngine → assistant response.""" with tempfile.TemporaryDirectory() as tmp: _init_git_repo(tmp) - (Path(tmp) / "CLAUDE.md").write_text( + (Path(tmp) / "CLAWCODEX.md").write_text( "E2E rule: always explain your reasoning.", encoding="utf-8", ) workspace = Path(tmp) diff --git a/tests/utils/test_legacy_migration.py b/tests/utils/test_legacy_migration.py index 69f6b25e1..b35ddeedb 100644 --- a/tests/utils/test_legacy_migration.py +++ b/tests/utils/test_legacy_migration.py @@ -77,6 +77,8 @@ def test_user_migration_copies_and_preserves_source(fake_home): (legacy / "skills" / "greet" / "SKILL.md").write_text("hello") (legacy / "agents").mkdir() (legacy / "agents" / "critic.md").write_text("agent") + # The legacy harness's real file name — migration renames it to the + # canonical CLAWCODEX.md at the destination (_MIGRATE_RENAMES). (legacy / "CLAUDE.md").write_text("global memory") mem = legacy / "projects" / "-repo-a" / "memory" mem.mkdir(parents=True) @@ -89,7 +91,7 @@ def test_user_migration_copies_and_preserves_source(fake_home): assert report is not None assert sorted(report.copied) == [ - "CLAUDE.md", + "CLAWCODEX.md", "agents", "projects/-repo-a/memory", "skills/greet", @@ -99,7 +101,7 @@ def test_user_migration_copies_and_preserves_source(fake_home): dst = fake_home / ".clawcodex" assert (dst / "skills" / "greet" / "SKILL.md").read_text() == "hello" assert (dst / "agents" / "critic.md").read_text() == "agent" - assert (dst / "CLAUDE.md").read_text() == "global memory" + assert (dst / "CLAWCODEX.md").read_text() == "global memory" assert (dst / "projects" / "-repo-a" / "memory" / "MEMORY.md").read_text() == "index" assert not (dst / "projects" / "-repo-a" / "session.jsonl").exists() diff --git a/ui-tui/src/gatewayTypes.ts b/ui-tui/src/gatewayTypes.ts index ae138dea7..5ee5af0a2 100644 --- a/ui-tui/src/gatewayTypes.ts +++ b/ui-tui/src/gatewayTypes.ts @@ -75,7 +75,7 @@ export interface SlashExecResponse { // ── /memory picker (memory_targets control) ───────────────────────── export interface MemoryTarget { - /** Secondary dim text — e.g. "Saved in ~/.clawcodex/CLAUDE.md", "@-imported". */ + /** Secondary dim text — e.g. "Saved in ~/.clawcodex/CLAWCODEX.md", "@-imported". */ description?: string label: string path: string diff --git a/ui-tui/src/lib/memoryEdit.test.ts b/ui-tui/src/lib/memoryEdit.test.ts index 7470423e9..9c6b93d4f 100644 --- a/ui-tui/src/lib/memoryEdit.test.ts +++ b/ui-tui/src/lib/memoryEdit.test.ts @@ -20,7 +20,7 @@ describe('ensureMemoryFile', () => { }) it('creates the config home and file for the user memory target', () => { - const path = join(home, '.clawcodex', 'CLAUDE.md') + const path = join(home, '.clawcodex', 'CLAWCODEX.md') ensureMemoryFile(path, home) @@ -28,7 +28,7 @@ describe('ensureMemoryFile', () => { }) it('preserves existing content (exclusive create swallows EEXIST)', () => { - const path = join(home, 'CLAUDE.md') + const path = join(home, 'CLAWCODEX.md') writeFileSync(path, 'precious') ensureMemoryFile(path, home) @@ -37,7 +37,7 @@ describe('ensureMemoryFile', () => { }) it('propagates non-EEXIST errors (missing dir outside the config home)', () => { - expect(() => ensureMemoryFile(join(home, 'nope', 'CLAUDE.md'), home)).toThrow() + expect(() => ensureMemoryFile(join(home, 'nope', 'CLAWCODEX.md'), home)).toThrow() }) }) @@ -63,14 +63,14 @@ describe('editorHint', () => { describe('relativeMemoryPath', () => { it('prefers the shorter of ~ and ./ spellings', () => { - expect(relativeMemoryPath('/home/u/w/CLAUDE.md', '/home/u/w', '/home/u')).toBe('./CLAUDE.md') - expect(relativeMemoryPath('/home/u/.clawcodex/CLAUDE.md', '/home/u/deep/nested/dir', '/home/u')).toBe( - '~/.clawcodex/CLAUDE.md' + expect(relativeMemoryPath('/home/u/w/CLAWCODEX.md', '/home/u/w', '/home/u')).toBe('./CLAWCODEX.md') + expect(relativeMemoryPath('/home/u/.clawcodex/CLAWCODEX.md', '/home/u/deep/nested/dir', '/home/u')).toBe( + '~/.clawcodex/CLAWCODEX.md' ) }) it('falls back to the absolute path outside home and cwd', () => { - expect(relativeMemoryPath('/etc/CLAUDE.md', '/home/u/w', '/home/u')).toBe('/etc/CLAUDE.md') + expect(relativeMemoryPath('/etc/CLAWCODEX.md', '/home/u/w', '/home/u')).toBe('/etc/CLAWCODEX.md') }) }) @@ -129,7 +129,7 @@ describe('openMemoryFileInEditor', () => { it('creates the file, spawns inside the suspend, notifies, and reports', async () => { const d = deps() - const path = join(cwd, 'CLAUDE.md') + const path = join(cwd, 'CLAWCODEX.md') await openMemoryFileInEditor(path, d) @@ -139,14 +139,14 @@ describe('openMemoryFileInEditor', () => { `spawn:vim ${path}`, 'suspend:exit', 'notify', - `sys:Opened memory file at ./CLAUDE.md\n\n${editorHint({ EDITOR: 'vim' })}` + `sys:Opened memory file at ./CLAWCODEX.md\n\n${editorHint({ EDITOR: 'vim' })}` ]) }) it('reports a launch failure and skips the edited notification', async () => { const d = deps({ spawn: vi.fn(() => ({ error: new Error('ENOENT') })) }) - await openMemoryFileInEditor(join(cwd, 'CLAUDE.md'), d) + await openMemoryFileInEditor(join(cwd, 'CLAWCODEX.md'), d) expect(d.notifyEdited).not.toHaveBeenCalled() expect(d.sys).toHaveBeenCalledWith('Error opening memory file: Error: ENOENT') @@ -155,7 +155,7 @@ describe('openMemoryFileInEditor', () => { it('reports ensure-create failures without spawning', async () => { const d = deps() - await openMemoryFileInEditor(join(cwd, 'missing-dir', 'CLAUDE.md'), d) + await openMemoryFileInEditor(join(cwd, 'missing-dir', 'CLAWCODEX.md'), d) expect(d.spawn).not.toHaveBeenCalled() expect(d.sys.mock.calls[0]?.[0]).toMatch(/^Error opening memory file: /)