Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 191 additions & 1 deletion packages/core/src/skills/bundled/review/DESIGN.md

Large diffs are not rendered by default.

108 changes: 57 additions & 51 deletions packages/core/src/skills/bundled/review/SKILL.md

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions packages/core/src/skills/bundled/review/SKILL.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @license
* Copyright 2026 Qwen
* SPDX-License-Identifier: Apache-2.0
*/

import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';

const skillDir = path.dirname(fileURLToPath(import.meta.url));

// Titles may end in one parenthesized qualifier, e.g. "The two-dot phantom
// regressions (PR #6626)", so the match allows a single nested group.
const POINTER_RE = /\(measured; DESIGN\.md — ([^()\n]+(?:\([^()\n]*\))?)\)/g;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] POINTER_RE silently drops any pointer it cannot parse instead of failing loudly, and the reverse-direction test only backstops headings with a single pointer — so a malformed pointer to a multiply-referenced heading escapes both tests. Two titles are referenced twice today ("The roles nobody launched", "The self-filed COMMENT review (PR #6771)"). Verified by execution: a pointer with two parenthesized qualifiers yields 2 literal openers, 1 regex match, and both tests green. — Failure scenario: a future edit gives one of two sibling pointers a shape the regex cannot parse → it is silently excluded, the sibling still satisfies heading coverage, and a stale/broken anchor ships with nothing red.

Suggested fix — count literal openers and assert the matched list has exactly that length:

const POINTER_OPEN = '(measured; DESIGN.md \u2014 ';
let opens = 0;
for (let i = body.indexOf(POINTER_OPEN); i !== -1; i = body.indexOf(POINTER_OPEN, i + 1)) opens++;
expect(pointers).toHaveLength(opens);
中文说明

