feat(server): read Claude Code conventions behind a compatibility setting - #304
Open
nfertigsw wants to merge 1 commit into
Open
feat(server): read Claude Code conventions behind a compatibility setting#304nfertigsw wants to merge 1 commit into
nfertigsw wants to merge 1 commit into
Conversation
…ting OpenFox only read CLAUDE.md as a plain instruction file. Projects coming from Claude Code keep their skills in `.claude/skills`, their memory in `.claude/CLAUDE.md` and `~/.claude/CLAUDE.md`, and compose memory files with `@file` imports — none of which were picked up. - Discover skills from `~/.claude/skills` and `<project>/.claude/skills` (sources `global-claude` / `project-claude`). The portable SKILL.md format was already compatible; only the paths were missing. OpenFox skills still win on id collisions. - Read `~/.claude/CLAUDE.md` (user memory, lowest priority) and `<dir>/.claude/CLAUDE.md` at every level of the instruction tree walk. - Expand `@file` imports inside instruction files: resolved against the importing file, `~/` expanded, depth capped at 5, each file inlined once so cycles terminate, skipped inside code fences and code spans, and left untouched when the path does not resolve (so `@mention` and e-mail addresses survive). - Deduplicate instruction files by content hash. Repositories commonly ship AGENTS.md and CLAUDE.md as copies or symlinks of one another, which was sending the same text to the model twice. This one is unconditional — it is a correctness fix, not a compatibility behaviour. All of the above except the deduplication sits behind the tri-state `compat.claudeCode` setting (Settings -> Advanced): `auto` (default) turns it on when the project holds a `.claude/` directory or a `CLAUDE.md`, `true` forces it on, `false` off. Overlap diagnostics now only fire when a user-picked directory overlaps another root; two automatic roots symlinked into each other (a common `.claude/skills` -> `.agents/skills` setup) is not actionable and was pure noise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nfertigsw
force-pushed
the
feat/claude-code-compat
branch
from
August 31, 2026 21:05
c6f3030 to
ea882ce
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OpenFox reads
CLAUDE.mdas one of its instruction filenames, which looks like Claude Code support but stops well short of it. Claude Code keeps skills in.claude/skills, memory in.claude/CLAUDE.mdand~/.claude/CLAUDE.md, and composes memory files with@fileimports. None of that was visible, so someone arriving from Claude Code silently lost most of their setup.Measured on a real machine before this PR:
~/.claude/skills, only the 8 that happened to be symlinks into~/.agents/skillswere discovered. The 6 real directories were invisible.~/.claude/CLAUDE.mdwas never read — and its entire content was@RTK.md, so even reading it would have contributed nothing without import expansion.Full design rationale, precedence tables and edge cases: docs/DESIGN-CLAUDE-CODE-COMPAT.md (added by this PR).
The setting
compat.claudeCode, tri-state, in Settings → Advanced (sits between "Speculative Cache Warming" and "Network"):auto(default).claude/directory or aCLAUDE.mdtruefalseTri-state rather than a toggle because a boolean cannot express "decide per project" — which is the behaviour that makes the feature invisible when it should be. A project using
AGENTS.mdis untouched and nothing new enters its prompt.What it turns on
~/.claude/skillsand<project>/.claude/skills, as new sourcesglobal-claude/project-claude. No parser work was needed —loadPortableSkillsalready reads<dir>/SKILL.mdwithname/descriptionfrontmatter, which is exactly Claude Code's format; only the paths were missing. Each Claude root sits just after its.agentscounterpart and before OpenFox's own, so an OpenFox skill always wins an id collision.~/.claude/CLAUDE.mdprepended before the tree walk (so user-level memory has the lowest priority and any project file overrides it) and<dir>/.claude/CLAUDE.mdat each level of the existing walk.@fileimports inside instruction files: resolved against the importing file,~/expanded, depth capped at 5, each path canonicalised throughrealpathand inlined once so cycles terminate, skipped inside fenced blocks and inline code spans, and left exactly as written when the path does not resolve — so@mentionanduser@example.comsurvive untouched.Always on, regardless of the setting
Instruction files are deduplicated by content hash (SHA-256 over trimmed content). Repositories very commonly ship
AGENTS.mdandCLAUDE.mdas copies or symlinks of one another, and both were being sent to the model. That is a correctness bug predating this feature and unrelated to Claude Code, so it is not gated.loadInstructionFilesnow reads each file once and returns its content, sogetAllInstructionsno longer re-reads them and its injected-file list reports only what was actually injected.One drive-by
The "reached through multiple paths" skill diagnostic now fires only when a directory the user picked (
selected) overlaps another root. Sharing one library between both tools by symlinking.claude/skills→.agents/skillsis common, and it produced one diagnostic line per skill that named a real overlap the user cannot act on — 8 lines of noise on the machine above. The existing test covering a user-selected symlinked directory still passes unchanged.Deliberately out of scope
Still invisible to OpenFox, and called out as such in the design doc:
.claude/agents/*.md,.claude/commands/*.md,.mcp.json,.claude/settings.json, and plugin skills under~/.claude/plugins.What to test
AGENTS.md, leave the setting onauto. Settings → Skills shows the same list as before; the prompt gains nothing. (OpenFox's own repo is such a project — it has no.claude/and noCLAUDE.md.).claude/directory or aCLAUDE.md, still onauto. Skills from.claude/skillsnow appear under Shared in the Skills modal, and~/.claude/CLAUDE.mdshows up among the injected instruction files.~/.claude/skillsentries appear on any project. Set "Always off" on a Claude Code project and they disappear again.@some-file.mdon its own line in aCLAUDE.mdand confirm the file's content is inlined, prefixed withInstructions from: <path>. Then check that@notafile, an e-mail address, and an@importwritten inside a fenced code block are all left alone.AGENTS.mdandCLAUDE.mdat a project root — the text should reach the model once, not twice, whatever the setting is..claude/skillsand.openfox/skills; the OpenFox one must win.Tests
25 added, covering detection and all three tri-state modes, both
.claudememory locations, import resolution / cycles / depth / code-span exclusion, content deduplication, and.claudeskill discovery with precedence and diagnostics. Existing instruction tests now pin the compat flag off so the suite never reaches into the developer's own~/.claude.Local run on WSL:
npm run typecheck✅,eslint src/ web/src/✅ (0 issues),npm run duplicate✅ (0 clones),prettier --check✅, e2e suite ✅ (335 passed), unit suite 4992 passed with one failure —inspect-proxy.test.ts > handles unreachable target gracefully. That test proxies tohttp://127.0.0.1:1and expects a refused connection; on this machine the connect hangs instead (verified independently: a raw TCP connect to port 1 times out rather than being refused), so the proxy never answers and the test hits its 15s cap. It fails identically on a clean checkout without this branch. CI on ubuntu-latest should be unaffected.AI-Enhanced Development
Tell what models helped shape this PR:
Cache Impact
Does this PR affect anything cached — system prompts, tool definitions, skills, or other context?
~/.claude/CLAUDE.mdplus any inlined@fileimports and loses duplicatedAGENTS.md/CLAUDE.mdtext, and the skill metadata list can gain.claudeskills. Both already feedcomputeDynamicContextHash(instruction content + sorted skill ids), so flipping the setting or opening a Claude Code project invalidates the prompt cache through the existing mechanism — no new cache key was needed. With the defaultautoon a project without.claude/orCLAUDE.md, the only reachable change is the deduplication, which fires solely when two instruction files carry byte-identical content.