Skip to content

feat(skills): add disabled skill levels - #8057

Merged
wenshao merged 18 commits into
QwenLM:mainfrom
zhangxy-zju:feat/disable-bundled-skills-8054
Aug 1, 2026
Merged

feat(skills): add disabled skill levels#8057
wenshao merged 18 commits into
QwenLM:mainfrom
zhangxy-zju:feat/disable-bundled-skills-8054

Conversation

@zhangxy-zju

Copy link
Copy Markdown
Collaborator

What this PR does

Adds skills.disabledLevels, a union-merged setting that accepts project, user, extension, and bundled. Skill discovery now skips disabled levels before accessing the filesystem, so users can set ["bundled"] to hide all bundled skills while retaining host-provided skills. The change also updates the generated settings schema, documentation, and focused tests.

Why it's needed

Hosts that provide their own skills currently cannot disable every bundled skill with one setting. This adds the level-based control preferred in #8054 while preserving the existing default behavior.

Reviewer Test Plan

How to verify

  1. Add "skills": { "disabledLevels": ["bundled"] } to .qwen/settings.json and restart Qwen Code.
  2. Open the skills list and confirm bundled skills such as simplify are absent.
  3. Confirm a project or host-provided skill is still available.
  4. Remove disabledLevels, restart, and confirm bundled skills return.
  5. Run:
    • npm run build
    • npm run typecheck
    • cd packages/core && npx vitest run src/skills/skill-manager.test.ts
    • cd packages/cli && npx vitest run src/config/config.test.ts --environment node
    • cd packages/cli && npx vitest run src/config/settingsSchema.test.ts --environment node

Evidence (Before & After)

Before: the released CLI listed 37 skills, including bundled review and simplify.

After: the branch CLI with disabledLevels: ["bundled"] listed 30 skills; simplify returned no matches, while the project-provided feat-dev skill remained available.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Local source build and CLI execution on macOS.

Risk & Scope

  • Main risk or tradeoff: a configured level is completely excluded from discovery; accepted values are constrained by the schema and filtered again at the CLI boundary.
  • Not validated / out of scope: Windows and Linux manual CLI execution; CI provides cross-platform coverage.
  • Breaking changes / migration notes: none. The setting is optional and defaults to the current behavior.

Linked Issues

Closes #8054

中文说明

本 PR 做了什么

新增 skills.disabledLevels 配置,以 union 方式合并各配置作用域,并接受 projectuserextensionbundled。Skill 发现流程现在会在访问文件系统前跳过被禁用的层级,因此用户可以设置 ["bundled"] 隐藏所有内置 skill,同时保留宿主提供的 skill。本次变更也更新了生成的设置 schema、文档和针对性测试。

为什么需要

提供自有 skill 的宿主目前无法通过单个配置禁用全部内置 skill。本变更按照 #8054 中倾向的层级控制方案实现,同时保持现有默认行为不变。

Reviewer 测试计划

如何验证

  1. .qwen/settings.json 中加入 "skills": { "disabledLevels": ["bundled"] },然后重启 Qwen Code。
  2. 打开 skill 列表,确认 simplify 等内置 skill 不再出现。
  3. 确认项目或宿主提供的 skill 仍然可用。
  4. 删除 disabledLevels,重启后确认内置 skill 恢复。
  5. 运行:
    • npm run build
    • npm run typecheck
    • cd packages/core && npx vitest run src/skills/skill-manager.test.ts
    • cd packages/cli && npx vitest run src/config/config.test.ts --environment node
    • cd packages/cli && npx vitest run src/config/settingsSchema.test.ts --environment node

证据(前后对比)

变更前:已发布 CLI 列出 37 个 skill,其中包含内置的 reviewsimplify

变更后:分支 CLI 配置 disabledLevels: ["bundled"] 后列出 30 个 skill;搜索 simplify 无匹配,同时项目提供的 feat-dev skill 仍然可用。

测试平台

系统 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

在 macOS 上完成本地源码构建和 CLI 执行。

风险与范围

  • 主要风险或权衡:配置的层级会被完整排除在发现流程之外;可接受值由 schema 约束,并在 CLI 边界再次过滤。
  • 未验证或范围外:未手工验证 Windows 和 Linux CLI;跨平台覆盖由 CI 提供。
  • 破坏性变更或迁移说明:无。该配置可选,默认保持现有行为。

关联 Issue

Closes #8054

@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Jul 29, 2026
@zhangxy-zju

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Jul 29, 2026
@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: real and well-defined. The linked issue #8054 was triaged and accepted (P2, welcome-pr). Embedders running Qwen Code headless/serve/ACP need a stable, upgrade-proof way to hide every bundled skill — the skills.disabled name-list breaks silently whenever a new bundled skill ships.

Direction: aligned. Claude Code already ships a disableBundledSkills setting, so the area is validated; the level-based disabledLevels approach generalizes it cleanly and maps directly onto the existing SkillLevel type (project > user > extension > bundled).