[Suggestion] POINTER_RE 遇到解析不了的指针会静默丢弃而不是大声失败,而反向测试只为“只有一个指针”的标题兜底——因此指向“被多次引用标题”的畸形指针能同时逃过两个测试。当前已有两个标题被引用两次(“The roles nobody launched”“The self-filed COMMENT review (PR #6771)”)。已实际执行验证:带两个括号限定符的指针会产生 2 个字面起始符、1 个正则匹配,两个测试全绿。— 失败场景:未来某次编辑把两个兄弟指针之一改成该正则无法解析的形状 → 它被静默排除,兄弟指针仍满足标题覆盖,陈旧/损坏的锚点在无任何测试变红的情况下被放行。

建议修复——统计字面起始符数量并断言匹配列表长度一致:

const POINTER_OPEN = '(measured; DESIGN.md \u2014 ';
let opens = 0;
for (let i = body.indexOf(POINTER_OPEN); i !== -1; i = body.indexOf(POINTER_OPEN, i + 1)) opens++;
expect(pointers).toHaveLength(opens);

— qwen3.8-max via Qwen Code /review (v0.21.5)

const POINTER_OPEN = '(measured; DESIGN.md — ';

function skillBody(): string {
return fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf8');
}

function incidentPointers(body: string): string[] {
return [...body.matchAll(POINTER_RE)].map(([, title]) => title.trim());
}

function incidentHeadings(): string[] {
const design = fs.readFileSync(path.join(skillDir, 'DESIGN.md'), 'utf8');
const start = design.indexOf('## Measured incidents');
const end = design.indexOf('\n## ', start + 1);
const section = end === -1 ? design.slice(start) : design.slice(start, end);
return [...section.matchAll(/^### (.+)$/gm)].map(([, title]) => title.trim());
}

describe('bundled review skill', () => {
it('anchors every SKILL.md incident pointer at a DESIGN.md heading', () => {
const body = skillBody();
const pointers = incidentPointers(body);
expect(pointers.length).toBeGreaterThan(0);

// A pointer the regex cannot parse must fail loudly, not drop silently:
// every literal opener owes exactly one match.
let opens = 0;
for (
let i = body.indexOf(POINTER_OPEN);
i !== -1;
i = body.indexOf(POINTER_OPEN, i + POINTER_OPEN.length)
) {
opens++;
}
expect(pointers).toHaveLength(opens);

const headings = new Set(incidentHeadings());
for (const title of pointers) {
expect(
headings.has(title),
`SKILL.md points at a missing DESIGN.md heading: "### ${title}"`,
).toBe(true);
}
});

it('leaves no DESIGN.md incident heading without a SKILL.md pointer', () => {
const referenced = new Set(incidentPointers(skillBody()));
for (const title of incidentHeadings()) {
expect(
referenced.has(title),
`DESIGN.md incident heading has no SKILL.md pointer: "### ${title}"`,
).toBe(true);
}
});

it('keeps the runtime guard against reading DESIGN.md mid-review', () => {
expect(skillBody()).toContain(
'Never `read_file` DESIGN.md during a review.',
);
});

it('pins the setup-batch ordering constraints', () => {
const body = skillBody();
expect(body).toContain('`fetch-pr` before all of them');
expect(body).toContain('`agent-prompt --roster` after the rules load');
});
});
6 changes: 5 additions & 1 deletion scripts/copy_bundle_assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export function copyBundleAssets({ root = defaultRoot } = {}) {
const destBundledDir = join(distDir, 'bundled');
fs.rmSync(destBundledDir, { recursive: true, force: true });
copyRecursiveSync(bundledSkillsDir, destBundledDir, {
skipEntry: isBundledSkillTestFile,
// DESIGN.md files are maintainer design narratives, not runtime inputs;
// shipping one would hand a review a ~125 KB read_file target that
// outweighs the context the slimmed skill saves.
skipEntry: (entry) =>
isBundledSkillTestFile(entry) || entry === 'DESIGN.md',
Comment on lines +76 to +77

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The DESIGN.md exclusion only covers the esbuild-bundle path (dist/bundled/); the ordinary package build still ships DESIGN.md beside SKILL.md at packages/core/dist/src/skills/bundled/review/ — the directory the transpiled-mode CLI loads bundled skills from. scripts/copy_files.js copies every .md from packages/core/src to dist/src with no exclusion; verified in the built worktree (DESIGN.md present at 125,225 bytes), and the same step puts the file into the publishable @qwen-code/qwen-code-core tarball. The new package-assets.test.js assertion pins only the bundle path. — Failure scenario: /review on a built-but-not-bundled checkout (the npm run dev:daemon transpiled shape) → DESIGN.md sits beside SKILL.md exactly as before this commit; the 47 new pointers invite a read that one unpinned prose sentence prevents, and one that lands costs the full 125 KB the slimmed SKILL.md just saved, while nothing turns red.

Suggested fix: mirror the exclusion in scripts/copy_files.js — skip DESIGN.md when the path is under skills/bundled/ — and extend a script test to assert packages/core/dist/src/skills/bundled/*/DESIGN.md is absent after the copy.

中文说明

[Suggestion] 对 DESIGN.md 的排除只覆盖了 esbuild 打包路径(dist/bundled/);普通包构建仍会把 DESIGN.md 与 SKILL.md 一起放到 packages/core/dist/src/skills/bundled/review/——而 transpiled 模式的 CLI 正是从该目录加载 bundled skills。scripts/copy_files.js 会把 packages/core/src 下所有 .md 无排除地拷到 dist/src;已在构建后的 worktree 中验证(DESIGN.md 以 125,225 字节存在),同一步骤还会把它带进可发布的 @qwen-code/qwen-code-core tarball。新增的 package-assets.test.js 断言只钉住了 bundle 路径。— 失败场景:在“已构建未打包”的检出上运行 /review(npm run dev:daemon 的 transpiled 形态)→ DESIGN.md 与本提交之前一模一样地躺在 SKILL.md 旁边;47 处新指针都在邀请读取,而唯一的屏障是一句没有测试兜底的散文,一旦读进来就花掉瘦身后 SKILL.md 刚省下的全部 125 KB,且没有任何测试变红。

建议修复:在 scripts/copy_files.js 中同样排除——路径位于 skills/bundled/ 下且文件名为 DESIGN.md 时跳过——并在脚本测试中断言拷贝后 packages/core/dist/src/skills/bundled/*/DESIGN.md 不存在。

— qwen3.8-max via Qwen Code /review (v0.21.5)

});
console.log('Copied bundled skills to dist/bundled/');
} else {
Expand Down
80 changes: 53 additions & 27 deletions scripts/copy_files.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@

import fs from 'node:fs';
import path from 'node:path';

const sourceDir = path.join('src');
const targetDir = path.join('dist', 'src');
import { fileURLToPath } from 'node:url';

const extensionsToCopy = ['.md', '.json', '.sb'];

function isBundledSkillDesignDoc(normalizedPath) {
// DESIGN.md files are maintainer design narratives, not runtime inputs
// (see copy_bundle_assets.js); the transpiled build loads bundled skills
// from dist/src/, so they must stay out of it too.
return (
normalizedPath.startsWith('skills/bundled/') &&
path.basename(normalizedPath) === 'DESIGN.md'
);
}

function copyFilesRecursive(source, target, rootSourceDir) {
if (!fs.existsSync(target)) {
fs.mkdirSync(target, { recursive: true });
Expand All @@ -49,38 +57,56 @@ function copyFilesRecursive(source, target, rootSourceDir) {
const normalizedPath = relativePath.replace(/\\/g, '/');
const isLocaleJs =
ext === '.js' && normalizedPath.startsWith('i18n/locales/');
if (extensionsToCopy.includes(ext) || isLocaleJs) {
if (
(extensionsToCopy.includes(ext) || isLocaleJs) &&
!isBundledSkillDesignDoc(normalizedPath)
) {
fs.copyFileSync(sourcePath, targetPath);
}
}
}
}

if (!fs.existsSync(sourceDir)) {
console.error(`Source directory ${sourceDir} not found.`);
process.exit(1);
}
export function copyFiles({ root = process.cwd() } = {}) {
const sourceDir = path.join(root, 'src');
const targetDir = path.join(root, 'dist', 'src');

copyFilesRecursive(sourceDir, targetDir, sourceDir);
if (!fs.existsSync(sourceDir)) {
console.error(`Source directory ${sourceDir} not found.`);
process.exit(1);
}

// Copy example extensions into the bundle.
const packageName = path.basename(process.cwd());
if (packageName === 'cli') {
const examplesSource = path.join(
sourceDir,
'commands',
'extensions',
'examples',
);
const examplesTarget = path.join(
targetDir,
'commands',
'extensions',
'examples',
);
if (fs.existsSync(examplesSource)) {
fs.cpSync(examplesSource, examplesTarget, { recursive: true });
copyFilesRecursive(sourceDir, targetDir, sourceDir);

// Copy example extensions into the bundle.
const packageName = path.basename(root);
if (packageName === 'cli') {
const examplesSource = path.join(
sourceDir,
'commands',
'extensions',
'examples',
);
const examplesTarget = path.join(
targetDir,
'commands',
'extensions',
'examples',
);
if (fs.existsSync(examplesSource)) {
fs.cpSync(examplesSource, examplesTarget, { recursive: true });
}
}

console.log('Successfully copied files.');
}

console.log('Successfully copied files.');
if (isDirectRun()) {
copyFiles();
}

function isDirectRun() {
return process.argv[1]
? fileURLToPath(import.meta.url) === path.resolve(process.argv[1])
: false;
}
38 changes: 38 additions & 0 deletions scripts/tests/package-assets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { copyBundleAssets } from '../copy_bundle_assets.js';
import { copyFiles } from '../copy_files.js';
import { preparePackage } from '../prepare-package.js';

describe('package asset scripts', () => {
Expand Down Expand Up @@ -102,6 +103,11 @@ describe('package asset scripts', () => {
'packages/core/src/skills/bundled/dataviz/references/palette.md',
'# Palette\n',
);
writeFile(
rootDir,
'packages/core/src/skills/bundled/dataviz/DESIGN.md',
'# Design notes\n',
);
writeFile(rootDir, 'dist/bundled/dataviz/scripts/stale.test.js', 'stale\n');
stubConsole();

Expand Down Expand Up @@ -203,6 +209,38 @@ describe('package asset scripts', () => {
),
),
).toBe(true);
expect(
existsSync(path.join(rootDir, 'dist', 'bundled', 'dataviz', 'DESIGN.md')),
).toBe(false);
});

it('keeps bundled-skill DESIGN.md out of the per-package dist/src copy', () => {
const rootDir = createFixtureRoot();
writeFile(
rootDir,
'packages/core/src/skills/bundled/review/SKILL.md',
'---\nname: review\ndescription: Review changes\n---\nBody\n',
);
writeFile(
rootDir,
'packages/core/src/skills/bundled/review/DESIGN.md',
'# Design notes\n',
);
writeFile(rootDir, 'packages/core/src/notes/DESIGN.md', '# Keep\n');
stubConsole();

copyFiles({ root: path.join(rootDir, 'packages', 'core') });

const distSrc = path.join(rootDir, 'packages', 'core', 'dist', 'src');
expect(
existsSync(path.join(distSrc, 'skills', 'bundled', 'review', 'SKILL.md')),
).toBe(true);
expect(
existsSync(
path.join(distSrc, 'skills', 'bundled', 'review', 'DESIGN.md'),
),
).toBe(false);
expect(existsSync(path.join(distSrc, 'notes', 'DESIGN.md'))).toBe(true);
});

it('includes extension examples in the prepared dist package', () => {
Expand Down
Loading