feat(memory): load .qwen/QWEN.local.md as project-local context (#4091) - #4394
Conversation
📋 Review SummaryThis PR implements a per-developer, project-scoped context file slot at 🔍 General Feedback
🎯 Specific Feedback🔵 Low
✅ Highlights
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Self-review pass before requesting review surfaced one defect, fixed in 97c6fb4:
The fix is two parts:
Three new regression tests pin the new behavior: Full suite: 27/27 in |
wenshao
left a comment
There was a problem hiding this comment.
Cross-file consistency note: findProjectRoot in memoryImportProcessor.ts:42-64 was not updated for .git file support (worktrees/submodules). The PR correctly fixes the copy in memoryDiscovery.ts, but the duplicate in memoryImportProcessor.ts still only checks stats.isDirectory(). In a git worktree, @import directives inside QWEN.local.md (or any QWEN.md) would resolve against the wrong project root — the file loads correctly but its imports silently break. Consider extracting findProjectRoot into a shared utility (e.g., utils/projectRoot.ts) to eliminate both the duplication and the divergence.
— qwen-latest-series-invite-beta-v34 via Qwen Code /review
Adds a per-developer, project-scoped context file slot at `<projectRoot>/.qwen/QWEN.local.md`. Loaded after all hierarchical QWEN.md / AGENTS.md files so local instructions can supplement or override shared ones. Use case: project-specific but personal instructions (local cluster IDs, container registry namespaces, accounts) that shouldn't live in the shared root `QWEN.md` (exposes them to the team) or in the global `~/.qwen/QWEN.md` (applies to every project). Mirrors Claude Code's `.claude/CLAUDE.local.md` convention. The slot is single and fixed (project root only — not searched in CWD subdirectories or via upward traversal), gated by the same trust and explicit-only checks as the rest of project-level discovery, and counted in `fileCount` so the `/memory` panel surfaces it. Users must gitignore the file themselves; `.qwen/` is not auto-ignored and `.qwen/settings.json` is commonly committed.
`findProjectRoot()` only accepted `.git` as a directory, so in git
worktrees and submodules (where `.git` is a file containing a `gitdir:`
pointer) it returned `null`. The new `.qwen/QWEN.local.md` slot then
fell back to `<cwd>/.qwen/QWEN.local.md`, silently breaking the
documented "single fixed slot at project root" behavior for users
inside worktrees — including the developer of this feature.
Two changes:
1. `findProjectRoot()` now accepts `.git` as either a directory or a
regular file. This also incidentally repairs pre-existing breakage
in `rulesDiscovery` / hierarchical-search stop boundary, both of
which consume the same helper.
2. The local-context-file slot now requires a real `foundRoot` (the
`null` case is no longer covered by the `effectiveRoot` fallback).
Without this guard:
- a deep cwd in a non-git workspace turned the slot into a
per-cwd file, opposite the design;
- `cwd === homedir` resolved the slot to `~/.qwen/QWEN.local.md`,
colliding with the global Qwen directory.
Three regression tests pin the new behavior: `.git`-as-file is
recognized, no-`.git`-ancestor skips the slot, `cwd === homedir`
without `.git` does not promote a global file to project-local.
Two duplicate `findProjectRoot` helpers existed in `packages/core/src/utils/`: one in `memoryDiscovery.ts` (returns `Promise<string | null>`) and one in `memoryImportProcessor.ts` (returns `Promise<string>`, falls back to startDir). The previous fix in 97c6fb4 only updated the first copy for `.git`-file support, so `@import` resolution under git worktrees and submodules was still silently broken — the QWEN.local.md file would load, but its imports would resolve against the wrong root. Extract the helper into `utils/projectRoot.ts`, with the unified nullable return type. Rewire both call sites; `memoryImportProcessor` preserves its previous fallback semantics at the call site (`?? path.resolve(basePath)`). Adds 5 unit tests for the utility (directory / file / null / deep / symlink) and 1 test for the previously-unverified dedup guard in `memoryDiscovery.ts` (exercised via `extensionContextFilePaths`). Addresses inline + cross-file findings from wenshao on PR #4394.
97c6fb4 to
ff7eac9
Compare
|
@wenshao Both findings addressed in ff7eac9 (refactor commit, rebased onto current main). Cross-file consistency note ( Dedup guard (inline at Validation:
Branch is rebased onto current main; commits on this branch are now 4890f4e (feat), 9a914c4 (fix), ff7eac9 (refactor). |
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ Both round 1 findings have been properly addressed in the refactor commit. The findProjectRoot extraction into a shared helper with .git file support is clean, well-tested, and fixes a real bug for worktree/submodule users. Test coverage is thorough (28 unit + 5 projectRoot + 111 memory suite = 144 tests all passing). — qwen3.7-max via Qwen Code /review
Verification:
|
pomelo-nwu
left a comment
There was a problem hiding this comment.
LGTM. Add .qwen/QWEN.local.md as a developer-local project context file loaded after all QWEN.md/AGENTS.md layers. Also fixes findProjectRoot() not recognizing .git files in worktree/submodule scenarios.
Summary
<projectRoot>/.qwen/QWEN.local.md. Loaded after all hierarchicalQWEN.md/AGENTS.mdfiles so local instructions can supplement or override shared ones.QWEN.mdexposes them to the team;~/.qwen/QWEN.mdpollutes every other project. Mirrors Claude Code's.claude/CLAUDE.local.mdconvention.memoryDiscovery.tsfileCountincludesQWEN.local.mdso the/memorypanel surfaces it.qwen/is not auto-ignored and.qwen/settings.jsonis commonly committed)Validation
memoryDiscovery.test.ts: 24/24 pass (16 existing + 8 new covering load / order / missing / untrusted / explicit-only / subdir-isolation / standalone)npm run typecheck: exit 0 across all workspacesnpx vitest run packages/core/src/utils/memoryDiscovery.test.ts— should report 24/24describe('QWEN.local.md (project-local context file)', ...)block inmemoryDiscovery.test.tsfor the behavior specScope / Risk
fs.access()stat perloadServerHierarchicalMemory()invocation. Negligible — single stat, same magnitude as the existing global / home / upward probes.loadServerHierarchicalMemory's public contract. The function is the single entry point used by bothpackages/cli/src/config/config.tsandpackages/core/src/config/config.ts, so unit coverage flows directly to runtime..qwen/settings.local.jsonis intentionally out of scope for this PR — settings merging is structured JSON deep-merge, mechanically different from memory string concatenation. Should be a separate issue/PR to keep this change small and focused.Testing Matrix
Testing matrix notes:
Linked Issues / Bugs
Closes #4091