Skip to content

fix(core): share memory filename config state#5419

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/share-memory-config-state
Jun 19, 2026
Merged

fix(core): share memory filename config state#5419
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/share-memory-config-state

Conversation

@tt-a1i

@tt-a1i tt-a1i commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make tools/memory-config.ts re-export the memory filename config from memory/const.ts
  • add regression coverage that the legacy tools entrypoint and memory entrypoint share the same state

Part of #4063.

Test plan

  • npx -p node@22 node node_modules/vitest/vitest.mjs run --coverage.enabled=false packages/core/src/memory/const.test.ts packages/core/src/memory/writeContextFile.test.ts packages/core/src/config/config.test.ts
  • npx prettier --check packages/core/src/tools/memory-config.ts packages/core/src/memory/const.test.ts
  • npx eslint packages/core/src/tools/memory-config.ts packages/core/src/memory/const.test.ts
  • git diff --check

AI Assistance Disclosure

I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.

@tt-a1i
tt-a1i marked this pull request as ready for review June 19, 2026 15:51
@wenshao

wenshao commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @tt-a1i!

Template: headings don't match .github/pull_request_template.md exactly — "Summary" / "Test plan" instead of "What this PR does" / "Why it's needed" / "Reviewer Test Plan", and the "Risk & Scope" / "Linked Issues" sections are missing. Content is all there though, so not blocking — but please use the template headings on future PRs so reviewers can scan quickly.

Direction: This is a textbook DRY fix. tools/memory-config.ts and memory/const.ts were independently holding the same module-level state (currentGeminiMdFilename), so callers importing from different paths could silently diverge. Re-exporting from memory/const.ts is the right call — single source of truth, no behavior change. Aligned with #4063.

Approach: Scope is minimal — one file reduced to re-exports, one test added for the shared-state invariant. Nothing to cut. The beforeEach reset in the test suite is a good hygiene addition since test order would otherwise matter.

Moving on to code review and testing. 🔍

中文说明

感谢 @tt-a1i 的贡献!

模板: 标题与 .github/pull_request_template.md 不完全一致——用了 "Summary" / "Test plan" 而非模板要求的 "What this PR does" / "Why it's needed" / "Reviewer Test Plan",且缺少 "Risk & Scope" 和 "Linked Issues" 章节。不过内容齐全,不阻塞——后续 PR 请使用模板标题以便快速浏览。

方向: 经典的 DRY 修复。tools/memory-config.tsmemory/const.ts 各自独立维护相同的模块级状态(currentGeminiMdFilename),不同路径的调用者可能静默产生分歧。改为从 memory/const.ts 重新导出是正确的——单一事实来源,无行为变更。与 #4063 一致。

方案: 范围最小化——一个文件精简为重新导出,一个测试验证共享状态不变量。没有可砍的部分。测试中的 beforeEach 重置是好的实践,避免测试顺序依赖。

进入代码审查和测试 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal before reading the diff: I'd replace the duplicated state in tools/memory-config.ts with re-exports from memory/const.ts, and add a test that imports from both paths and asserts shared state. Exactly what the PR does.

Reviewing the diff against this baseline:

  • Correctness ✓ — all 5 previously-exported symbols (DEFAULT_CONTEXT_FILENAME, AGENT_CONTEXT_FILENAME, MEMORY_SECTION_HEADER, setGeminiMdFilename, getCurrentGeminiMdFilename, getAllGeminiMdFilenames) are re-exported from memory/const.ts. No API breakage for existing importers.
  • No circular dependencytools/memory-config.ts imports from ../memory/const.js; memory/const.ts has no import from tools/.
  • Behavioral note: memory/const.ts's getCurrentGeminiMdFilename() has more robust empty-entry filtering than the old tools/memory-config.ts version (falls back to DEFAULT_CONTEXT_FILENAME when all entries are whitespace). This is a strict improvement — callers who previously got '' now get 'QWEN.md'.
  • Test quality ✓ — the shared-state test verifies bidirectional reads/writes through both entry points and the beforeEach ensures isolation from the array test above.
  • Prettier ✓, ESLint ✓, TypeScript build ✓ (no new warnings).

No blockers found.

Test Results

Non-user-visible refactoring — verified via unit tests (250 tests across all related test files):

