feat: load custom output styles from ~/.qwen/output-styles and .qwen/output-styles - #10761
Conversation
…output-styles A style is a Markdown file whose body is the prompt section, with an optional frontmatter (name, description, keep-coding-instructions) that defaults to the file name, the first body line, and false. The catalog is built-ins plus the user's files plus, in a trusted workspace, the project's, with project > user > built-in precedence by case-insensitive name. The startup resolver, /output-style <name>, and the picker all read the catalog, so a new file needs no restart. --bare and --safe-mode keep the built-ins only; invalid files are skipped and logged rather than fatal. Follows QwenLM#10683 as the custom-style slice it deferred.
|
Thanks for the PR! Template looks good ✓ — all sections present, bilingual body, reviewer test plan with before/after. Problem: this is an observed gap, not a theoretical one. The output-style chain already shipped — #9565 (prompt layer), #10282 (per-turn reminder), #10283 (startup selection), #10683 ( Direction: aligned. Claude Code's changelog shows custom/project output styles are an established cross-tool expectation — including the Size: touches core paths ( Approach: scope feels right and matches what I'd have done independently: a core catalog loader ( Risk: no elevated-risk path signals (no matches against the revert-correlated paths). Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 各节齐全,中英双语,附带评审者测试计划和 before/after。 问题: 这是已观测到的缺口,不是理论问题。输出风格链已经落地 —— #9565(提示词层)、#10282(每轮提醒)、#10283(启动时选择)、#10683( 方向: 对齐。Claude Code 的 changelog 显示自定义/项目级输出风格已是跨工具的既有预期 —— 包括本 PR 沿用的 规模: 触及核心路径( 方案: 范围合理,与我独立设想的做法一致:core 目录加载器( 风险: 无升级风险信号(未命中与回滚相关的高风险路径)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewI wrote my independent proposal before reading the diff — a core catalog loader plus wiring at the three existing resolution points, trust-gated project reads — and this PR lands essentially that shape, so there was no simpler path it missed. What's here is careful:
No critical blockers, no convention violations (kebab-case filenames, colocated tests, ESM, no Files changed (22)
Test evidence — the PR's own CIThe table below reflects the PR's checks at the reviewed commit, fetched via the API; I have not run any of the PR's code or tests (static review only, per triage rules). CI is not settled yet: the ubuntu unit suite is still running, and it is the one to watch. The macOS/Windows test jobs and the second integration lane are skipped on this fork PR, so platform coverage comes from CI only via the green Desktop Shell checks. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The one red check is pre-existing on Sandboxed verification would settle the behavioural claim: 中文说明代码审查读 diff 之前我先独立写了方案 —— core 目录加载器 + 三个既有解析点接线 + 信任门控的项目读取 —— 本 PR 的形状与之一致,没有更简的漏网路径。实现很细致:
无关键阻塞项,无约定违规(kebab-case 文件名、测试与源码同目录、ESM、无 测试证据上表来自评审提交上的 PR 自身 CI(经 API 获取);按 triage 规则,本次为纯静态审查,未运行任何 PR 代码或测试。CI 尚未定型:ubuntu 单元测试仍在运行,是最需要关注的一项。本 fork PR 上 macOS/Windows 测试与第二条集成通道被跳过,平台覆盖仅来自两个绿色的 Desktop Shell 检查。 唯一红色检查是 沙箱验证可以坐实行为主张: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean review across every stage; the only reservations are external to the diff (a red board inherited from Stepping back: this is the natural fourth slice of a shipped feature chain, filling an extension point that What keeps this at 4 rather than 5: I can't attest to the behaviour end-to-end from a static review. The author's transcript and the 496 lines of new tests are good evidence, but the ubuntu unit suite is still running on this commit, and the integration lane is red — verified as a pre-existing Approval deferred until CI lands green on 中文说明回顾整体:这是已落地功能链上顺理成章的第四个切片,填补的正是 之所以是 4 分而非 5 分:静态审查无法为端到端行为背书。作者的会话记录与 496 行新测试是很好的证据,但该提交上的 ubuntu 单元测试仍在运行,集成通道是红的 —— 已核实为 — Qwen Code · qwen3.8-max Reviewed at |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran locally on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": none of my scope was cut short — I read all 367 assigned diff lines (511-877) un-truncated and the full post-change source of every production file in it. I did….
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Test Plan (not a blocker): src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more.
Deferred under the convergence posture (round 1, not a blocker) — recorded, not requested in this round:
docs/design/2026-09-02-output-style-files.md:50 — [probe] design doc + PR description claim a clear error when a style file is deleted between open and select; the code silently applies the cached definition and persists an unresolvable nam…packages/cli/src/config/config.ts:1374 — [probe] the Unknown output style warning states something false when the catalog was deliberately narrowed by --bare/--safe-mode or an untrusted workspace, and names neither causepackages/cli/src/ui/commands/output-style-utils.ts:35 — [probe] the picker shows the freshly read definition as current while the session runs the startup one, with no re-apply cue after the active file is editedpackages/cli/src/ui/commands/output-style-utils.ts:59 — [probe] getBuiltInOutputStyle left exported with no production caller; two public resolvers now differ only by scopepackages/cli/src/ui/components/OutputStyleDialog.tsx:41 — [probe] the unbounded picker list windows at 10 rows with no scroll affordance (neither showScrollArrows nor maxItemsToShow is passed)packages/cli/src/ui/components/OutputStyleDialog.tsx:50 — [probe] the (user)/(project) source marker is appended last in a wrap=truncate label, so it is cut off for any description over ~50 charspackages/cli/src/ui/hooks/use-output-style-command.test.ts:89 — [probe] the only degraded-catalog test fixtures a rejection the loader cannot produce, so a guard placed inside the catch passes green and fixes nothingpackages/core/src/core/output-style-files.ts:59 — [probe] the filename-derived name is not trimmed, so a trailing space silently resolves to the built-in or to nothingpackages/core/src/core/output-style-files.ts:62 — [probe] the declared name is coerced with String() and no type check; a YAML mapping throws with no reachable diagnostic and an array becomes a comma-joined namepackages/core/src/core/output-style-files.ts:128 — [probe] deriveDescription emphasis strip deletes content characters (snake_case to snakecase, __init__ to init, *.md to .md)packages/core/src/core/output-style-files.ts:227 — [probe] the home-directory guard compares roots not the two directories, so it mis-fires on an empty homedir and on QWEN_HOME relocation
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran locally on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":none of my scope was cut short — I read all 367 assigned diff lines (511-877) un-truncated and the full post-change source of every production file in it. I did…。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
Test Plan(非阻断):src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more。
收敛姿态下延后(第 1 轮,非阻断)——已记录,本轮不要求修改:共 11 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max-2026-09-02 via Qwen Code /review (v0.22.3)
|
@qwen-code /takeover from 1 |
|
🤝 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. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). This window's round counter starts at 1 (the rounds this PR spent in review before takeover), so the Critical-only brake engages after 4 more change-producing round(s) instead of a full fresh 5. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。本窗口轮次计数从 1 起算(即本 PR 托管前已进行的评审轮数),因此再经过 4 个产生改动的轮次即进入 Critical-only,而非重新计满 5 轮。移除 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
14 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R1-61 seven unusable E2E plan steps — still standing; already reported (comment 3914106020)
- R1-21 keepCodingInstructions:false reachable via config.getOutputStyle/arena prompts — still standing; already reported (comment 3914106031)
- R1-53 project trust gate enforced only on the establishing side — still standing; already reported (comment 3914106042)
- R1-62 four stale surfaces describing the outputStyle value — still standing; already reported (comment 3914106056)
- R1-8 persist scope chosen by key ownership, ignoring style source — still standing; already reported (comment 3914106066)
- R1-6 initialIndex degrades to 0 when the active style is not listed — still standing; already reported (comment 3914106079)
- R1-23 unawaited dialog open can re-open a dismissed dialog — still standing; already reported (comment 3914106089)
- R1-60 pattern: tests assert outcomes without discriminating the mechanism — still standing; already reported (comment 3914106093)
- R1-48 size bound 41x the house bound, enforced on stat.size while the read is unbounded — still standing; already reported (comment 3914106104)
- R1-7 frontmatter fence grammar too strict and too loose — still standing; already reported (comment 3914106112)
- R1-58 HTML comments survive into prompt and derived description — still standing; already reported (comment 3914106124)
- R1-20 a file shadowing a built-in inherits keep-coding-instructions:false — still standing; already reported (comment 3914106132)
- R1-22 keep-coding-instructions coerced by exact two-value comparison — still standing; already reported (comment 3914106141)
- R1-49 every *.md entry decoded as UTF-8 with no text sniff — still standing; already reported (comment 3914106150)
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour.
Not reviewed: build-and-test — test-efficacy probe inconclusive (the probe scratch tree has no built dist, so no green baseline); full packages/cli and packages/core suites timed out at the per-run budget on this saturated runner; every test file this PR adds or changes was run individually and passes.
Test Plan (not a blocker): src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 7 more.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/commands/output-style-command.test.ts:105 — [probe] the listing-message assertion matches only the i18n key — vacuous under the t mock (round-2 code-age rule: anchored on code unchanged since round 1, which read it and d…packages/cli/src/config/config.ts:1374 — [probe] the startup unknown-style warning's catalog names have no test — reverting to built-ins ships green (round-2 code-age rule)packages/core/src/core/output-style-files.test.ts:91 — [probe] the fallback-name validation path has no test pin — a bare default.md mutant escapes the suite green (round-2 code-age rule)packages/core/src/core/output-style-files.ts:119 — [probe] deriveDescription mixes and ~~~ fence toggles — the description can come from inside a code block (round-2 code-age rule)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 14 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:reverse audit — did not converge within the reverse-audit round cap of 5。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour。
未审查:build-and-test — test-efficacy probe inconclusive (the probe scratch tree has no built dist, so no green baseline); full packages/cli and packages/core suites timed out at the per-run budget on this saturated runner; every test file this PR adds or changes was run individually and passes。
Test Plan(非阻断):src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 7 more。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 4 条(原文未翻译,列表见上方英文部分)。
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.3)
|
@qwen-code /takeover stop |
|
👋 Takeover released: the autofix loop will no longer engage this PR (an in-flight round, if any, completes its bounded work). Re-apply 中文说明👋 已释放:autofix 循环不再介入此 PR(在飞的一轮如有,将完成其有界工作)。重新打上 |
Two ways a style file could carry content it was never meant to.
A style file's body goes into the system prompt verbatim, and the loader
followed links with only an `fs.stat`. A repo can commit
`.qwen/output-styles/notes.md -> ~/.aws/credentials` plus a `.qwen/settings.json`
naming it; folder trust is off by default, so `git clone && qwen` treats the
workspace as trusted, `stat.isFile()` reports on the *target*, and the secret
is embedded under `# Output Style: notes` with no tool call and no approval.
A hard link (`nlink > 1`) and a symlinked ancestor (a checked-in
`.qwen -> /outside`, which a final-component check cannot see) reach the same
place.
This mirrors `readLoopTaskFile`, which guards the identical sink. A project
file is read with `lstat`, so a link is not a regular file and never reaches
the read -- confinement alone would wave through `notes.md -> .env`, whose
target is inside the root. A user file may be a link, because
`~/.qwen/output-styles/x.md -> ~/dotfiles/x.md` is an ordinary setup, but its
canonical path must stay inside the home root. Both refuse `nlink > 1` and
both confine the realpath.
Separately, the description reached the terminal un-sanitized while the
sibling `name` from the same untrusted file was refused for exactly those
characters, and a declared description had no length bound: an OSC-8 hyperlink
made a picker row link somewhere it did not say, and U+202E reordered the row
so the `(project)` marker read as something else. Both description sources now
go through `stripAnsiAndControl` plus a `\p{Cf}` strip, collapse to one line,
and share the cap the derived description already used.
Each guard is pinned by a case that goes red without it, including the
in-workspace symlink that distinguishes `lstat` from confinement.
…t the name The docs this PR adds tell users a file "overrides a built-in of that name", so a user customizing Learning ships `~/.qwen/output-styles/Learning.md`. The lowercase dedupe key shadows the built-in and the name keeps the basename's capital L, so `resolveEffectiveOutputStyle`'s `name === 'Learning'` check fires on the user's own prompt: `qwen -p` then carries no output style at all -- no prompt section, no turn reminder -- and `warnAboutOutputStyle` stays quiet because the name resolved, so it is a silent no-style run. Renaming the file `learning.md` shadows the identical built-in and *is* applied, so two spellings of one name behaved differently in a module that dedupes and looks names up case-insensitively everywhere else. The rule exists because the built-in Learning prompt tells the model to stop and wait for a reply a headless run cannot send. A custom file that happens to take the name carries no such instruction, so the check keys on `source === 'built-in'` as well.
`openOutputStyleDialog` suspends on `loadSessionOutputStyles` and then sets
the dialog open. A dismissal landing in that window leaves the continuation
queued, so the dialog the user just closed re-opens and the next Enter is
captured by `RadioButtonSelect`, which writes `general.outputStyle` with
`{ throwOnWriteFailure: true }`. An open generation, retired both by a newer
open and by a selection, makes the stale continuation a no-op.
The defect had no in-suite witness because this diff deleted
`act(() => result.current.openOutputStyleDialog())` from `clears the style
when "default" is chosen` rather than adapting it with the siblings'
`waitFor`. Its closing `expect(isOutputStyleDialogOpen).toBe(false)` then read
the `useState(false)` initial value and passed for any implementation,
including one that never closes. The open is restored with the wait, and a new
case dismisses an open and then resolves its load, asserting the dialog stays
closed.
Startup style resolution is renderer-independent, so a custom style can be
active under OpenTUI via `--output-style`, `general.outputStyle`, or the
renderer-agnostic `/output-style <name>`. The dialog built its list from
`BUILT_IN_OUTPUT_STYLES` alone, so the active style was not in it,
`findIndex` returned `-1`, and `Math.max(0, -1)` highlighted `default` --
contradicting the comment two lines below claiming index 0 tells the truth.
One Enter then applied `undefined` and persisted `"outputStyle": "default"`
with `{ throwOnWriteFailure: true }`, clearing the user's own setting. The
docs line this PR adds, that custom styles appear in the picker labelled with
their source, was false on this renderer.
The dialog now loads through `loadSessionOutputStyles`, which carries the ink
path's trust gate, and labels non-built-ins with their source the way
`describe()` does. `useState` runs its initialiser once, so the pre-selection
is re-derived when the catalog arrives -- otherwise the `-1 -> 0` clamp would
survive the fix.
The test stubs the catalog load so it never reads the developer's own
`~/.qwen/output-styles`, and asserts a `user` style renders as a selectable
row and is the pre-selected one.
All five Criticals addressed —
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
10 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R2-1 design doc picker bullet backwards (clear error vs silent snapshot apply) — still standing; already reported (comment 3921888866)
- R2-2 file-name-derived style name skips trimming — still standing; already reported (comment 3921888873)
- R2-3 non-string YAML name String()-coerced without type check — still standing; already reported (comment 3921888888)
- R2-4 home-directory guard compares os.homedir() while the user level resolves through QWEN_HOME — still standing; already reported (comment 3921888898)
- R1-60 occurrence 12 — vacuous i18n-key assertion at output-style-command.test.ts:105 — already reported (comment 3914106093)
- R1-60 occurrence 13 — startup unknown-style warning list at config.ts:1374 has no test pin — already reported (comment 3914106093)
- R1-62 occurrence — settings.md:808 --output-style row still a closed built-in enumeration — already reported (comment 3914106056)
- R1-62 occurrence — settings.md:83 "custom style file's name" wording — already reported (comment 3914106056)
- R1-61 occurrence 6 — E2E plan scenario 8 cannot exercise the trust gate — already reported (comment 3914106020)
- R1-6 ink picker initialIndex fallback re-discovered at OutputStyleDialog.tsx:49-51 — already reported (comment 3914106079)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour.
Not reviewed: build-and-test — test-efficacy probe could not validate its positive control (the probe scratch tree tripped the repo's vitest globalSetup dist-guard, harnessValidated null); full packages/cli and packages/core suites timed out at their per-run deadlines on this runner; every test file this PR adds or changes was run individually and passes (494/494).
Not explored to full depth (tool budget reached): chunk 3: running the new tests in packages/cli/src/config/config.test.ts and packages/cli/src/ui/commands/output-style-command.test.ts — npm run build (the documen….
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Test Plan (not a blocker): src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more.
Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:
packages/core/src/core/output-style-files.ts:150 — [probe] deriveDescription emphasis strip corrupts identifiers (MAX_RETRY_COUNT → MAXRETRYCOUNT) in picker descriptions (round-3 code-age rule: anchored on code unchanged since round 2, whic…packages/core/src/core/output-style-files.ts:290 — [probe] getUserOutputStylesRoot() branches (QWEN_HOME relocation vs home-root dotfiles) have no discriminating test; both regression mutants ship green (round-3 code-age rule: anchored on c…packages/core/src/core/output-style-files.ts:323 — [probe] includeProject home-dir skip unguarded for empty os.homedir(); path.resolve('') is cwd, so a trusted workspace's project styles silently never load (round-3 code-age rule: anchored …
Convergence: round 3 posted 6 inline comment(s), 6 of them reported for the first time; the previous round posted 9 (4 new). Findings keep coming back to the same files: packages/cli/src/ui/hooks/use-output-style-command.test.ts (findings in round 1; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 10 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour。
未审查:build-and-test — test-efficacy probe could not validate its positive control (the probe scratch tree tripped the repo's vitest globalSetup dist-guard, harnessValidated null); full packages/cli and packages/core suites timed out at their per-run deadlines on this runner; every test file this PR adds or changes was run individually and passes (494/494)。
未探索到全部深度(达到工具调用预算):chunk 3:running the new tests in packages/cli/src/config/config.test.ts and packages/cli/src/ui/commands/output-style-command.test.ts — npm run build (the documen…。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
Test Plan(非阻断):src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more。
收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 3 轮发布了 6 条行内评论,其中 6 条是首次提出;上一轮发布了 9 条(其中 4 条首次提出)。发现反复回到同一批文件:packages/cli/src/ui/hooks/use-output-style-command.test.ts(第 1 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.3)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
3 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R2-2 fallback style name skips trimming (incl. ' default.md' reserved-name bypass variant) — already reported (comment 3921888873)
- R1-62 occurrence — settings.md:83 'custom style file's name' wording — already reported (comment 3914106056)
- R1-62 occurrence — settings.md:808 --output-style row still a closed built-in enumeration — already reported (comment 3914106056)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only.
Not reviewed: build-and-test — test-efficacy probe inconclusive (harnessValidated null: the probe scratch tree tripped the repo's vitest globalSetup dist-guard); mutant coverage neither validated nor refuted.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Test Plan (not a blocker): src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more.
Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/commands/output-style-command.test.ts:105 — [probe] the listing assertion matches only the i18n key under the identity t mock; OUTPUT_STYLE_LIST mutant ships green (round-4 code-age rule; round-2 deferral re-confirmed)packages/core/src/core/output-style-files.ts:290 — [probe] getUserOutputStylesRoot's no-QWEN_HOME branch is never exercised; unconditional-Storage.getGlobalQwenDir() mutant ships green (round-4 code-age rule; round-3 deferral re-confirmed)packages/core/src/core/output-style-files.ts:323 — [probe] includeProject home-skip mis-fires under QWEN_HOME relocation, silently dropping home-rooted project styles (round-4 code-age rule)packages/core/src/core/output-style-files.test.ts:30 — [probe] keep-coding-instructions: "true" quoted-string branch unwitnessed; strict-boolean mutant ships green (round-4 code-age rule)packages/core/src/core/output-style-files.test.ts:123 — [probe] \p{Cf} class of the name-rejection regex unwitnessed; dropping it accepts U+202E names into the picker and prompt heading (round-4 code-age rule)packages/core/src/core/output-style-files.test.ts:110 — [probe] derived-description >120 truncation branch unwitnessed; cap-before-strip ordering slices escape-padded lines to a junk prefix (round-4 code-age rule)packages/core/src/core/output-styles.ts:168 — [review] OutputStyleSource contract comment still claims only 'built-in' is populated, which this PR falsifies (round-4 code-age rule)packages/core/src/core/output-style-files.test.ts:281 — [probe] QWEN_HOME confinement root rejects the dotfiles symlink setup the docstring endorses; neither branch's production-root semantics is pinned (round-4 code-age rule)packages/core/src/core/output-style-files.test.ts:189 — [probe] hard caps tested only from the rejection side; off-by-one mutants ship green and refuse files at the documented maximum (round-4 code-age rule)packages/core/src/core/output-style-files.test.ts:309 — [probe] user-over-built-in precedence tier unpinned; winners-iteration reorder mutant ships green (round-4 code-age rule)packages/core/src/core/output-styles.test.ts:171 — [probe] headless keep-rule pinned only for source 'user'; source !== 'user' mutant ships green and drops project Learning styles (round-4 code-age rule)
Convergence: round 4 posted 5 inline comment(s), 5 of them reported for the first time; the previous round posted 6 (6 new). Findings keep coming back to the same files: packages/cli/src/ui/opentui/dialogs-modes.tsx (findings in rounds 1, 3; 3 more now); packages/cli/src/ui/hooks/use-output-style-command.ts (findings in round 3; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 3 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未审查(原文为英文):build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only.
未审查(原文为英文):build-and-test — test-efficacy probe inconclusive (harnessValidated null: the probe scratch tree tripped the repo's vitest globalSetup dist-guard); mutant coverage neither validated nor refuted.
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
Test Plan(非阻断):src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more。
收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 11 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 4 轮发布了 5 条行内评论,其中 5 条是首次提出;上一轮发布了 6 条(其中 6 条首次提出)。发现反复回到同一批文件:packages/cli/src/ui/opentui/dialogs-modes.tsx(第 1、3 轮已出过发现,本轮又有 3 条);packages/cli/src/ui/hooks/use-output-style-command.ts(第 3 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
The catalog effect depended on the `onClose` and `notify` props, which the mount site rebuilds as inline closures on every render, so any shell re-render tore down the settled load and re-read both style directories. The reload landed a new array, the selection was re-derived, and the user's arrow-key navigation was discarded -- Enter then applied the previously active style. Keep the callbacks in refs and depend only on `config`. The catalog is also re-read on every open and skips a file it cannot parse, so the active style can be missing from it while the session still runs it. Snapping to index 0 marked `default` as the active style and one Enter persisted that over the user's setting; list the live definition instead, matching membership case-insensitively the way the catalog dedupes.
The loader took plausible Markdown files and turned them into something the author did not write. A `---` rule around prose was parsed as frontmatter and its text vanished from the prompt, while a fence with a trailing space was not frontmatter at all and its raw YAML became the prompt; an HTML comment meant for a teammate was fed to the model, and a comment-only template file loaded as a selectable style; a UTF-16 file -- what PowerShell's `>` and Notepad write -- decoded to NUL-riddled mojibake and still reached the system prompt; the size bound was 41x the house bound for this sink and was checked against a `stat` the file could outgrow between the check and the read. A file that shadows a built-in also inherited `keep-coding-instructions: false`, so customizing the wording of `concise.md` silently deleted the verification and faithful-reporting guidance, and a declared `True` was read as false whenever an unrelated frontmatter line made the YAML parser fall back to its simple mode. Alongside those: the file-name-derived name is trimmed, so a stray trailing space no longer makes an entry that is listed but unselectable and ` default.md` no longer bypasses the reserved name; a non-string YAML `name` falls back to the file name instead of throwing an opaque TypeError or loading as `a,b`; and the home-directory guard compares the directories that would actually be read, so a relocated `QWEN_HOME` neither hides the project level nor relabels the user's own files. `stripHtmlComments` moves to `textUtils` next to `normalizeContent`, the home the rules loader's copy should always have had.
…s to Two consumers took the resolved style further than it was meant to go. Project trust was enforced only where the catalog is read, so a checked-in style kept shaping every turn after the workspace lost trust mid-session -- the IDE companion changes that verdict in place, with no restart -- while the picker had already stopped listing it. The gate now sits in the resolver the prompt and the per-turn reminder share, so both follow the flip. Arena peers were handed `getOutputStyle()` raw, so a peer inherited a style even when the main session's prompt was replaced, and a style with `keepCodingInstructions: false` stripped the software-engineering guidance -- verification and faithful reporting included -- from agents whose whole job is to produce a diff. The trust mock in the config tests answered the same way whatever it was asked, and the unknown-style warning's "Available styles" list had no test at all; both are now pinned.
Four surfaces described the value `--output-style` and `general.outputStyle` accept: two still listed only the four built-ins, and two named the style *file* where the code matches the declared frontmatter `name` -- so a user with `review-v2.md` declaring `name: Reviewer` typed `review-v2`, following the editor hover attached to the very file being edited, and got an unknown-style warning on every startup. Selecting a style also wrote the name wherever the key already lived, without saying so: a project style's name into user settings, where no other project can resolve it, or a personal style's name into the shared checked-in workspace file, where every teammate and CI run warns and runs unstyled. The scope stays where it is -- moving it would be shadowed by the workspace value -- and the confirmation now names the mismatch. The ink picker matched the active style name exactly while every other lookup is case-insensitive, and an active style the re-read catalog no longer carries fell back to `default`, where one Enter persisted it over the user's setting. Also restores the design doc and the E2E plan dropped by an earlier fix commit, with the corrections their reviews asked for: the picker resolves against the open-time snapshot, so a file deleted after the picker opened still applies and its persisted name warns at the next startup -- the opposite of what the bullet claimed -- and the plan's baseline, width, trust setup and restore steps are now executable and falsifiable.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- the no-QWEN_HOME branch of getUserOutputStylesRoot() is never exercised by any test — already recorded in the round-3 deferral paragraph and re-confirmed in the round-4 deferral paragraph
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran locally on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour.
Not reviewed: build-and-test — packages/cli suite timed out at its per-run deadline and packages/webui did not run (whole-call budget on this runner); every test file this PR adds or changes was run individually and passes.
Not reviewed: build-and-test — test-efficacy probe could not validate its positive control (harnessValidated null: the probe scratch tree tripped the repo's vitest globalSetup dist-guard); mutant coverage neither validated nor refuted.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": trace the daemon trust-policy monitor's onSnapshot follow-through (whether revocation drains/restarts workspace sessions, which would end the embedded-style exp….
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Test Plan (not a blocker): src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more.
Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:
packages/core/src/core/output-style-files.ts:236 — [probe] deriveDescription returns a bare '---' thematic break as the picker description (round-5 code-age rule: anchored on code unchanged since round 4, which read it and did not flag it)packages/cli/src/ui/commands/output-style-command.ts:97 (+2 locations) — [probe] pattern: unknown-name error interpolations unpinned at output-style-command.ts:97 and use-output-style-command.ts:137; mutations ship green (round-5 code-age r…packages/core/src/core/output-style-files.ts:146 — [probe] unknown-frontmatter-key warning is unreachable; an unknown key silently demotes the whole frontmatter to prose (round-5 code-age rule: anchored on code unchanged since round 4)packages/core/src/core/output-style-files.test.ts:251 — [probe] sanitization tests assert absence only; an over-stripping sanitizer ships green (round-5 code-age rule: anchored on code unchanged since round 4)
Convergence: round 5 posted 9 inline comment(s), 9 of them reported for the first time; the previous round posted 5 (5 new). Findings keep coming back to the same files: packages/cli/src/ui/opentui/dialogs-modes.tsx (findings in rounds 1, 4; 1 more now); packages/cli/src/ui/hooks/use-output-style-command.ts (findings in round 4; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未审查(原文为英文):build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran locally on Linux only, and this diff touches path, homedir, symlink and file-encoding behaviour.
未审查(原文为英文):build-and-test — packages/cli suite timed out at its per-run deadline and packages/webui did not run (whole-call budget on this runner); every test file this PR adds or changes was run individually and passes.
未审查(原文为英文):build-and-test — test-efficacy probe could not validate its positive control (harnessValidated null: the probe scratch tree tripped the repo's vitest globalSetup dist-guard); mutant coverage neither validated nor refuted.
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":trace the daemon trust-policy monitor's onSnapshot follow-through (whether revocation drains/restarts workspace sessions, which would end the embedded-style exp…。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
Test Plan(非阻断):src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 9 more。
收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 4 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 5 轮发布了 9 条行内评论,其中 9 条是首次提出;上一轮发布了 5 条(其中 5 条首次提出)。发现反复回到同一批文件:packages/cli/src/ui/opentui/dialogs-modes.tsx(第 1、4 轮已出过发现,本轮又有 1 条);packages/cli/src/ui/hooks/use-output-style-command.ts(第 4 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- unknown-name error interpolation at output-style-command.ts:96 — already recorded in the round-5 deferral paragraph
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only.
Test Plan (not a blocker): src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 7 more.
Deferred under the convergence posture (round 6, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/commands/output-style-utils.ts:126 — [probe] D6-1 new untrusted-workspace branch's source === 'project' conjunct is unpinned; deleting it ships greenpackages/core/src/core/output-style-files.ts:380 — [review] R5-2 UTF-16 guard relies solely on isBinary's NUL-byte sniff; single-line UTF-16LE without ASCII/whitespace evades itpackages/core/src/core/output-style-files.ts:458 — [review] R5-3 duplicate-load guard compares raw path.resolve strings — no case folding, no symlink resolutionpackages/cli/src/ui/hooks/use-output-style-command.ts:115 — [review] R5-4 append guard checks name membership only; a same-named different definition silently replaces the live onepackages/cli/src/ui/opentui/dialogs-modes.tsx:304 — [review] R5-5 append-live-style branch cannot distinguish 'file no longer parses' from 'excluded by trust policy'packages/core/src/core/output-style-files.ts:55 — [review] R5-6 frontmatter fence closer anchored to end-of-line only; a value line ending in ' ---' closes the block mid-frontmatterpackages/core/src/core/output-style-files.ts:79 — [review] R5-7 key-line grammar narrower than YAML; quoted or spaced-colon keys demote valid frontmatter to prosepackages/cli/src/config/settingsSchema.ts:446 — [review] R5-8 closing sentence 'Only the built-ins are available in --bare and --safe-mode' misdescribes the settingpackages/vscode-ide-companion/schemas/settings.schema.json:74 — [review] R5-9 closing sentence 'Only the built-ins are available in --bare and --safe-mode' misdescribes the setting
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未审查(原文为英文):build-and-test — Test (windows-latest, Node 22.x) and Test (macos-latest, Node 22.x) were skipped in CI; the unit suite ran on Linux only.
Test Plan(非阻断):src/core/output-style-files.test.ts — no such file or directory; src/core/output-styles.test.ts — no such file or directory; src/ui/commands/output-style-command.test.ts — no such file or directory; src/ui/hooks/use-output-style-command.test.ts — no such file or directory; src/ui/components/OutputStyleDialog.test.tsx — no such file or directory; and 7 more。
收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改:共 9 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.0)
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Reviewed at head bb936adf.
- History: five earlier CHANGES_REQUESTED rounds all predate the current head; the fixes they forced (session-scoped resolution, prompt-safe file reading, steady picker, inactive-project explanation) are in the five most recent commits, all 48 review threads are resolved with the author's replies recorded, and the last formal pass at this head (ci-bot, 11:53) posted no new Critical — its one open item is the previously recorded S-level unknown-name interpolation note.
- Independent pass on the new loader found no Criticals. The file surface is bounded the way prompt-injected files should be: 25 KB cap matched to the house bound with the +1-byte read, control-character stripping, reserved-name and length checks, and — for untrusted-by-definition project files —
lstatrejecting the final-component symlink,nlink > 1rejecting hardlinks, and a realpath containment check against the workspace root before the file is read; the caller only passesprojectRootwhen the folder is trusted, and--bare/--safe-modecollapse to built-ins. The picker resolving against the list it displayed, and invalid files skipping with a log rather than killing the catalog, are both pinned by the new suites. - Author evidence is execution-grade (real
npm run devpicker capture, haiku applied end to end, focused 41+247+28 tests green), and the one remaining pre-existingtscerror in the OpenTUI commands registry is honestly disclosed as out of scope. - CI facts: every completed check on this head passes — 19/19, zero failures, nothing pending. Per the channel convention the call is on the review itself, and here the lanes agree.
- Housekeeping note: the page may still read CHANGES_REQUESTED from the bot's round-5 ticket (filed at
71c71968, superseded by its own re-review of this head); a triage re-run lifts it. That ticket does not reflect any unresolved finding atbb936adf.
yiliang114
left a comment
There was a problem hiding this comment.
Verified at head bb936ad against the three open Criticals from the last round — all fixed and test-pinned:
- R1-1 (fix-induced picker reload): the catalog-load effect now keys on
[config]only, withonClose/notifymirrored into refs synced per render; pinned by 'keeps the navigated row when the shell re-renders with new callbacks' (load runs once, selection survives). - R4-1 (active style clamped to index 0): a live style missing from the re-read catalog is now appended to the list (case-insensitive identity) instead of snapping to default; pinned by 'lists the active style the reloaded catalog no longer carries' and the case-duplicate guard, with the ink twin fixed in the hook too.
- R5-1 (untrusted-workspace project style misattributed): applyOutputStyleSelection now has a dedicated
project source + untrusted workspaceexplanation ahead of the headless-Learning branch, with locale entries and a test asserting the trust reason (and the absence of the headless message).
Test (ubuntu), Lint & Static and Integration Tests (no-AK) all green on this head; no unresolved threads.
Non-blocking, carried on the record: R5-2 (UTF-16 files without NUL in the sample window evade the binary sniff), R5-3 (home/project dir comparison is lexical — no case folding/realpath), R5-4/R5-5 (append guard keys on name membership; a trust-dropped project style can still be listed/pre-selected with the ● marker), R5-6/R5-7 (frontmatter fence anchored to end-of-line only; key grammar narrower than YAML), R5-8 (the new '--bare/--safe-mode' sentence misdescribes the setting — the setting is ignored entirely in those modes). Also minor: the two new scope-mismatch messages from 71c7196 ('…is a personal/project style, so it will not resolve…') have no locale entries in any of the nine locale files, so they will render as raw keys.
|
Post-merge review of head Verified in the code at this head
Executed: full workspace build clean (0 TS errors) · core suites ( Cross-check: the two existing approvals at this head match my read — the last round's three Criticals are fixed and test-pinned, and the carried non-blocking items (UTF-16-without-NUL sniff window, lexical dir comparison, fence/grammar narrowness, the No blockers found in what merged. Reviewed with AI assistance. |
|
Released in v0.23.1. |
What this PR does
This lets users define their own output styles as Markdown files. A file in
~/.qwen/output-styles/is a user style; a file in the project's.qwen/output-styles/is a project style. The body is the style's prompt section, and an optional YAML frontmatter carriesname,description, andkeep-coding-instructions, defaulting to the file name, the first body line, andfalse. A bare Markdown file is therefore already a valid style.Core gains
loadOutputStyleCatalog, which returns the built-ins plus the user's and (when a project root is given) the project's files, deduplicated by case-insensitive name with project > user > built-in precedence, in picker order.parseOutputStyleFileandloadOutputStylesFromDirare exported separately so the extension slice can reuse them withsource: 'extension'.The CLI reads the catalog in three places: at startup to resolve
general.outputStyleor--output-style, when/output-style <name>is given, and when the picker opens. Custom entries appear after the built-ins with their source in the label. The picker resolves the selection against the list it displayed, so a file removed between open and select gives a clear error instead of a silent no-op. Project files are read only from a trusted workspace;--bareand--safe-modekeep the built-ins only. An invalid file (empty body, reserved or malformed name, over 1 MiB) is skipped and logged, and the other files still load.Documentation gains a "Custom styles" section in
output-styles.md, the settings and headless references mention custom names, the VS Code settings schema is regenerated, and the design decisions are recorded indocs/design/2026-09-02-output-style-files.md.Why it's needed
#9565 shipped built-in styles, #10283 made them selectable at startup, and #10683 added the
/output-stylepicker. All three leftOutputStyleSource'suserandprojectmembers unpopulated, so the only styles a user could pick were the four built-ins. This slice makes the style set user-extensible with the same file shape other CLI agents use, so one file works across tools, while keeping the loader small: two directories, top level only, no watcher.Reviewer Test Plan
How to verify
Create
~/.qwen/output-styles/reviewer.mdwith aname: Reviewerfrontmatter and a body,.qwen/output-styles/haiku.mdwith just a body, and.qwen/output-styles/broken.mdwithname: default. Runnpm run devand/output-style: the picker should list the four built-ins, thenReviewer … (user)andhaiku … (project), and nobrokenentry. Selecthaikuand ask a question; the next answer should be a haiku and~/.qwen/settings.jsonshould carry"outputStyle": "haiku". Confirm/output-style REVIEWERapplies case-insensitively, that a file added while the session is open shows up on the next/output-stylewithout a restart, that an unknown name lists the custom styles among the choices, and that after a restart the persisted custom style applies from startup. The full plan is in.qwen/e2e-tests/output-style-files.md.Focused verification:
Result: core 2 files, 41 tests passed; CLI 8 files, 247 tests passed, plus 28 config output-style tests passed. ESLint and Prettier are clean on the changed files.
tsc --noEmitinpackages/clireports one error that is already present onmain(ui/opentui/commands-registry.tsdoes not route theoutput-styledialog kind); that is a separate OpenTUI parity gap and is not touched here.Evidence (Before & After)
Before:
/output-styleoffered onlydefaultand the four built-ins; a custom name ingeneral.outputStylewarned and fell back to the default style.After, with the fixtures above, from a real
npm run devsession:terseabove is a file added while the session was open; it appeared on the next/output-stylewithout a restart. After/quitand a freshnpm run dev, the first question was answered as a haiku with no command issued.Tested on
Environment (optional)
Node 22;
npm run devfor the interactive session; focused Vitest runs inpackages/coreandpackages/cli.Risk & Scope
settings.jsonalready draws. A custom file may shadow a built-in name, mirroring how project skills shadow bundled ones.source: 'extension', theoutputStylesmanifest field the Claude-plugin converter currently warns about), a policy/managed level, additional-directory scans, a settings-file watcher, and the OpenTUI route for theoutput-styledialog kind.resolveOutputStylegains an optional third parameter that defaults to the built-ins.Linked Issues
Follows #9565, #10282, #10283, and #10683.
中文说明
这个 PR 做了什么
本 PR 允许用户用 Markdown 文件定义自己的输出风格。
~/.qwen/output-styles/下的文件是用户级风格,项目.qwen/output-styles/下的是项目级风格。文件正文就是风格的提示词段落,可选的 YAML frontmatter 携带name、description、keep-coding-instructions,缺省分别为文件名、正文首行和false,因此一个纯 Markdown 文件本身就是合法风格。core 新增
loadOutputStyleCatalog:返回内置风格加用户文件、加(给定项目根时)项目文件,按大小写不敏感的名称去重,优先级 project > user > built-in,输出顺序即选择器顺序。parseOutputStyleFile与loadOutputStylesFromDir单独导出,便于扩展切片以source: 'extension'复用。CLI 在三处读取目录:启动时解析
general.outputStyle或--output-style,/output-style <name>时,以及打开选择器时。自定义条目排在内置之后并在标签中标注来源。选择器按打开时展示的列表解析选择,因此在打开与选择之间被删除的文件会给出明确错误而不是静默无效。项目文件只在可信工作区读取;--bare与--safe-mode只保留内置风格。无效文件(空正文、保留或非法名称、超过 1 MiB)会被跳过并记入调试日志,其余文件照常加载。文档方面,
output-styles.md新增「Custom styles」章节,settings 与 headless 参考提到自定义名称,VS Code 设置 schema 已重新生成,设计取舍记录在docs/design/2026-09-02-output-style-files.md。为什么需要它
#9565 提供内置风格,#10283 让启动时可选,#10683 增加了
/output-style选择器,但三者都没有填充OutputStyleSource中的user与project,用户能选的仍只有四个内置风格。本切片让风格集合可由用户扩展,文件格式与其他 CLI 代理一致,一份文件多处可用,同时把加载器保持得很小:两个目录、仅顶层、无监听。评审者测试计划
如何验证
创建
~/.qwen/output-styles/reviewer.md(frontmatter 含name: Reviewer)、.qwen/output-styles/haiku.md(仅正文)和.qwen/output-styles/broken.md(name: default)。运行npm run dev并执行/output-style:选择器应列出四个内置风格,随后是Reviewer … (user)与haiku … (project),且没有broken。选择haiku后提问,下一条回答应为俳句,~/.qwen/settings.json中应有"outputStyle": "haiku"。确认/output-style REVIEWER大小写不敏感;会话中新增文件后再次/output-style无需重启即可看到;未知名称的报错列表包含自定义风格;重启后已持久化的自定义风格从启动起生效。完整计划见.qwen/e2e-tests/output-style-files.md。上方 focused Vitest 命令:core 2 个文件 41 项测试通过;CLI 8 个文件 247 项测试通过,另有 config 的 28 项 output-style 测试通过。改动文件的 ESLint 与 Prettier 均干净。
packages/cli的tsc --noEmit有一处main上已存在的错误(ui/opentui/commands-registry.ts未路由output-style对话框类型),属于独立的 OpenTUI 对齐缺口,本 PR 不涉及。证据(Before & After)
Before:
/output-style只提供default与四个内置风格;general.outputStyle写自定义名称会警告并回退默认风格。After:见上方真实会话记录。选择
haiku后下一条回答即为俳句;REVIEWER大小写不敏感生效;会话中新增的terse无需重启即出现在列表与报错提示中;重启后首个回答仍为俳句。测试环境
风险与影响范围
settings.json的界线一致。自定义文件可以遮蔽内置名称,与项目级 skill 遮蔽内置 skill 的行为一致。source: 'extension',即 Claude 插件转换器目前告警的outputStyles字段)、策略/托管级目录、additional directories 扫描、设置文件监听,以及 OpenTUI 对output-style对话框类型的路由。resolveOutputStyle新增可选的第三个参数,缺省为内置风格。关联 Issue
承接 #9565、#10282、#10283、#10683。