Size: core paths touched (packages/core/src/config/config.ts, packages/core/src/skills/skill-manager.ts, packages/cli/src/config/**, packages/cli/src/serve/**). Production logic: 99 lines. Tests: 171 lines. Schema: 13 lines. Docs: 11 lines. Well under every threshold — no maintainer escalation needed.

Approach: the scope feels right, and it grew in the right direction since the first pass — the daemon's workspace-skills-status path now honors the setting too (with the same safe-mode / untrusted-workspace guard as the CLI), so a headless host gets consistent behavior across the CLI child session and the daemon skill listing. No unrelated changes, no drive-by refactors.

Risk: no elevated risk signals — none of the changed files match the high-revert-correlation paths.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:真实且定义清晰。关联 issue #8054 已被分类并接受(P2、welcome-pr)。在 headless/serve/ACP 场景中嵌入 Qwen Code 的用户需要一种稳定、升级安全的方式来隐藏所有内置 skill——skills.disabled 名称列表在新增 bundled skill 时会静默失效。

方向:对齐。Claude Code 已发布 disableBundledSkills 设置,验证了该方向;基于 level 的 disabledLevels 方案是其干净的自然泛化,并直接映射到现有 SkillLevel 类型(project > user > extension > bundled)。

规模:触及核心路径(packages/core/src/config/config.tspackages/core/src/skills/skill-manager.tspackages/cli/src/config/**packages/cli/src/serve/**)。生产逻辑 99 行,测试 171 行,schema 13 行,文档 11 行。远低于所有阈值——无需维护者升级。

方案:范围合理,且自首轮以来朝正确方向扩展——daemon 的 workspace-skills-status 路径现在也尊重该设置(与 CLI 相同的 safe-mode / 不可信工作区守卫),使 headless 宿主在 CLI 子会话与 daemon skill 列表间获得一致行为。无无关改动,无顺手重构。

风险:无升级风险信号——变更文件均未匹配高回滚关联路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

Reviewed at fabe6e7bcea16cd76ca8e4d80f8aacb327c9b0e9 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Code Review

Clean, minimal, and it follows the existing patterns exactly. My independent proposal before reading the diff — store disabled levels as a frozen ReadonlySet in Config, early-return at the top of listSkillsAtLevel (after the bare-mode check, which is the stronger override), validate at the CLI boundary with a type guard, suppress in safe/bare mode — is precisely what's here.

The core is a five-line guard in SkillManager.listSkillsAtLevel(). The optional chaining (getDisabledSkillLevels?.()) is justified, not defensive overkill: workspace-skill-management.ts constructs SkillManager from partial shims ({} as Config, { getProjectRoot } as Config), so the core must tolerate a config that lacks the method — and a regression test pins exactly that.

The wiring mirrors the neighboring conventions: ConfigParameters.disabledSkillLevels?: readonly SkillLevel[] stored as a ReadonlySet with a getter (same shape as disabledSkillNamesProvider / customSkillDirs); the CLI filters invalid values with isSkillLevel and nullifies the setting in safe/bare mode; the schema entry is MergeStrategy.UNION + requiresRestart: true with an items.enum, mirrored in the VS Code companion JSON.

The growth since the first review is the right kind: the daemon's workspace-skills-status.ts now reads settings.merged.skills?.disabledLevels, applies the same safe-mode / untrusted-workspace guard as loadCliConfig, and exposes it through the shim — so the headless skill listing matches the CLI child session. It also hoists the existing loadSettings call instead of loading settings twice. The new daemon test exercises the real wiring (no listSkills mock) and asserts a bundled skill (review) disappears under disabledLevels: ["bundled"] and reappears in safe mode.

No critical blockers. No convention violations.

Testing

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Real daemon E2E / Java 11 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success
route ✅ success
review-pr ✅ success
Test (windows-latest, Node 22.x) ⏭️ skipped (fork PR)
Test (macos-latest, Node 22.x) ⏭️ skipped (fork PR)

All green on the reviewed commit fabe6e7 — including the ubuntu unit suite that was red on the earlier head and triggered the prior downgrade. Windows/macOS legs are skipped because this is a fork PR; cross-platform coverage comes from the CI matrix on merge.

The behavioural claim — disabledLevels: ["bundled"] actually removes bundled skills end-to-end — is now pinned at the integration level, not just in mock-based unit tests: the daemon wiring test above runs the real path, and Serve A/B plus Real daemon E2E are green. A sandboxed @qwen-code /verify already ran A/B against the base build and reported ✅ merge-ready; a fresh /verify on this exact head is still in flight and will post its own report.

中文说明

代码审查

干净、精简,且完全遵循现有模式。我在阅读 diff 前的独立提案——将禁用的 level 存储为 Config 中冻结的 ReadonlySet,在 listSkillsAtLevel 顶部(bare-mode 检查之后,bare mode 是更强的覆盖)提前返回,在 CLI 边界用类型守卫校验,在 safe/bare 模式下抑制——与实现完全一致。

核心是 SkillManager.listSkillsAtLevel() 中五行守卫。可选链(getDisabledSkillLevels?.())是合理的,并非过度防御:workspace-skill-management.ts 用部分 shim({} as Config{ getProjectRoot } as Config)构造 SkillManager,因此 core 必须容忍缺少该方法的 config——并且有回归测试精确固定了这一点。

接线方式与相邻约定一致:ConfigParameters.disabledSkillLevels?: readonly SkillLevel[] 存为 ReadonlySet 并提供 getter(与 disabledSkillNamesProvider / customSkillDirs 同形);CLI 用 isSkillLevel 过滤非法值并在 safe/bare 模式下置空该设置;schema 条目为 MergeStrategy.UNION + requiresRestart: true 且带 items.enum,并在 VS Code companion JSON 中镜像。

自首轮评审以来的扩展属于正确的方向:daemon 的 workspace-skills-status.ts 现在读取 settings.merged.skills?.disabledLevels,应用与 loadCliConfig 相同的 safe-mode / 不可信工作区守卫,并通过 shim 暴露——使 headless skill 列表与 CLI 子会话一致。它还将已有的 loadSettings 调用上提,避免重复加载。新增的 daemon 测试走真实接线(不 mock listSkills),断言在 disabledLevels: ["bundled"] 下 bundled skill(review)消失、在 safe 模式下恢复。

无关键阻塞项。无约定违规。

测试

审查提交 fabe6e7 上全部绿灯——包括在较早 head 上变红并触发上次降级的 ubuntu 单元测试。Windows/macOS 矩阵腿因 fork PR 被跳过;跨平台覆盖由合入时的 CI 矩阵提供。

行为性声明——disabledLevels: ["bundled"] 真正端到端移除 bundled skill——现在在集成层面被固定,而不仅是 mock 单元测试:上述 daemon 接线测试走真实路径,Serve A/BReal daemon E2E 均为绿。沙箱 @qwen-code /verify 已对 base 构建做 A/B 并报告 ✅ 可合入;针对当前 head 的新一轮 /verify 仍在运行,会自行发布报告。

Qwen Code · qwen3.8-max-preview

Reviewed at fabe6e7bcea16cd76ca8e4d80f8aacb327c9b0e9 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — clean across every stage; CI green on the reviewed commit; would merge without hesitation.

This is what a well-scoped feature PR looks like, and it improved at each round. The problem was validated through #8054 (P2, welcome-pr), and the solution maps directly onto the existing SkillLevel architecture — one early-return check in the right place, wired through config with proper boundary validation, then extended to the daemon so headless hosts get the same behavior as the CLI child session. Ninety-nine production lines, no abstractions, no scope creep.

My independent proposal matched the author's exactly, including the safe-mode / untrusted-workspace guard and the optional-chaining tolerance for partial config shims. The behavioural claim is now pinned at the integration level — a real-wiring daemon test, green Serve A/B and Real daemon E2E, and a sandboxed /verify that reported merge-ready — and the maintainer's own round-2 verification (real CLI build, mutation testing, strace-level filesystem checks) concluded LGTM. The ubuntu suite that was red on the earlier head is green here.

Approving now, pinned to fabe6e7bcea16cd76ca8e4d80f8aacb327c9b0e9. Note: the standing CHANGES_REQUESTED on this PR comes from stale bot reviews filed against earlier red-CI heads (e.g. 178a961, 8b21844, 51f513f); the code has changed substantially and CI is green, so those gates are superseded — flagging for the maintainer to dismiss if branch protection still counts them.

中文说明

置信度:5/5 —— 每个阶段都干净;审查提交上 CI 绿灯;毫不犹豫地合入。

这是一个范围良好的功能 PR 的典范,并且每一轮都在改进。问题已通过 #8054 验证(P2、welcome-pr),方案直接映射到现有 SkillLevel 架构——在正确位置添加一个提前返回检查,通过 config 传递并做适当的边界校验,随后扩展到 daemon,使 headless 宿主获得与 CLI 子会话一致的行为。99 行生产代码,无抽象,无范围蔓延。

我的独立提案与作者完全一致,包括 safe-mode / 不可信工作区守卫,以及对部分 config shim 的可选链容忍。行为性声明现已在集成层面固定——真实接线的 daemon 测试、绿色的 Serve A/BReal daemon E2E、以及报告可合入的沙箱 /verify——且维护者本人的第 2 轮验证(真实 CLI 构建、mutation 测试、strace 级文件系统检查)结论为 LGTM。在较早 head 上变红的 ubuntu 套件在此处为绿。

现在批准,固定于 fabe6e7bcea16cd76ca8e4d80f8aacb327c9b0e9。注意:本 PR 上现存的 CHANGES_REQUESTED 来自针对较早红 CI head(如 178a9618b2184451f513f)的陈旧 bot 评审;代码已大幅变更且 CI 为绿,这些门槛已被取代——提示维护者,若分支保护仍计入它们,可予以驳回。

Qwen Code · qwen3.8-max-preview

Reviewed at fabe6e7bcea16cd76ca8e4d80f8aacb327c9b0e9 · re-run with @qwen-code /triage

@doudouOUC doudouOUC 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.

⚠️ Downgraded from Approve to Comment: CI failing: review-pr. Reviewed.

中文说明

⚠️ 已从批准降级为评论:CI failing: review-pr。 已审查。

— qwen3.7-max via Qwen Code /review

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), review-pr] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@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.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max-preview via Qwen Code /review

Comment thread packages/cli/src/config/config.ts

@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.

Reviewed. Suggestions are inline.

中文说明

已审查。 建议见行内评论。

— qwen3.8-max-preview via Qwen Code /review

Comment thread packages/cli/src/config/config.ts
@qwen-code-dev-bot

qwen-code-dev-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 6 finishedview run. See this round's report below.

中文说明

AutoFix 第 6 轮已完成 —— 查看运行。本轮报告见下方。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Review round — no code changes

Two automated suggestions were triaged; both are declined with reasons below. The CI failure is pre-existing on main.

rc:3676454844 — warn on invalid disabledLevels values

Declined. The silent-drop matches the established convention in this file (customSkillDirs uses .filter(Boolean), nearby .filter(isSkillLevel) is the same pattern). The new test explicitly documents the ignore as intentional. Adding a debug warning for intentionally-handled input is defense for a scenario that is already covered by design and by the VS Code schema items.enum at edit time. Not worth the diff growth (AGENTS.md Simplicity First).

rc:3676876836 — compile-time exhaustiveness for SKILL_LEVELS

Declined. The suggested type _AssertAllLevels = … pattern fails the build: the project sets noUnusedLocals: true (root tsconfig.json), and TypeScript flags the unused type alias (TS6196). The workaround — exporting the assertion type — has no precedent in this codebase and adds public API noise for an internal guard. The existing settingsSchema.test.ts test already validates the enum values match the expected list at test time, and adding a new SkillLevel is a rare, multi-file change that would naturally surface the array during review.

Failed CI check — Test (ubuntu-latest Node 22.x)

Pre-existing on main. Reproduced locally:

  • packages/cli/src/config/config.test.ts: 5 sandbox-image/sandbox-session-ID tests fail identically on origin/main (verified by checking out main's test file and re-running).
  • packages/core/src/skills/skill-manager.test.ts: 3 tests fail only when QWEN_HOME is set (as in this sandbox); all 79 pass with QWEN_HOME= (empty). CI does not set QWEN_HOME, so these are environment-specific to the local runner.

No PR-introduced test failures were found.

中文说明

审查轮次 — 无代码变更

两条自动建议已分类处理,均附理由予以拒绝。CI 失败为 main 分支上的已有问题。

rc:3676454844 — 对无效的 disabledLevels 值发出警告

已拒绝。 静默丢弃与此文件中的既有约定一致(customSkillDirs 使用 .filter(Boolean),附近的 .filter(isSkillLevel) 也是相同模式)。新增测试已明确记录该忽略行为是有意的。为已被设计处理的输入添加调试警告,属于对已有覆盖场景的多余防御,不值得增加 diff 体积(AGENTS.md 简洁优先原则)。

rc:3676876836 — SKILL_LEVELS 的编译期穷举检查

已拒绝。 建议的 type _AssertAllLevels = … 模式会导致构建失败:项目根 tsconfig.json 设置了 noUnusedLocals: true,TypeScript 会标记未使用的类型别名(TS6196)。变通方案——导出该断言类型——在此代码库中无先例,且会为内部守卫增加公共 API 噪音。现有的 settingsSchema.test.ts 测试已在测试时验证枚举值与预期列表一致,且添加新的 SkillLevel 是罕见的多文件变更,审查时自然会注意到该数组。

CI 失败 — Test (ubuntu-latest Node 22.x)

main 分支上的已有问题。本地复现结果:

  • packages/cli/src/config/config.test.ts:5 个 sandbox-image/sandbox-session-ID 测试在 origin/main 上同样失败(通过检出 main 的测试文件并重新运行已验证)。
  • packages/core/src/skills/skill-manager.test.ts:3 个测试仅在设置了 QWEN_HOME 时失败(如本沙箱环境);QWEN_HOME=(空)时全部 79 个通过。CI 不设置 QWEN_HOME,因此这些是本地运行器的环境特有问题。

未发现本 PR 引入的测试失败。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@doudouOUC doudouOUC 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.

⚠️ Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x). Reviewed.

中文说明

⚠️ 已从批准降级为评论:CI failing: Test (ubuntu-latest, Node 22.x)。 已审查。

— qwen3.7-max via Qwen Code /review

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Review Round Summary — No Action Taken

Feedback: The automated reviewer downgraded from Approve to Comment because the CI check "Test (ubuntu-latest, Node 22.x)" is failing.

Diagnosis: The CI failure is pre-existing on main and is not caused by this PR. I reproduced the exact same failures by checking out origin/main's versions of the affected test files and running them locally:

Failing test File On main?
should list skills from both levels packages/core/src/skills/skill-manager.test.ts ✅ Yes
should return all user-level base dirs packages/core/src/skills/skill-manager.test.ts ✅ Yes
should prioritize user-level over bundled skills with same name packages/core/src/skills/skill-manager.test.ts ✅ Yes
should reject direct use of the internal sandbox session ID flag packages/cli/src/config/config.test.ts ✅ Yes
uses --sandbox-image over env and settings packages/cli/src/config/config.test.ts ✅ Yes
uses QWEN_SANDBOX_IMAGE over tools.sandboxImage packages/cli/src/config/config.test.ts ✅ Yes
uses tools.sandboxImage when cli and env are absent packages/cli/src/config/config.test.ts ✅ Yes
falls back to package default image when no explicit source is provided packages/cli/src/config/config.test.ts ✅ Yes

All 8 failures are in sandbox image resolution and user-level skill directory tests — areas this PR does not touch.

This PR's tests all pass:

  • loadCliConfig skills.disabledLevels (2 tests) — passed
  • defines disabled skill levels as a restart-required union setting (1 test) — passed
  • should skip disabled skill levels without scanning them (1 test) — passed

No code changes were made this round. The CI failure should resolve once the pre-existing test failures on main are fixed independently.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run generate:settings-schema — passed (no diff; schema is up to date)
  • vitest run src/config/config.test.ts -t "disabledLevels" (packages/cli) — 2 passed
  • vitest run src/config/settingsSchema.test.ts (packages/cli) — 37 passed
  • vitest run src/skills/skill-manager.test.ts -t "disabled skill levels" (packages/core) — 1 passed
  • Pre-existing failure reproduction on origin/main — confirmed identical 3 + 5 failures
中文说明

审查轮次总结 — 未采取行动

反馈: 自动审查器因 CI 检查 "Test (ubuntu-latest, Node 22.x)" 失败,将评审从"批准"降级为"评论"。

诊断: CI 失败是 main 分支上的已有问题,并非由本 PR 引起。我通过检出 origin/main 上受影响测试文件的版本并在本地运行,复现了完全相同的失败:

失败的测试 文件 main 上也失败?
should list skills from both levels packages/core/src/skills/skill-manager.test.ts ✅ 是
should return all user-level base dirs packages/core/src/skills/skill-manager.test.ts ✅ 是
should prioritize user-level over bundled skills with same name packages/core/src/skills/skill-manager.test.ts ✅ 是
should reject direct use of the internal sandbox session ID flag packages/cli/src/config/config.test.ts ✅ 是
uses --sandbox-image over env and settings packages/cli/src/config/config.test.ts ✅ 是
uses QWEN_SANDBOX_IMAGE over tools.sandboxImage packages/cli/src/config/config.test.ts ✅ 是
uses tools.sandboxImage when cli and env are absent packages/cli/src/config/config.test.ts ✅ 是
falls back to package default image when no explicit source is provided packages/cli/src/config/config.test.ts ✅ 是

所有 8 个失败都在沙箱镜像解析和用户级技能目录测试中——这些是本 PR 未涉及的领域。

本 PR 新增的测试全部通过:

  • loadCliConfig skills.disabledLevels(2 个测试)— 通过
  • defines disabled skill levels as a restart-required union setting(1 个测试)— 通过
  • should skip disabled skill levels without scanning them(1 个测试)— 通过

本轮未做任何代码更改。 CI 失败应在 main 上的已有测试失败被独立修复后自行解决。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npm run generate:settings-schema — 通过(无差异;schema 已是最新)
  • vitest run src/config/config.test.ts -t "disabledLevels"(packages/cli)— 2 个通过
  • vitest run src/config/settingsSchema.test.ts(packages/cli)— 37 个通过
  • vitest run src/skills/skill-manager.test.ts -t "disabled skill levels"(packages/core)— 1 个通过
  • origin/main 上复现已有失败 — 确认相同的 3 + 5 个失败

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@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.

Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

— qwen3.8-max-preview via Qwen Code /review

@doudouOUC doudouOUC 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.

⚠️ Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x). Reviewed.

中文说明

⚠️ 已从批准降级为评论:CI failing: Test (ubuntu-latest, Node 22.x)。 已审查。

— qwen3.7-max via Qwen Code /review

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No code changes this round

The new feedback contains no inline comments and no requested code change — only two informational review notes and a failed-check signal (Test (ubuntu-latest, Node 22.x)). After investigating, the failing check does not appear to be caused by this PR, so there is no scoped fix to make.

Triage of the feedback

  • @qwen-code-ci-bot review — informational: it noted the integration suite was skipped in CI and not run locally. Not a defect in this PR.
  • @doudouOUC review — downgraded Approve → Comment solely because Test (ubuntu-latest, Node 22.x) was red. This is the only actionable signal.

Investigation of the Test failure

  1. The check passes on current main. The autofix base-update step already recorded on this PR: "red check(s) [Test (ubuntu-latest, Node 22.x), review-pr] pass on current main — merged current main via update-branch." This PR is current main plus a small, self-contained change, so a green main plus a correct diff should be green.
  2. No PR-caused failure is reproducible locally. Running the touched test files in a CI-like environment (with this sandbox's extra env vars unset) passes fully:
    • packages/core/src/skills/skill-manager.test.ts — 79/79 passed (includes the new disabledLevels skip test)
    • packages/cli/src/config/settingsSchema.test.ts — 37/37 passed (includes the new schema-shape test)
    • packages/cli/src/config/config.test.ts — 313/313 passed (includes the two new disabledLevels wiring tests)
  3. The only locally-failing tests are environment-specific and pre-existing on origin/main. This sandbox container exports QWEN_HOME, QWEN_CODE_CUSTOM_SANDBOX_IMAGE, and SANDBOX. Those break 3 user-level skill-dir tests (Storage.getGlobalQwenDir() prefers QWEN_HOME over the mocked os.homedir()) and 5 sandbox-image-resolution tests. I confirmed the same 3 skill tests fail on a clean checkout of origin/main (3 failed | 75 passed), and all of them pass once those env vars are unset. The CI Test job (.github/workflows/ci.yml) does not set these variables, so these tests pass in CI. They are unrelated to this PR's skills.disabledLevels change.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run generate:settings-schema — no diff (committed settings.schema.json is up to date)
  • vitest packages/core skill-manager (touched) — 79 passed (CI-like env)
  • vitest packages/cli settingsSchema (touched) — 37 passed
  • vitest packages/cli config (touched) — 313 passed (CI-like env)

Conclusion

The implementation was already reviewed at 4/5 confidence with no blockers. The reported Test failure is consistent with a transient/flaky CI run on a fork PR rather than a defect introduced here — it passes on current main and no PR-caused failure reproduces locally. No code change is warranted this round; re-running CI on the current head is the recommended next step.

中文说明

本轮不做代码改动

新反馈没有行内评论,也没有要求任何代码改动——只有两条信息性评审说明和一个失败检查信号(Test (ubuntu-latest, Node 22.x))。经排查,该失败检查并非由本 PR 引起,因此没有可做的小范围修复。

反馈分类

  • @qwen-code-ci-bot 评审 —— 信息性:指出集成测试套件在 CI 中被跳过、且未在本地运行。并非本 PR 的缺陷。
  • @doudouOUC 评审 —— 仅因 Test (ubuntu-latest, Node 22.x) 变红而将「批准」降级为「评论」。这是唯一可操作的信号。

Test 失败的排查

  1. 该检查在当前 main 上通过。 autofix 的 base 更新步骤已在本 PR 上记录:“红色检查 [Test (ubuntu-latest, Node 22.x), review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main。” 本 PR 即为当前 main 加上一个小而自包含的改动,绿色的 main 加上正确的 diff 理应为绿。
  2. 本地无法复现任何由 PR 引起的失败。 在类 CI 环境(取消本沙箱额外的环境变量)下运行所触及的测试文件,全部通过:
    • packages/core/src/skills/skill-manager.test.ts —— 79/79 通过(含新增的 disabledLevels 跳过测试)
    • packages/cli/src/config/settingsSchema.test.ts —— 37/37 通过(含新增的 schema 形状测试)
    • packages/cli/src/config/config.test.ts —— 313/313 通过(含两个新增的 disabledLevels 接线测试)
  3. 本地唯一失败的测试是环境特异性的,且在 origin/main 上同样存在。 本沙箱容器导出了 QWEN_HOMEQWEN_CODE_CUSTOM_SANDBOX_IMAGESANDBOX。它们导致 3 个 user 级 skill 目录测试失败(Storage.getGlobalQwenDir() 优先使用 QWEN_HOME 而非被 mock 的 os.homedir())以及 5 个 sandbox 镜像解析测试失败。我已确认同样的 3 个 skill 测试在 origin/main 的干净检出上也会失败(3 failed | 75 passed),并且一旦取消这些环境变量它们就全部通过。CI 的 Test 任务(.github/workflows/ci.yml)并不设置这些变量,因此这些测试在 CI 中通过。它们与本 PR 的 skills.disabledLevels 改动无关。

验证

  • npm run build —— 通过
  • npm run typecheck —— 通过
  • npm run lint —— 通过
  • npm run generate:settings-schema —— 无差异(已提交的 settings.schema.json 为最新)
  • vitest packages/core skill-manager(触及)—— 79 通过(类 CI 环境)
  • vitest packages/cli settingsSchema(触及)—— 37 通过
  • vitest packages/cli config(触及)—— 313 通过(类 CI 环境)

结论

实现此前已以 4/5 的置信度完成评审,无阻塞项。所报告的 Test 失败更符合 fork PR 上一次瞬时/抖动的 CI 运行,而非本 PR 引入的缺陷——它在当前 main 上通过,且本地无法复现任何由 PR 引起的失败。本轮无需代码改动;建议的下一步是在当前 head 上重新运行 CI。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action taken

The only feedback this round is an informational COMMENTED review from the automated reviewer noting that the "Integration Tests (CLI, No Sandbox)" suite was skipped in CI and did not run locally. The review raises no defect, requests no change, and has no inline comments, failed checks, or still-red checks.

No code changes are warranted.

中文说明

Autofix 审查轮次 — 未采取任何操作

本轮唯一的反馈是自动审查机器人的一条信息性 COMMENTED 审查,指出 "Integration Tests (CLI, No Sandbox)" 套件在 CI 中被跳过且未在本地运行。该审查未报告任何缺陷、未请求任何更改,也没有内联评论、失败的检查或持续红色的检查。

无需进行代码更改。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@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.

Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max-preview via Qwen Code /review

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action taken

The only feedback newer than the last evaluation is a single [COMMENTED]
review from the automated reviewer:

Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox)
was skipped in CI and its suite did not run locally.

This is a disclosure of the reviewer's own coverage scope, not a finding. It
names no defect, requests no code change, and is not a CHANGES_REQUESTED
review. There are also no inline comments, no issue-level comments, no failed
checks, and no still-red checks in this round.

No code change is warranted:

  • The note concerns the reviewer's verification coverage, not a problem with
    the diff.
  • The behavior this PR adds (skills.disabledLevels) is covered by focused
    unit tests in packages/cli/src/config/config.test.ts,
    packages/cli/src/config/settingsSchema.test.ts,
    packages/cli/src/serve/workspace-skills-status.test.ts, and
    packages/core/src/skills/skill-manager.test.ts, including the daemon
    wiring regression and the safe-mode/bare-mode guards.

No commits were made this round.

中文说明

Autofix 审查轮次 — 未采取任何行动

距上次评估之后唯一的反馈,是自动审查机器人发出的一条 [COMMENTED]
审查:

已审查。未审查:build-and-test — Integration Tests (CLI, No Sandbox)
在 CI 中被跳过,且其测试套件未在本地运行。

这是审查者对自身审查覆盖范围的说明,并非一项审查发现。它没有指出任何缺陷,
没有要求任何代码改动,也不是一条 CHANGES_REQUESTED(要求修改)审查。本轮同样
没有行内评论、没有 issue 级评论、没有失败的检查、也没有持续飘红的检查。

无需进行代码改动:

  • 该说明涉及的是审查者自身的验证覆盖范围,而非本 PR diff 中的问题。
  • 本 PR 新增的行为(skills.disabledLevels)已由以下文件中的聚焦单元测试覆盖:
    packages/cli/src/config/config.test.ts
    packages/cli/src/config/settingsSchema.test.ts
    packages/cli/src/serve/workspace-skills-status.test.ts 以及
    packages/core/src/skills/skill-manager.test.ts,其中包括 daemon 接线回归
    测试,以及 safe-mode / bare-mode 守卫测试。

本轮未产生任何提交。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@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.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

— qwen3.8-max-preview via Qwen Code /review

Comment thread packages/core/src/skills/skill-manager.ts
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下:

Review feedback addressed

[rc:3688030999] Suggestion: add regression test for getDisabledSkillLevels?.() undefined branch

Decision: Implemented.

Changes: Added should not crash when config lacks getDisabledSkillLevels test in packages/core/src/skills/skill-manager.test.ts, following the existing should not crash when config lacks getCustomSkillDirs pattern. The test constructs a partial Config shim (providing isSafeMode, getProjectRoot, getBareMode) that intentionally omits getDisabledSkillLevels, then asserts listSkills({ force: true }) resolves without throwing. This guards against a future refactor dropping the ?. operator, which the static type makes look like dead defensiveness.

The optional comment at the call site was declined for consistency: the pre-existing getCustomSkillDirs?.() in the same file carries no such comment.

Conflict notes: None (--conflict false).

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx vitest run src/skills/skill-manager.test.ts (packages/core, touched) — 77 passed, 4 failed (all 4 pre-existing on the base branch, confirmed by stash-and-rerun; the new test passes)
中文说明

已处理的评审反馈

[rc:3688030999] 建议:为 getDisabledSkillLevels?.() 的 undefined 分支添加回归测试

决定: 已实现。

变更:packages/core/src/skills/skill-manager.test.ts 中添加了 should not crash when config lacks getDisabledSkillLevels 测试,遵循已有的 should not crash when config lacks getCustomSkillDirs 模式。该测试构造了一个部分 Config 模拟对象(提供 isSafeModegetProjectRootgetBareMode),故意省略 getDisabledSkillLevels,然后断言 listSkills({ force: true }) 能正常解析而不抛出异常。这可以防止未来的重构移除 ?. 运算符——因为静态类型会让它看起来像是多余的防御性代码。

调用处的可选注释被拒绝,原因是一致性:同文件中已有的 getCustomSkillDirs?.() 也没有此类注释。

冲突说明: 无(--conflict false)。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npx vitest run src/skills/skill-manager.test.ts(packages/core,已修改)— 77 通过,4 失败(全部 4 个在基础分支上已存在,通过 stash 后重新运行确认;新测试通过)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@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.

Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max-preview via Qwen Code /review

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action needed

No actionable feedback was found in this round:

  • Reviews: none
  • Inline comments: none
  • Issue-level comments: none
  • Failed checks: none
  • Still-red checks: none

The only feedback present is in the Deferred non-Critical feedback section (automated reviewer suggestions deferred under Critical-only mode after 5 change-producing rounds). Per the review policy, these items are an audit record — no code changes, thread resolutions, or comment replies are made on their behalf. They remain open for human follow-up.

中文说明

Autofix 评审轮次 — 无需操作

本轮未发现需要处理的反馈:

  • 评审(Reviews):无
  • 行内评论(Inline comments):无
  • Issue 级评论(Issue-level comments):无
  • 失败的检查(Failed checks):无
  • 持续失败的检查(Still-red checks):无

唯一存在的反馈位于延后的非 Critical 反馈部分(在 5 个产生改动的轮次后,自动评审的建议按仅处理 Critical 模式被延后)。根据评审策略,这些条目属于审计记录——不会代其修改代码、解决线程或回复评论。它们保持开放,留待人工跟进。

Deferred non-Critical feedback

Critical-only mode is active after 5 change-producing rounds: the automated reviewer's non-Critical suggestions below are deferred and stay open for human follow-up — do not modify code, resolve threads, or reply on their behalf. Maintainer feedback defers only once its author has already had 2 regular feedback batches addressed in this window's Critical-only tail — an account can host an automated reviewer loop, so the brake keys on measured regeneration, not identity; authors at their budget, if any, are named below. (A maintainer can lift the mode itself: @qwen-code /retry starts a fresh counting window.)

中文说明

完成 5 个产生改动的轮次后进入仅处理 Critical 的模式:以上为自动评审的非 Critical 建议,予以延后、保持开放并留待人工跟进——不要为其修改代码、解决线程或代为回复。维护者的反馈仅在其本人于本窗口 Critical-only 阶段已被处理 2 批常规反馈之后才会被延后——账号可能挂着自动评审循环,因此刹车依据实测的再生频度而非身份;达到预算的作者(如有)在下方点名。(如需解除该模式,评论 @qwen-code /retry 即可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@wenshao

wenshao commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /verify

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 30 passed · 0 failed · 30 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:30 通过 · 0 失败 · 30 总计

Verification report (report.md)

# PR 8057 — Deep Verification Report

**Verdict: `merge-ready`** — assertions **30 pass / 0 fail / 30 total** (every
scripted observation matched its prediction; the one mutation survivor is
completeness-reporting, not a blocking defect — see Finding S1). Verified head
`ba09ffd0e25ddea3f797a51fff5186a507e2fb13` (`HEAD^2`); base tip
`a182bdf6185fb4c607df6a3cfa58b68c516ff0c9` (`HEAD^1`). First verification round
(no `previous-report.md`).

> Assertion tally (recomputable from `logs/`): `ab-skillmanager` 15/0 +
> `union-merge` 4/0 + `daemon-wiring` 8/0 = 27 wire-oracle assertions, plus 3
> executed mutation-matrix observations (M1 survives, M2 pinned, controls live)
> = **30**. `fail` counts only *unexpected* outcomes; there were none — every
> A/B cell and every mutation behaved exactly as the analysis predicted. The
> vacuity in Finding S1 is encoded as the *observed* M1 survivor (a passing
> observation whose *interpretation* is the finding), never as a `fail`, because
> it is a test-completeness gap on a defense-in-depth guard, not a correctness
> failure of the shipped behavior (see Methodology for the mapping).

<details>
<summary>中文摘要</summary>

- **结论:`merge-ready`**。功能本身正确且被 A/B 证明为 load-bearing:在
  `disabledLevels=["bundled"]` 下,head 构建只加载 project+user 两级 skill(9 个
  bundled 全部跳过),而 base 对照在相同配置下仍加载全部 9 个 bundled(guard 缺失)。
  详见下文「Central claim」表与 `01-ab-skillmanager-head-vs-base.png`。
- **次要声明均通过**:`disabledLevels` 跨 scope 以 UNION 合并(user `["bundled"]` +
  workspace `["project"]` → `["bundled","project"]`,去重正确);daemon 的
  `createWorkspaceSkillsStatusProvider` 端到端隐藏 bundled、保留 project、并在 safe
  mode 下与 CLI 子会话一致地忽略该配置;提交的 `settings.schema.json` 与生成器输出
  字节一致。
- **唯一 finding(S1,非阻塞 / 完整性报告)**:PR 最后一个 commit 新增的回归测试
  `should not crash when config lacks getDisabledSkillLevels` 是**空测试**——把 guard
  的 `?.` 改成 `.` 后它仍然绿,因为 `refreshCache` 用 `Promise.allSettled` 吞掉了
  `TypeError`。该 `?.` 是「纵深防御」,仓库内所有会走到该 guard 的生产调用方都已提供
  `getDisabledSkillLevels`,故任何测试都无法 pin 住它;按 verify 规则这属于完整性报告,
  **不是合并条件**,因此不降级 verdict。功能本身由行为测试 pin 住(M2:删除 skip 块后
  该测试变红)。
- **未覆盖**:per-commit 归因(depth-2 shallow,仅校验聚合 `HEAD^1..HEAD`);全仓
  test/typecheck/lint(仅跑受影响 suite);CI 注入的 `QWEN_HOME` 导致 4 个**既有**
  user-level 测试失败(已用 unset 做 A/A 证明为环境因素);Windows/macOS 手工 CLI。

数字请直接引用下文各表,避免与摘要重复而可能不一致。

</details>

## Central claim + A/B load-bearing proof

**Central claim:** `SkillManager.listSkillsAtLevel` skips a level whose name is in
`config.getDisabledSkillLevels()`, *before* touching the filesystem, so
`skills.disabledLevels: ["bundled"]` hides every bundled skill while project/user
skills survive; and the change is inert when the setting is absent.

The control is a base worktree at `HEAD^1` with the root `node_modules` symlinked
(rebuilt core via `node …/typescript/bin/tsc --build` + `copy_files.js`, because the
`.bin/tsc` shim is non-executable in this sandbox). `core`'s `skill-manager.js` has
**no** cross-workspace imports, so the shared `node_modules` is a clean control; the
base emission was asserted to lack the guard (`grep -c getDisabledSkillLevels` = 0 in
base dist vs 1 at line 768 in head dist). The base `tsc` reported 15 errors confined
to `ignore`/`ajv`-dependent utils (a worktree module-resolution artifact — those same
files typecheck clean in the head tree); none touch `skills/` or `config/`, and with
no `noEmitOnError` the JS still emitted. Harness: `ab-skillmanager.mjs` (real
compiled `SkillManager` from each dist against a real temp fs: a project skill, a
user skill, and the 9 real bundled skills; `HOME`/`QWEN_HOME` isolated to the temp
world). Witness: `01-ab-skillmanager-head-vs-base.png`.

| Cell | build | `disabledLevels` | observable oracle (skills loaded) | result |
| ---- | ----- | ---------------- | --------------------------------- | ------ |
| 1 | **head** | `["bundled"]` | `count=2` → `proj-skill, user-skill`; bundled level empty | bundled dropped, others kept |
| 2 | **base** (control) | `["bundled"]` | `count=11` → all 9 bundled **still present** + proj + user | guard absent ⇒ setting ignored |
| 3 | head | `[]` (default) | `count=11`, bundled=9 | default unchanged |
| 4 | base | `[]` (A/A) | `count=11`, bundled=9 | head default set ≡ base default set |
| 5 | head | n/a (shim lacks method) | `count=11`, no throw | optional-chain guard holds |
| 6 | base | n/a (shim lacks method) | `count=11`, no throw | base never calls the method (new call site) |

The load-bearing pair is **Cell 1 vs Cell 2**: identical setting, identical real
skill world, identical harness — head loads 0 bundled, base loads 9. Cells 3/4 prove
the change is inert when unset (head ≡ base). Cells 5/6 prove the `?.` guard and that
the call site is new in head. `ab-skillmanager` asserts **15/0**.

## Secondary claim 1 — UNION merge across scopes (wire oracle)

Harness `union-merge.mjs` drives the real compiled `loadSettings` with real files
(user scope `$QWEN_HOME/settings.json` = `["bundled"]`, workspace scope
`<ws>/.qwen/settings.json` = `["project"]`). Witness: counts in
`logs/union-merge.log`.

| input (user / workspace) | `merged.skills.disabledLevels` | assertion |
| ------------------------ | ------------------------------ | --------- |
| `["bundled"]` / `["project"]` | `["bundled","project"]` | union, not last-wins (len 2) |
| `["bundled"]` / `["bundled"]` | `["bundled"]` | de-duplicated |

`union-merge` asserts **4/0**. The schema entry is `mergeStrategy: UNION`,
`requiresRestart: true`, `items.enum = [project,user,extension,bundled]`,
`default: undefined` (asserted by `settingsSchema.test.ts`).

## Secondary claim 2 — daemon wiring end-to-end (wire oracle)

Harness `daemon-wiring.mjs` drives the real compiled
`createWorkspaceSkillsStatusProvider` against real workspaces + real bundled skills
(no `listSkills` mock — this is the production path the regression commits fixed).
Witness: `03-daemon-wiring.png`.

| scenario | oracle | result |
| -------- | ------ | ------ |
| workspace `disabledLevels=["bundled"]` | `initialized=true`; no `bundled`-level skill; `review`/`simplify` absent; `proj-skill` present | hidden via daemon wiring |
| no setting (control) | `review` present, `proj-skill` present | default intact |
| `QWEN_CODE_SAFE_MODE=1` + `["bundled"]` | `review` present | safe mode nullifies, mirroring the CLI child |

`daemon-wiring` asserts **8/0**.

## Committed generated artifact — schema regeneration

Re-ran the generator (`npm run generate:settings-schema`) and diffed its output
against the committed `packages/vscode-ide-companion/schemas/settings.schema.json`:
**byte-identical** (then restored). The artifact was genuinely generated, not
hand-edited. Log: regenerated in place, `diff` empty.

## Targeted gates (affected suites only)

Run with `QWEN_HOME` unset (see Not covered for why). All from compiled-or-source
vitest on the affected files:

| suite | result |
| ----- | ------ |
| `packages/core` `skill-manager.test.ts` | **81 passed** (incl. the 2 new tests) |
| `packages/cli` `config.test.ts` | **319 passed** (incl. the 4 new `disabledLevels` tests: valid/invalid filter, default, safe mode, bare mode) |
| `packages/cli` `settingsSchema.test.ts` | **37 passed** (incl. the new union/restart-required assertion) |
| `packages/cli` `workspace-skills-status.test.ts` | **11 passed** (incl. the 2 new daemon-wiring tests) |

## Mutation matrix (test quality)

Rerunnable, self-restoring: `mutation-matrix.sh` (witness `04-mutation-matrix.png`);
the focused vacuity run is `vacuity-demo.sh` (witness `02-vacuity-regression-test.png`).
Controls (both targeted tests green on pristine source) confirm the suite is live.

| # | guard mutated | test that should pin it | observed | pinned? |
| - | ------------- | ----------------------- | -------- | ------- |
| M1 | `?.` → `.` (skill-manager.ts:963) | `should not crash when config lacks getDisabledSkillLevels` | test **stays green** | **NO — survivor** |
| M2 | remove skip-disabled-levels block (963–966) | `should skip disabled skill levels without scanning them` | test **goes red** (`simplify` reappears: `expected [ ['review','project'], …(1) ] to deeply equal [ ['review','project'] ]`) | **YES** |

M2 proves the *feature* is load-bearing (its behavioral test catches removal). M1 is
the survivor behind Finding S1.

## Findings

### S1 — the new regression-guard test is vacuous (Suggestion / completeness-reporting, **non-blocking**)

The PR's final commit (`ba09ffd0e`, "add regression guard for missing
getDisabledSkillLevels") adds a test whose stated purpose is to guard the `?.` on the
new call site. It does not:

- With the guard intact, a partial config (no `getDisabledSkillLevels`) loads **9**
  bundled skills.
- Mutate `?.` → `.` (the exact regression the guard exists to prevent): the same
  partial config silently loads **0** skills — every level throws `TypeError:
  this.config.getDisabledSkillLevels is not a function`, and `refreshCache`'s
  `Promise.allSettled` *swallows* it, so `listSkills` still **resolves** (with an
  empty cache).
- The test asserts only `await expect(listSkills(...)).resolves.toBeDefined()`, and
  `[]` *is* defined — so it passes in both states. It cannot distinguish "guard
  works" from "guard removed and every level silently failed."

Reproduce (self-restoring): `bash tmp/pr8057-verify-*/vacuity-demo.sh` or the matrix
row M1 in `mutation-matrix.sh`. Witnesses `02-vacuity-regression-test.png` and
`04-mutation-matrix.png`.

**Why this is completeness-reporting, not a merge condition.** I enumerated every
`new SkillManager(...)` site. The only production callers that reach
`listSkillsAtLevel` (where the `?.` lives) are `packages/core/src/config/config.ts`
(real `Config`, which has the method) and
`packages/cli/src/serve/workspace-skills-status.ts` (the daemon shim, which **this
PR** gives the method). The two partial-config sites in
`packages/cli/src/serve/workspace-skill-management.ts` only call
`getSkillsBaseDirs`/`parseSkillContent`, which never reach the guard. So the `?.` is
defense-in-depth that **no in-repo production caller can trip today** — which is
exactly why no test can pin it, and why the skill's mutation-matrix rule classifies
such a survivor as completeness-reporting rather than a merge condition. The feature
itself *is* pinned (M2). I therefore record S1 as a Suggestion and do **not** let it
downgrade the verdict, per that rule.

<details>
<summary>Minimal suggested fix (preserves the commit's intent) — optional</summary>

The intent ("a config shim that omits `getDisabledSkillLevels` must not crash
discovery") is real and worth keeping; only the *assertion* is too weak. Assert the
behavior the guard protects, not merely resolution:

```ts
it('should not crash when config lacks getDisabledSkillLevels', async () => {
  const partialConfig = {
    isSafeMode: () => false,
    getProjectRoot: () => '/test/project',
    getBareMode: () => false,
  } as Config;
  const partialManager = new SkillManager(partialConfig);

  const skills = await partialManager.listSkills({ force: true });
  // The guard must keep discovery alive: a partial shim still surfaces the
  // bundled skills. Without the `?.`, every level throws inside refreshCache's
  // Promise.allSettled and this silently becomes [] — which the old
  // `resolves.toBeDefined()` could not see.
  expect(skills.some((s) => s.level === 'bundled')).toBe(true);
});
```

Caveat: this assertion only goes red under the mutation if the test's mocked `fs`
actually yields bundled entries for the partial-config manager (the existing
`mockReaddirForLevels`/`setupReviewSkillMocks` setup in the surrounding `describe`
does). If the surrounding mock state isn't in scope for this `it`, the equivalent
fix is to drive it the way `ab-skillmanager.mjs` Cell 5 does (real fs, real bundled
dir) and assert a non-empty bundled result. Either way, assert a *count/level*, not
`toBeDefined()`.

</details>

## Not covered

- **Per-commit attribution.** The checkout is depth-2 (shallow); only the merge
  commit, `HEAD^1`, and `HEAD^2` are reachable, so the 16 commits in the PR metadata
  could not be individually exercised. Verified the aggregate `HEAD^1..HEAD` diff.
- **Full-repo gates.** Ran only the affected suites (table above); did not run the
  whole `npm run test`, repo-wide `typecheck`, or `lint` (the PR's own CI covers
  those; my A/B needed the affected suites from a known-clean state).
- **The 4 pre-existing core `skill-manager` failures under CI `QWEN_HOME`.** With the
  CI-injected `QWEN_HOME=/__w/_temp/verify-agent-home/.qwen`, four *pre-existing*
  user-level tests fail (`should list skills from both levels`, `reads the committed
  cache…`, `should return all user-level base dirs`, `should prioritize user-level
  over bundled…`) because they mock `os.homedir()` to `/home/user` while
  `Storage.getGlobalQwenDir()` reads `$QWEN_HOME`. **A/A proof this is environmental,
  not a regression:** with `QWEN_HOME` unset the same head suite is **81/81 green**;
  the four tests are absent from the PR diff (`git diff HEAD^1..HEAD` over the test
  file does not touch them). All targeted suites above were therefore run with
  `QWEN_HOME` unset.
- **`["user"]` also hides `skills.directories` entries.** The docs state this; the
  mechanism is visible in code (custom dirs are appended in the `user` branch of
  `getSkillsBaseDirs`), but I did not exercise a `skills.directories` +
  `disabledLevels:["user"]` combination end-to-end.
- **Empty-skills corner (safe mode + `disabledLevels:["bundled"]` via a direct
  consumer that does *not* nullify).** Both real callers nullify the set in safe mode
  (CLI `loadCliConfig`; daemon `isSafeModeEnv()`), so this corner is unreachable in
  production; not exercised.
- **Windows / macOS manual CLI** — not run (Linux sandbox); cross-platform coverage
  is the PR's own CI.

## Methodology

Environment: CI verify job, container, no GitHub token, `QWEN_VERIFY_CHROMIUM=1`
(Chromium pre-installed; no `playwright install`). Working tree = `pull/8057/merge`
at depth 2; `HEAD^1`/`HEAD^2` used as base/PR-head per the merge-ref contract. The
head build was already present; the base control was built in a scratch worktree
(`tmp/base-tree`, since removed) by invoking `tsc --build` through `node` (the
`.bin/tsc` shim is non-executable here) plus `copy_files.js`, wired to the head
root `node_modules` — valid because the PR leaves the dependency tree untouched and
`core/skill-manager.js` imports no `@qwen-code/*` workspace (verified by grep), so
the internal-symlink confound does not apply; the base emission was independently
asserted to lack the guard. Each harness is mock-free w.r.t. the unit under test:
`ab-skillmanager.mjs` and `daemon-wiring.mjs` drive the real compiled `SkillManager`
/ `createWorkspaceSkillsStatusProvider` from `dist/` against real temp filesystems
and the real bundled skills; `union-merge.mjs` drives the real compiled
`loadSettings` with real settings files. Image evidence was rendered through the
`terminal-capture` engine (node-pty → xterm.js → Playwright) with a terminal
sentinel per run so vitest's silent transform phase could not truncate the shot;
filenames are kebab-case captions referenced above. Raw per-run output lives in
`logs/`; harnesses in the artifact root. Assertion mapping: the 27 wire-oracle
`assert()` results (15+4+8) plus 3 executed mutation-matrix observations (M1
survives, M2 pinned, controls live) = 30 pass; `fail=0` because no outcome was
unexpected — the M1 survivor is recorded as a passing *observation* whose
*interpretation* is Finding S1 (a completeness gap on a defense-in-depth guard), not
as a `fail`, since it is not a correctness failure of the shipped behavior and the
skill's mutation-matrix rule treats such survivors as completeness-reporting rather
than merge conditions. The working tree was left pristine after every mutation
(`git diff` empty; the `?.` guard present at `skill-manager.ts:963`).

Evidence images

01-ab-skillmanager-head-vs-base

02-vacuity-regression-test

03-daemon-wiring

04-mutation-matrix

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No actionable feedback this round.

  • Reviews: none
  • Inline comments: none
  • Issue-level comments: none
  • Failed checks: none
  • Still-red checks: none

The PR is in Critical-only mode after 5 change-producing rounds. The single deferred item (automated reviewer non-Critical suggestion) is an audit record and stays open for human follow-up — no code changes, thread resolutions, or replies are warranted.

中文说明

本轮没有可操作的反馈。

  • 评审:
  • 行内评论:
  • Issue 级评论:
  • 失败的检查:
  • 持续失败的检查:

该 PR 在经历 5 个产生改动的轮次后已进入仅处理 Critical 的模式。唯一的延后条目(自动评审的非 Critical 建议)属于审计记录,保持开放并留待人工跟进——无需修改代码、解决线程或进行回复。

Deferred non-Critical feedback

Critical-only mode is active after 5 change-producing rounds: the automated reviewer's non-Critical suggestions below are deferred and stay open for human follow-up — do not modify code, resolve threads, or reply on their behalf. Maintainer feedback defers only once its author has already had 2 regular feedback batches addressed in this window's Critical-only tail — an account can host an automated reviewer loop, so the brake keys on measured regeneration, not identity; authors at their budget, if any, are named below. (A maintainer can lift the mode itself: @qwen-code /retry starts a fresh counting window.)

中文说明

完成 5 个产生改动的轮次后进入仅处理 Critical 的模式:以上为自动评审的非 Critical 建议,予以延后、保持开放并留待人工跟进——不要为其修改代码、解决线程或代为回复。维护者的反馈仅在其本人于本窗口 Critical-only 阶段已被处理 2 批常规反馈之后才会被延后——账号可能挂着自动评审循环,因此刹车依据实测的再生频度而非身份;达到预算的作者(如有)在下方点名。(如需解除该模式,评论 @qwen-code /retry 即可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@zhangxy-zju

Copy link
Copy Markdown
Collaborator Author

Addressed the S1 test-completeness suggestion in fabe6e7bce.

The partial-Config regression test now asserts that bundled discovery still returns review and simplify. A mutation from getDisabledSkillLevels?.() to the direct call makes this test fail with an empty skill list, so the optional guard is now pinned.

Validation:

  • core skill-manager.test.ts: 81/81
  • cumulative CLI config/schema/daemon suites: 367/367
  • full build, typecheck, lint, Prettier, and git diff --check: passed

The latest main currently makes the CLI suites fail during collection because qwen-logger.ts imports the Node built-in as https; I used a temporary local node:https substitution to run those suites and reverted it before committing.

@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.

Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

— qwen3.8-max-preview via Qwen Code /review

@wenshao

wenshao commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Local verification report (round 2) — real CLI build + E2E

Re-verification of head fabe6e7 after the five autofix rounds, following up on my round-1 report which found a blocking daemon regression on 136cbbf.

Verdict: merge-ready. The blocker is fixed, and I confirmed the fix is load-bearing by mutating it back out. The feature itself behaves exactly as specified across every level, scope, and mode I could drive. One non-blocking observation is recorded at the end.

Built from source on Linux: full npm run build, then node esbuild.config.js + copy_bundle_assets.js so the 9 bundled skills are really on disk, driven against an isolated HOME with skills installed at all four levelsproject, user, extension, bundled — plus a host directory via skills.directories (13 skills total).


1. The round-1 blocker is fixed — and pinned

The regression I reported was: SkillManager.listSkillsAtLevel called getDisabledSkillLevels() non-optionally, the qwen serve shim did not provide it, refreshCache's Promise.allSettled swallowed the TypeError, and GET /workspace/skills returned zero skills with initialized: true for every workspace under a default config.

Driving a real qwen serve daemon at this head:

# config initialized total byLevel
D1 — (control) true 11 bundled 9 · project 1 · user 1
D2 ["bundled"] @workspace true 2 project 1 · user 1
D3 ["bundled"] + QWEN_CODE_SAFE_MODE=1 true 9 bundled 9

D1 is the regression check — non-zero, correct. D2 shows the daemon now honors the setting (the round-2 shim wiring). D3 shows it matches the CLI child in safe mode (the round-3 guard).

To confirm none of that is accidental, I mutated each fix back out of the built bundle, one line at a time, re-auditing the deployed chunk before and after every arm:

# mutation daemon result verdict
M1 drop getDisabledSkillLevels from the serve shim 11 skills, 9 of them bundled setting silently ignored on the daemon path — killed
M2 M1 + drop ?. in listSkillsAtLevel 0 skills, initialized: true reproduces the round-1 blocker exactly — killed
M3 drop isSafeModeEnv() from the shim's rawLevels guard 0 skills in safe mode (child shows 9) daemon/child disagree — killed

All three fixes are load-bearing. M2 in particular reproduces the original failure mode byte for byte, so the ?. is not dead defensiveness.


2. The feature works, across every level and scope

Surface measured is the model-facing <available_skills> block captured off the wire from a mock OpenAI-compatible server — what the model actually receives, not an internal API.

matrix

Every claim in the PR body holds:

  • each of the four levels can be disabled individually, and only that level disappears (S2/S9/S10);
  • UNION merge — a workspace entry adds to and cannot remove a user-scope entry (S3);
  • non-enum junk is filtered at the CLI boundary, including case ("PROJECT""project"), and does not crash (S4);
  • disabling all four drops the block entirely with a clean exit (S5);
  • safe mode and bare mode nullify the setting (S6/S7, with S8 as the flag-matched control).

S3 also confirms the new doc note: skills.directories entries are discovered at the user level, so ["user"] hides those too — host-gamma disappears along with user-alpha.


3. The user-facing surfaces

/skills panel in the real TUI, same fixture, one setting added:

tui

And the slash-command surface, which is the thing an embedder actually wants gone:

slash


4. "Before accessing the filesystem" — verified at the syscall level

The PR body claims discovery skips disabled levels before touching disk. The PR's unit test asserts this against a mocked fs; I checked it on a real process with strace -e trace=openat,getdents64:

arm syscalls touching dist/bundled
control 10 — the bundled/ dir plus all 9 SKILL.md files
disabledLevels: ["bundled"] 0

Not a filtered listing — the directory is never opened.


5. Suites and merge state

suite result
packages/core src/skills/skill-manager.test.ts 81 / 81
packages/cli config.test.ts + settingsSchema.test.ts + workspace-skills-status.test.ts 367 / 367

npm run build clean; npm run generate:settings-schema regenerates packages/vscode-ide-companion/schemas/settings.schema.json byte-identical to the committed file; no conflict with current main (7918717).

On the collection failure @zhangxy-zju hit on latest main (qwen-logger.ts importing https rather than node:https): that import is indeed still on main (introduced by #8176), but the CLI suites collected and passed here without any substitution, so it appears environment-specific rather than a blocker for this PR.


6. Non-blocking observation — untrusted workspaces

Not a merge blocker; recording it so it is not lost.

The daemon shim drops disabledLevels when !workspaceTrusted || isSafeModeEnv(), but loadCliConfig's guard is only bareMode || safeMode, and an untrusted folder does not put the child into safe mode. So with folderTrust on, the workspace marked DO_NOT_TRUST, and disabledLevels at user scope:

config daemon GET /workspace/skills CLI child <available_skills> overlap
— (today, without the setting) 9 · all bundled 13 · incl. all 9 bundled daemon ⊂ child
["bundled"] @user 9 · all bundled 4 · no bundled disjoint

The root cause is the pre-existing isSafeMode: () => !workspaceTrusted || isSafeModeEnv() line in the shim, which this PR does not touch — it only mirrors the same predicate for rawLevels. The effect is a pre-first-prompt autocomplete offering bundled skills the child will then hide: the same flicker class round 2 fixed, in the one branch it does not reach. Cosmetic, untrusted-workspace-only, and arguably better handled by revisiting that pre-existing line separately.

Two smaller notes, both correct-as-designed:

  • skills.directories skills are invisible on the daemon-local path (host-gamma shows in the CLI/TUI listing but not in GET /workspace/skills) — the shim provides no getCustomSkillDirs. Pre-existing, unchanged here.
  • The daemon snapshots disabledLevels once per workspace at SkillManager construction, while the name-based disablements are re-read on every call. That asymmetry matches the declared schema semantics — disabledLevels and directories are requiresRestart: true, disabled/enabled/defaultDisabled are not.

LGTM. Feature verified end to end on the real CLI, the real daemon, and the real TUI; the round-1 blocker is fixed and mutation-pinned.

中文版

本地验证报告(第 2 轮)—— 真实 CLI 构建 + E2E

针对 5 轮 autofix 之后的 head fabe6e7 做复验,承接我第 1 轮的报告(当时在 136cbbf 上发现了一个阻断性的 daemon 回归)。

结论:可以合入。 阻断问题已修复,我通过把修复逐行改回去(mutation)确认了这些修复确实起作用。功能本身在我能驱动的所有层级、作用域和模式下都完全符合规格说明。文末记录一条非阻断性的观察。

在 Linux 上从源码构建:完整 npm run build,然后 node esbuild.config.js + copy_bundle_assets.js(确保 9 个内置 skill 真实落盘),在隔离的 HOME 下驱动,四个层级projectuserextensionbundled)都装了 skill,另外通过 skills.directories 加了一个宿主目录,共 13 个 skill。


1. 第 1 轮的阻断问题已修复,并已被钉死

我当时报告的回归是:SkillManager.listSkillsAtLevel 非可选地调用 getDisabledSkillLevels(),而 qwen serve 的 shim 并未提供该方法,refreshCachePromise.allSettled 吞掉了 TypeError,导致在默认配置下任意 workspace 的 GET /workspace/skills 都返回 0 个 skill,且 initialized: true

在当前 head 上驱动真实的 qwen serve daemon:

# 配置 initialized 总数 分层级
D1 —(对照) true 11 bundled 9 · project 1 · user 1
D2 ["bundled"] @workspace true 2 project 1 · user 1
D3 ["bundled"] + QWEN_CODE_SAFE_MODE=1 true 9 bundled 9

D1 就是回归检查——非零、正确。D2 说明 daemon 现在会遵守该配置(第 2 轮的 shim 接线)。D3 说明它在 safe mode 下与 CLI 子进程一致(第 3 轮的守卫)。

为确认这些都不是巧合,我把每处修复逐行从构建产物里改回去,每一组实验前后都重新审计部署的 chunk:

# 变异 daemon 结果 判定
M1 从 serve shim 里去掉 getDisabledSkillLevels 11 个 skill,其中 9 个 bundled 配置在 daemon 路径上被静默忽略 —— 被杀死
M2 M1 + 去掉 listSkillsAtLevel 里的 ?. 0 个 skill,initialized: true 精确复现第 1 轮的阻断问题 —— 被杀死
M3 从 shim 的 rawLevels 守卫里去掉 isSafeModeEnv() safe mode 下 0 个 skill(子进程是 9 个) daemon 与子进程不一致 —— 被杀死

三处修复都是有效负载。尤其 M2 精确复现了原始故障形态,所以那个 ?. 并非无用的防御性代码。


2. 功能在所有层级和作用域上都正确

测量面是从 mock OpenAI 兼容服务端在网络层抓到的、面向模型的 <available_skills> 块 —— 即模型真正收到的内容,而非内部 API。

matrix

PR 描述中的每一项主张都成立:

  • 四个层级都可以被单独禁用,且只有该层级消失(S2/S9/S10);
  • UNION 合并 —— workspace 条目只能叠加,无法移除 user 作用域的条目(S3);
  • 非枚举的垃圾值在 CLI 边界被过滤,包括大小写("PROJECT""project"),且不会崩溃(S4);
  • 四个层级全部禁用时整个块消失,进程干净退出(S5);
  • safe mode 与 bare mode 都会使该配置失效(S6/S7,S8 是参数对齐的对照组)。

S3 同时印证了新增的文档说明:skills.directories 的条目是在 user 层级被发现的,所以 ["user"] 也会把它们隐藏 —— host-gammauser-alpha 一起消失。


3. 用户可见的两个界面

真实 TUI 的 /skills 面板,同一套 fixture,只加了一个配置项:

tui

以及斜杠命令界面,这正是宿主方真正希望屏蔽掉的东西:

slash


4. "在访问文件系统之前" —— 在系统调用层面得到验证

PR 描述声称发现流程会在接触磁盘之前跳过被禁用的层级。PR 自带的单测是基于 mock 的 fs 做断言的;我用 strace -e trace=openat,getdents64 在真实进程上做了核对:

实验组 触及 dist/bundled 的系统调用
对照组 10 —— bundled/ 目录加全部 9 个 SKILL.md
disabledLevels: ["bundled"] 0

不是"列出来再过滤",而是那个目录根本没有被打开。


5. 测试套件与合入状态

套件 结果
packages/core src/skills/skill-manager.test.ts 81 / 81
packages/cli config.test.ts + settingsSchema.test.ts + workspace-skills-status.test.ts 367 / 367

npm run build 通过;npm run generate:settings-schema 重新生成的 packages/vscode-ide-companion/schemas/settings.schema.json 与提交的文件逐字节一致;与当前 main7918717)无冲突。

关于 @zhangxy-zju 在最新 main 上遇到的收集失败(qwen-logger.ts 把 Node 内置模块写成 https 而非 node:https):该 import 确实还在 main 上(由 #8176 引入),但这里的 CLI 套件未做任何替换就正常收集并通过了,所以看起来是环境相关,而不是本 PR 的阻断项。


6. 非阻断性观察 —— 未受信任的 workspace

不构成合入阻断,记录下来以免遗失。

daemon 的 shim 在 !workspaceTrusted || isSafeModeEnv() 时丢弃 disabledLevels,但 loadCliConfig 的守卫只有 bareMode || safeMode,而未受信任的目录不会让子进程进入 safe mode。因此当开启 folderTrust、workspace 被标记为 DO_NOT_TRUST、且 disabledLevels 位于 user 作用域时:

配置 daemon GET /workspace/skills CLI 子进程 <available_skills> 交集
—(当前不带该配置的行为) 9 · 全为 bundled 13 · 含全部 9 个 bundled daemon ⊂ 子进程
["bundled"] @user 9 · 全为 bundled 4 · 无 bundled 不相交

根因是 shim 中既有的 isSafeMode: () => !workspaceTrusted || isSafeModeEnv() 这一行,本 PR 并未改动它 —— 只是对 rawLevels 沿用了同一谓词。表现为首个 prompt 之前的自动补全会提示子进程随后会隐藏的内置 skill:与第 2 轮修复的是同一类闪烁,只是发生在它未覆盖到的那个分支。属于观感问题,仅限未受信任的 workspace,更适合单独回头处理那一行既有代码。

另有两条较小的说明,都属于"设计如此":

  • skills.directories 提供的 skill 在 daemon 本地路径上不可见(host-gamma 出现在 CLI/TUI 列表中,但不在 GET /workspace/skills 里)—— shim 未提供 getCustomSkillDirs。这是既有行为,本 PR 未改变。
  • daemon 在 SkillManager 构造时对 disabledLevels 做一次快照,而基于名字的 disablements 每次调用都会重新读取。这一不对称与 schema 声明的语义一致 —— disabledLevelsdirectoriesrequiresRestart: true,而 disabled/enabled/defaultDisabled 不是。

LGTM。 功能在真实 CLI、真实 daemon 和真实 TUI 上端到端验证通过;第 1 轮的阻断问题已修复,并已用 mutation 钉死。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No action required this round

The feedback since the last evaluation contains no actionable items:

  • @zhangxy-zju posted a status update confirming the S1 test-completeness suggestion was addressed in fabe6e7bce. This is informational, not a change request.
  • @wenshao posted a detailed round-2 local verification report (real CLI build + E2E, mutation testing, strace-level filesystem verification) concluding with LGTM — merge-ready. No changes requested.
  • Deferred non-Critical feedback from the automated reviewer is in Critical-only mode and must not be modified, resolved, or replied to.
  • No inline comments, no reviews, and no failed checks are present.

No code changes were made.

中文说明

本轮无需操作

自上次评估以来的反馈中不包含任何需要处理的事项:

  • @zhangxy-zju 发布了一条状态更新,确认 S1 测试完整性建议已在 fabe6e7bce 中处理。这是信息性说明,不是变更请求。
  • @wenshao 发布了详细的第 2 轮本地验证报告(真实 CLI 构建 + E2E、mutation 测试、strace 级文件系统验证),结论为 LGTM——可以合入。未请求任何变更。
  • 自动评审的非 Critical 延后反馈处于仅处理 Critical 的模式,不得修改、解决或代为回复。
  • 不存在行内评论评审失败的检查

未做任何代码变更。

Deferred non-Critical feedback

Critical-only mode is active after 5 change-producing rounds: the automated reviewer's non-Critical suggestions below are deferred and stay open for human follow-up — do not modify code, resolve threads, or reply on their behalf. Maintainer feedback defers only once its author has already had 2 regular feedback batches addressed in this window's Critical-only tail — an account can host an automated reviewer loop, so the brake keys on measured regeneration, not identity; authors at their budget, if any, are named below. (A maintainer can lift the mode itself: @qwen-code /retry starts a fresh counting window.)

中文说明

完成 5 个产生改动的轮次后进入仅处理 Critical 的模式:以上为自动评审的非 Critical 建议,予以延后、保持开放并留待人工跟进——不要为其修改代码、解决线程或代为回复。维护者的反馈仅在其本人于本窗口 Critical-only 阶段已被处理 2 批常规反馈之后才会被延后——账号可能挂着自动评审循环,因此刹车依据实测的再生频度而非身份;达到预算的作者(如有)在下方点名。(如需解除该模式,评论 @qwen-code /retry 即可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@wenshao

wenshao commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 36 passed · 0 failed · 36 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:36 通过 · 0 失败 · 36 总计

Verification report (report.md)

# PR 8057 — Deep Verification Report (follow-up round 2)

**Verdict: `merge-ready`** — assertions **36 pass / 0 fail / 36 total** (every
scripted observation matched its prediction; the single finding carried from
the previous round, **S1**, is now **fixed** and proven so by re-running its
mutation — see _Previous-finding status_ and the mutation matrix). Verified
head `fabe6e7bcea16cd76ca8e4d80f8aacb327c9b0e9` (`HEAD^2`); base tip
`c50137cf86adf7bc39c279ff5185db8dab7f2985` (`HEAD^1`). This is a **follow-up
round**: a substantive `previous-report.md` exists (it verified head
`ba09ffd0e` as `merge-ready` with one non-blocking finding, S1).

> Assertion tally (recomputable from `logs/`): `ab-skillmanager` 14/0 +
> `union-merge` 4/0 + `daemon-wiring` 10/0 + `mutation-assert` 8/0 = **36**.
> `fail` counts only *unexpected* outcomes; there were none. The two mutation
> cells that go RED (M1-keep, M2-skip) are *predicted* reds — the adjudicator
> asserts `observed == predicted`, so a red that matches its prediction is a
> PASS (the "expected failures are passes" rule). The M1 cell, which
> *survived* in the previous round, now matches a RED prediction: that is the
> empirical proof that S1 is fixed.

<details>
<summary>中文摘要</summary>

- **结论:`merge-ready`**,且上一轮唯一的 finding **S1 已修复并被实证**。本轮
  head `fabe6e7bc` 是一个**纯测试**提交("test(core): pin partial config skill
  discovery"),它把上一轮判为空测试的 `should not crash when config lacks
  getDisabledSkillLevels` 重写为 `should keep discovery working when config lacks
  getDisabledSkillLevels`:现在它先 `mockReaddirForLevels(['bundled'])` +
  `setupReviewSkillMocks()`,再断言 partial config 真的把 bundled skill
  `['review','simplify']` 加载出来——正是上一轮 suggested fix 的写法。
- **S1 修复的实证(重新测量,非对照旧报告)**:把 guard 的 `?.` 改成 `.`(即 M1,
  上一轮的 survivor),新测试现在**变红**,且红在它**本应断言的语句**上
  (`expected [] to deeply equal ['review','simplify']`,skill-manager.test.ts:1192)——
  因为 partial config 在 `refreshCache` 的 `Promise.allSettled` 里对每个 level 抛
  `TypeError`,bundled 静默变 `[]`,强化后的断言能看见。详见下文「Mutation matrix」
  表与 `02-mutation-matrix-m1-now-pinned.png`。M1 由 survived → killed,S1 关闭。
- **功能本身仍 load-bearing(A/B 重新测量)**:`disabledLevels=["bundled"]` 下 head
  加载 0 个 bundled,base 对照加载全部 9 个;默认无配置时 head≡base(inert)。详见
  「Central claim」表与 `01-ab-skillmanager-head-vs-base.png`。
- **次要声明重新测量均通过**:UNION 合并(user`["bundled"]`+ws`["project"]`→
  `["bundled","project"]`,去重正确,默认 absent);daemon 端到端隐藏 bundled、保留
  project、safe mode 下与 CLI 子会话一致地把该配置置空。
- **无新 finding**。提交 schema 与生成器输出字节一致(重新生成验证)。
- **未覆盖**:per-commit 归因(depth-2 shallow,仅校验聚合 `HEAD^1..HEAD`);全仓
  test/typecheck/lint(仅跑受影响 suite);CI 注入 `QWEN_HOME` 导致 4 个**既有**
  user-level 测试失败(上一轮已用 unset 做 A/A 证明为环境因素,本轮同样 unset 跑);
  Windows/macOS 手工 CLI。

数字请直接引用下文各表,避免与摘要重复而可能不一致。

</details>

## Previous-finding status (follow-up round)

The previous round (verified head `ba09ffd0e`) reported one finding. It is
re-measured here at the new head `fabe6e7bc`, not diffed off the old text.

| # | finding | severity (then) | status at new head | agrees? |
| - | ------- | --------------- | ------------------ | ------- |
| S1 | the regression-guard test `should not crash when config lacks getDisabledSkillLevels` was vacuous — it asserted only `resolves.toBeDefined()`, which `[]` satisfies, so mutating `?.`→`.` left it green while every level silently failed inside `refreshCache`'s `Promise.allSettled` | Suggestion / completeness-reporting (non-blocking) | **fixed** | yes — the prior classification (defense-in-depth guard, correctly non-blocking) was right; the author then closed the gap anyway |

**Re-measurement of S1.** The new commit renames and strengthens that test to
`should keep discovery working when config lacks getDisabledSkillLevels`, which
now sets up the bundled mocks and asserts the bundled names
`['review','simplify']` are actually surfaced — exactly the prior round's
suggested fix. I re-ran the mutation matrix against the new head:

- control (pristine source): both new tests **GREEN** (suite live).
- **M1** (`?.`→`.`): the strengthened test now goes **RED** on its intended
  assertion (`expected [] to deeply equal ['review','simplify']` at
  `skill-manager.test.ts:1192`); the sibling feature test stays GREEN because
  its `makeFakeConfig` builds a real `Config` that *has* the method. **M1 went
  survived → killed.**
- M2 (remove the skip block): the feature test still goes **RED** (`simplify`
  reappears), so the feature remains pinned.

Witness `02-mutation-matrix-m1-now-pinned.png`; raw logs `logs/mut-*.log`;
scripted adjudication `mutation-assert.mjs` (8/8). S1 is therefore resolved and
carries no weight into this round's verdict.

## Central claim + A/B load-bearing proof (re-measured)

**Central claim:** `SkillManager.listSkillsAtLevel` skips a level whose name is
in `config.getDisabledSkillLevels()`, *before* touching the filesystem, so
`skills.disabledLevels: ["bundled"]` hides every bundled skill while
project/user skills survive; and the change is inert when the setting is
absent. The production source for this claim (`skill-manager.ts:962-965` guard;
`getDisabledSkillLevels` in core `config.ts`) is **byte-identical** to the
previous round — the new commit is test-only — but per the follow-up rule I
re-ran the A/B at the new head rather than carrying the old cells.

The control is the head build with **only** the guard hunk reverted, written to
a sibling file `skill-manager.base.js` in the same `dist/src/skills/` dir so its
relative imports and its module-relative `bundledSkillsDir`
(`dist/src/skills/bundled`) resolve identically; the sibling is deleted after
the run (verified absent). The PR leaves the dependency tree untouched and
`skill-manager.js` imports **no** `@qwen-code/*` workspace (verified by grep),
so the internal-symlink confound does not apply; the base emission was asserted
to lack the guard (`?.()` present in head, absent in base). Harness
`ab-skillmanager.mjs` drives the real compiled `SkillManager` from each build
against a real temp fs (a project skill, a user skill) and the 9 real bundled
skills; `HOME` isolated, `QWEN_HOME` unset. Witness
`01-ab-skillmanager-head-vs-base.png`.

| Cell | build | `disabledLevels` | observable oracle (skills loaded by level) | result |
| ---- | ----- | ---------------- | ------------------------------------------ | ------ |
| 1 | **head** | `["bundled"]` | `project:[proj-skill], user:[user-skill]`; bundled absent | bundled dropped, others kept |
| 2 | **base** (control) | `["bundled"]` | `bundled:[all 9]` + proj + user | guard absent ⇒ setting ignored |
| 3 | head | `[]` (default) | `bundled:[all 9]` + proj + user | default unchanged |
| 4 | base | `[]` (A/A) | `bundled:[all 9]` + proj + user | head default ≡ base default |
| 5 | head | shim lacks method | `bundled:[all 9]`, no throw | optional-chain guard holds |
| 6 | base | shim lacks method | `bundled:[all 9]`, no throw | base never calls the method |

The load-bearing pair is **Cell 1 vs Cell 2**: identical setting, identical
real skill world, identical harness — head loads 0 bundled, base loads 9. Cells
3/4 prove the change is inert when unset; 5/6 prove the `?.` guard and that the
call site is new in head. `ab-skillmanager` asserts **14/0**.

## Secondary claim 1 — UNION merge across scopes (re-measured)

Harness `union-merge.mjs` drives the real compiled `loadSettings` with real
files (user scope `$QWEN_HOME/settings.json`, workspace scope
`<ws>/.qwen/settings.json`). Witness `04-union-merge-across-scopes.png`.

| input (user / workspace) | `merged.skills.disabledLevels` | assertion |
| ------------------------ | ------------------------------ | --------- |
| `["bundled"]` / `["project"]` | `["bundled","project"]` | union, not last-wins (len 2) |
| `["bundled"]` / `["bundled"]` | `["bundled"]` | de-duplicated |
| `{}` / `{}` | `undefined` | default absent |

`union-merge` asserts **4/0**. The schema entry is `mergeStrategy: UNION`,
`requiresRestart: true`, `items.enum = [project,user,extension,bundled]`,
`default: undefined` (confirmed by re-running the generator and by
`settingsSchema.test.ts`).

## Secondary claim 2 — daemon wiring end-to-end (re-measured)

Harness `daemon-wiring.mjs` drives the real compiled
`createWorkspaceSkillsStatusProvider` against real workspaces + real bundled
skills (no `listSkills` mock). Witness `03-daemon-wiring-safemode-nullifies.png`.

| scenario | oracle | result |
| -------- | ------ | ------ |
| trusted + `disabledLevels=["bundled"]` | `initialized=true`; no `bundled`-level skill; `review`/`simplify` absent; `proj-skill` present | hidden via daemon shim |
| trusted, no setting (control) | `review` present, `proj-skill` present, bundled=9 | default intact |
| trusted + `QWEN_CODE_SAFE_MODE=1` + `["bundled"]` | `review` present, bundled=9 | safe mode nullifies the setting (mirrors the CLI child) |

`daemon-wiring` asserts **10/0**.

## Committed generated artifact — schema regeneration (re-verified)

Re-ran `npm run generate:settings-schema` and diffed its output against the
committed `packages/vscode-ide-companion/schemas/settings.schema.json`:
**byte-identical** (then restored; `git status` clean). The artifact was
genuinely generated, not hand-edited. Log `logs/schema-regen.log`.

## Targeted gates (affected suites, re-run at new head)

Run with `QWEN_HOME` unset (see _Not covered_ for why). All green and matching
the previous round's counts:

| suite | result |
| ----- | ------ |
| `packages/core` `skill-manager.test.ts` | **81 passed** (incl. the 2 new tests) |
| `packages/cli` `config.test.ts` | **319 passed** (incl. the `disabledLevels` tests) |
| `packages/cli` `settingsSchema.test.ts` | **37 passed** (incl. the union/restart-required assertion) |
| `packages/cli` `workspace-skills-status.test.ts` | **11 passed** (incl. the daemon-wiring tests) |

## Mutation matrix (re-measured; S1's survivor now killed)

Rerunnable, self-restoring: `mutation-matrix.sh` (witness
`02-mutation-matrix-m1-now-pinned.png`); scripted adjudication
`mutation-assert.mjs` reads the raw vitest witness logs and asserts
observed-vs-predicted per cell (8/8). Controls (both new tests green on
pristine source) confirm the suite is live.

| # | guard mutated | test that should pin it | observed | pinned? |
| - | ------------- | ----------------------- | -------- | ------- |
| M1 | `?.` → `.` (skill-manager.ts:962) | `should keep discovery working when config lacks getDisabledSkillLevels` | **RED** — `expected [] to deeply equal ['review','simplify']` (line 1192) | **YES — killed** (was the S1 survivor) |
| M2 | remove skip-disabled-levels block | `should skip disabled skill levels without scanning them` | **RED** — `simplify`/`bundled` reappears (line 1163) | **YES** |

The M1 red is on the **intended assertion** (the behavioral mismatch the test
exists to catch — empty cache vs the two bundled names), not a compile or
fixture break; the adjudicator checks for that pattern explicitly. M2 proves
the feature itself is load-bearing. With M1 now killed, the matrix has **no
survivors**, so the previous round's completeness-reporting note (S1) no longer
applies and there is nothing to carry forward as a gap.

## Findings

**None.** The only finding from the previous round (S1) is fixed and proven so
above; no new defect, regression, or vacuity was observed in this round's
re-measurement.

## Not covered

- **Per-commit attribution.** The checkout is depth-2 (shallow); only the merge
  commit, `HEAD^1`, and `HEAD^2` are reachable, so the 18 commits in the PR
  metadata could not be individually exercised. Verified the aggregate
  `HEAD^1..HEAD` diff. The new commit `fabe6e7bc` is test-only (its diff touches
  only `skill-manager.test.ts`), so per-commit attribution of the *production*
  change is moot — the production surface is the same one the previous round
  A/B-proved, re-measured here.
- **Full-repo gates.** Ran only the affected suites (table above); did not run
  the whole `npm run test`, repo-wide `typecheck`, or `lint` (the PR's own CI
  covers those; my A/B needed the affected suites from a known-clean state).
- **The 4 pre-existing core `skill-manager` failures under CI `QWEN_HOME`.** As
  in the previous round, with the CI-injected `QWEN_HOME` four *pre-existing*
  user-level tests fail because they mock `os.homedir()` while
  `Storage.getGlobalQwenDir()` reads `$QWEN_HOME`. The previous round's A/A
  proof (unset → 81/81 green; the four tests absent from the PR diff) stands,
  and all targeted suites here were run with `QWEN_HOME` unset.
- **`["user"]` also hides `skills.directories` entries** and the
  safe-mode-+-`["bundled"]` corner via a non-nullifying consumer — both
  documented / unreachable in production; not exercised (unchanged from the
  previous round's scope).
- **Windows / macOS manual CLI** — not run (Linux sandbox); cross-platform
  coverage is the PR's own CI.

## Methodology

Environment: CI verify job, container, no GitHub token. Working tree =
`pull/8057/merge` at depth 2; `HEAD^1`/`HEAD^2` used as base/PR-head per the
merge-ref contract; the head build was already present. This is a follow-up
round, so every measurement was **re-run at the new head** rather than carried
from the old report (the previous round's head/base differ from this round's).
The production closure for the central claim is byte-identical between rounds
(the new commit is test-only), which I confirmed by `git diff HEAD^1..HEAD`
over `skill-manager.ts` and core `config.ts`; that fact is supporting context,
not a substitute for the re-run. Each harness is mock-free w.r.t. the unit
under test: `ab-skillmanager.mjs` drives the real compiled `SkillManager` from
the head dist and from a hunk-reverted sibling control (same dir, identical
resolution; sibling deleted after); `union-merge.mjs` drives the real compiled
`loadSettings` with real settings files; `daemon-wiring.mjs` drives the real
compiled `createWorkspaceSkillsStatusProvider` against real workspaces and the
real bundled skills; `mutation-matrix.sh` mutates the real source under vitest
and restores via `git checkout --` (verified pristine after). Image evidence
was rendered with `node scripts/verify-capture.mjs` (xterm-headless + sharp, no
browser/pty); filenames are kebab-case captions referenced above. Raw per-run
output lives in `logs/`; harnesses in the artifact root. The working tree was
left pristine after every mutation (`git status` shows no tracked changes; the
guard present at `skill-manager.ts:962`). Assertion mapping: 14 + 4 + 10 + 8 =
36 pass, 0 fail; the 8 mutation-assert checks include the M1-killed cell, its
intended-assertion check, the pristine check, and the five other predicted
colors — all scripted comparisons that executed.

Evidence images

01-ab-skillmanager-head-vs-base

02-mutation-matrix-m1-now-pinned

03-daemon-wiring-safemode-nullifies

04-union-merge-across-scopes

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@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 added this pull request to the merge queue Aug 1, 2026
Merged via the queue into QwenLM:main with commit de02266 Aug 1, 2026
68 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(skills): add a single switch to disable all bundled skills

5 participants