$ cd packages/core && npx vitest run src/memory/const.test.ts src/memory/writeContextFile.test.ts src/config/config.test.ts

 RUN  v3.2.4 /home/runner/work/qwen-code/qwen-code/packages/core
      Coverage enabled with v8

 ✓ src/memory/const.test.ts (4 tests) 7ms
 ✓ src/memory/writeContextFile.test.ts (16 tests) 50ms
 ✓ src/config/config.test.ts (230 tests) 371ms

 Test Files  3 passed (3)
      Tests  250 passed (250)
   Start at  17:08:36
   Duration  7.14s (transform 2.49s, setup 82ms, collect 4.33s, tests 428ms, environment 1ms, prepare 399ms)
中文说明

代码审查

读 diff 前我的独立方案:将 tools/memory-config.ts 的重复状态替换为从 memory/const.ts 重新导出,并添加一个测试从两条路径导入并断言共享状态。PR 的做法完全一致。

  • 正确性 ✓ — 全部 5 个已导出符号均从 memory/const.ts 重新导出,不破坏现有调用方 API。
  • 无循环依赖tools/memory-config.ts 导入 ../memory/const.jsmemory/const.ts 不反向导入 tools/
  • 行为改进: memory/const.tsgetCurrentGeminiMdFilename() 有更健壮的空条目过滤(全空白时回退到 DEFAULT_CONTEXT_FILENAME),比旧版严格提升。
  • 测试质量 ✓ — 共享状态测试验证双向读写,beforeEach 确保测试隔离。
  • Prettier ✓,ESLint ✓,TypeScript 构建 ✓(无新警告)。

无阻塞问题。

测试结果

非用户可见的重构——通过单元测试验证(3 个相关测试文件共 250 个测试全部通过)。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

This is a clean, focused PR that does exactly one thing and does it well.

My independent proposal (replace duplicated state with re-exports, add a shared-state test) matches the PR's approach — which means the author found the obvious right answer, not a clever workaround. That's the best signal you can get.

The behavioral improvement in getCurrentGeminiMdFilename() (whitespace-entry filtering from memory/const.ts replacing the simpler tools/memory-config.ts version) is a free correctness win that probably prevents a real edge case someone would have hit eventually.

All 250 related tests pass, build is clean, linting is clean. The template heading mismatch is minor and doesn't affect review quality.

Approving. ✅

中文说明

这是一个干净、聚焦的 PR,只做一件事并且做得很好。

我的独立方案(将重复状态替换为重新导出,添加共享状态测试)与 PR 方案一致——说明作者找到了显而易见的正确答案,而非一个取巧的方案。这是最好的信号。

getCurrentGeminiMdFilename() 的行为改进(来自 memory/const.ts 的空白条目过滤替换了较简单的 tools/memory-config.ts 版本)是一个免费的正确性提升,可能防止了将来某人会遇到的边界情况。

250 个相关测试全部通过,构建干净,lint 干净。模板标题不匹配是次要问题,不影响审查质量。

批准 ✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

✅ Verification report — recommend merge

I built a real, runtime verification of this PR (esbuild-transpiled module A/B + a tmux real-process boot + the PR's own unit/lint suite), not just a diff read. Verdict: correct, low-risk de-duplication fix. The divergent-state bug is real at the module level; it is currently latent in production but tools/memory-config.ts is by-design a future import target, so this is sound preventive hardening with a genuine regression test.

TL;DR

Check Result
Module-state A/B (memory/consttools/memory-config) BEFORE: DIVERGED ✘ (5/5 cross-module checks fail) · AFTER: SHARED ✔
tmux real-process load through package barrel (export *) No circular-import crash; BEFORE diverged / AFTER shared
memory/const.test.ts (file the PR edits) 4/4 ✓ incl. new shares filename state with the legacy tools memory config entrypoint
memory/writeContextFile.test.ts 16/16 ✓
prettier --check / eslint on changed files clean ✓ / clean ✓
Export-surface parity exactly the original 6 symbols re-exported; no LOCAL_CONTEXT_FILENAME leak
config/config.test.ts could not load in my sandbox — pre-existing undici resolution failure that reproduces identically on main; PR touches neither config.ts nor config.test.ts, so no signal either way

What the bug was

memory/const.ts and tools/memory-config.ts each declared their own let currentGeminiMdFilename plus setter/getters. They were independent module-level state — mutating one never affected the other.

Evidence

1. Module-state A/B. I transpiled both branches' modules with esbuild into a tree where memory/const.js is byte-identical and only tools/memory-config.js differs, then drove the real production mutation (config.ts:1393 calls setGeminiMdFilename imported from ../memory/const.js) and read back through the lightweight entrypoint:

BEFORE (main, duplicate module)              AFTER (PR #5419, re-export)
  set 'CUSTOM_CONTEXT.md' via const setter     set 'CUSTOM_CONTEXT.md' via const setter
  tool.getCurrent() -> "QWEN.md"        FAIL    tool.getCurrent() -> "CUSTOM_CONTEXT.md"  PASS
  tool.getAll()     -> ["QWEN.md","AGENTS.md"]  tool.getAll()     -> ["CUSTOM_CONTEXT.md"] PASS
  (array case + reverse direction also FAIL)    (all PASS, both directions)
  => STATE DIVERGED ✘                           => STATE SHARED ✔

2. tmux real-process boot. Mirroring the real public surface (index.ts:191export * from './memory/const.js'), I loaded barrel + const + re-export in one Node process and mutated via the public API:

[before] loaded barrel+const+re-export with NO circular-import crash
  public setGeminiMdFilename('BARREL_SET.md') -> tool.getAll() = ["QWEN.md","AGENTS.md"]  => DIVERGED ✘
[after]  loaded barrel+const+re-export with NO circular-import crash
  public setGeminiMdFilename('BARREL_SET.md') -> tool.getAll() = ["BARREL_SET.md"]        => SHARED ✔

This also confirms the re-export introduces no circular import and the ESM live-binding works through the barrel — the only real structural risk of a re-export-only module.

Reachability — honest caveat for the merge decision

The divergence is real at the module level but latent in production today. No source file imports tools/memory-config.ts; the only reference anywhere is a now-vestigial vi.mock('../tools/memory-config', …) at config/config.test.ts:221 (the live mock is the ../memory/const.js one at line 215). Both the mutation path (config.ts:1393) and the package's public surface (index.ts:191) already resolve to memory/const.ts, so there is no user-facing bug right now.

It still matters: tools/memory-config.ts exists specifically as the lightweight entrypoint ("to avoid loading the full tool module when only the filename configuration is needed"). Any future code that imports it to read the configured context filename would silently get the stale QWEN.md/AGENTS.md default after a user set contextFileName. This PR removes that trap by collapsing to one source of truth, and the export surface is preserved exactly (the 6 original symbols; LOCAL_CONTEXT_FILENAME is intentionally not re-exported), so it is backward-compatible.

Optional non-blocking follow-up

vi.mock('../tools/memory-config', …) at packages/core/src/config/config.test.ts:221 is now dead (nothing under test imports that module). Removing it in a follow-up would fully close the duplication debt this PR targets.

🇨🇳 中文版(点击展开)

✅ 验证报告 — 建议合并

我对本 PR 做了真实的运行时验证(esbuild 转译后的模块 A/B + tmux 真实进程启动 + PR 自带的单测/lint),不是只读 diff。结论:这是一个正确、低风险的去重修复。状态分裂的 bug 在模块层面是真实存在的;目前在生产中是“潜伏”的,但 tools/memory-config.ts 本就是为未来被 import 而设计的轻量入口,因此这是合理的预防性加固,且带了一个真正有效的回归测试。

速览

检查项 结果
模块状态 A/B(memory/consttools/memory-config 修复前:分裂 ✘(5/5 跨模块断言失败) · 修复后:共享 ✔
tmux 真实进程经包 barrel(export *)加载 无循环依赖崩溃;修复前分裂 / 修复后共享
memory/const.test.ts(PR 改动的文件) 4/4 ✓,含新增的 shares filename state with the legacy tools memory config entrypoint
memory/writeContextFile.test.ts 16/16 ✓
改动文件的 prettier --check / eslint 干净 ✓ / 干净 ✓
导出面一致性 恰好重导出原有 6 个符号;未泄漏 LOCAL_CONTEXT_FILENAME
config/config.test.ts 在我的沙箱里无法加载 —— 这是 undici 模块解析的既有问题,main 上完全一致地复现;且本 PR 既不改 config.ts 也不改 config.test.ts,因此对本 PR 无任何信号

Bug 是什么

memory/const.tstools/memory-config.ts 各自都声明了一份 let currentGeminiMdFilename 以及对应的 setter/getter。它们是互相独立的模块级状态 —— 改其中一个永远不会影响另一个。

证据

1. 模块状态 A/B。 我用 esbuild 把两个分支的模块转译到同一个目录树里:memory/const.js 逐字节相同,只有 tools/memory-config.js 不同;然后驱动真实的生产改写路径(config.ts:1393 调用从 ../memory/const.js 导入的 setGeminiMdFilename),再从轻量入口读回:

修复前(main,重复模块)                       修复后(PR #5419,重导出)
  经 const setter 设置 'CUSTOM_CONTEXT.md'      经 const setter 设置 'CUSTOM_CONTEXT.md'
  tool.getCurrent() -> "QWEN.md"        失败     tool.getCurrent() -> "CUSTOM_CONTEXT.md"  通过
  tool.getAll()     -> ["QWEN.md","AGENTS.md"]   tool.getAll()     -> ["CUSTOM_CONTEXT.md"] 通过
  (数组写入 + 反向写入同样失败)                (全部通过,双向都通过)
  => 状态分裂 ✘                                  => 状态共享 ✔

2. tmux 真实进程启动。 复刻真实的公开导出面(index.ts:191export * from './memory/const.js'),我在同一个 Node 进程里加载 barrel + const + 重导出,并经公开 API 改写:

[before] loaded barrel+const+re-export with NO circular-import crash
  public setGeminiMdFilename('BARREL_SET.md') -> tool.getAll() = ["QWEN.md","AGENTS.md"]  => DIVERGED ✘
[after]  loaded barrel+const+re-export with NO circular-import crash
  public setGeminiMdFilename('BARREL_SET.md') -> tool.getAll() = ["BARREL_SET.md"]        => SHARED ✔

这同时证明该重导出不引入循环依赖,且 ESM live-binding 经 barrel 正常工作 —— 这正是“纯重导出模块”唯一真正的结构性风险点。

可达性 —— 给合并决策的诚实提示

这个分裂在模块层面是真实的,但目前在生产中是潜伏的。没有任何源码文件 import tools/memory-config.ts;全代码库唯一的引用是 config/config.test.ts:221 处一个现已失效的 vi.mock('../tools/memory-config', …)(真正生效的 mock 是第 215 行那个 ../memory/const.js)。改写路径(config.ts:1393)和包的公开导出面(index.ts:191)都已经指向 memory/const.ts,所以当前没有用户可见的 bug。

但它仍然重要:tools/memory-config.ts 的存在本就是作为轻量入口(“当只需要文件名配置时,避免加载整个 tool 模块”)。将来任何 import 它来读取已配置上下文文件名的代码,在用户设置 contextFileName 之后都会悄悄拿到过期的 QWEN.md/AGENTS.md 默认值。本 PR 通过收敛到单一数据源消除了这个陷阱,且导出面被完整保留(原有 6 个符号;LOCAL_CONTEXT_FILENAME 有意不重导出),因此是向后兼容的。

可选、不阻塞的后续

packages/core/src/config/config.test.ts:221 处的 vi.mock('../tools/memory-config', …) 现在已是死代码(被测代码不再 import 该模块)。后续 PR 顺手删掉它,就能彻底了结本 PR 想解决的这笔重复债。


Verified on a worktree at PR head 4975b279. Module A/B & tmux boot use esbuild-transpiled real modules sharing a byte-identical memory/const.js; unit tests via vitest run. The config.test.ts load failure is a pre-existing sandbox undici resolution issue (reproduces on main), unrelated to this PR.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] config/config.test.ts:225-232 still has a vi.mock('../tools/memory-config', ...) block, but config.ts only imports from memory/const.js. This mock intercepts a module that no production import touches — consider removing it in a follow-up.

— qwen3.7-max via Qwen Code /review

// QWEN.md is first to maintain backward compatibility (used by /init command and save_memory tool).
let currentGeminiMdFilename: string | string[] = [
DEFAULT_CONTEXT_FILENAME,
export {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The JSDoc above this export says "Extracted from memoryTool.ts to avoid loading the full tool module when only the filename configuration is needed" — but the file is now a pure re-export barrel with no logic of its own. Additionally, grep shows zero production-code consumers of this module (all imports already use memory/const.js directly). Consider either updating the comment to reflect the barrel role (e.g., "Re-export barrel for backward compatibility. Canonical definitions live in ../memory/const.ts.") or removing the file entirely in a follow-up if no external consumers depend on it.

— qwen3.7-max via Qwen Code /review

@wenshao
wenshao merged commit e4ec477 into QwenLM:main Jun 19, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants