mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-06 23:35:34 +00:00
* perf(review): issue independent setup calls in one response Measured on a real small-PR run: the stretch from parse-args to the first agent launch took 7 minutes of wall clock, one round-trip at a time, on calls that never needed an order — pr-context, comment-status and the Step 2 rules load are mutually independent reads. Step 1 now tells the orchestrator to issue all three in a single response (the same rule Step 3 already enforces for the agent fan-out) and to page their outputs in shared responses too. comment-status loses its wait-for-the-context-file guard in worktree mode: learning whether inline comments exist cost a serial round-trip, while running it on a commentless PR just writes an empty index. Step 6's two deterministic gates (script-lint, test-plan) get the same one-response note. The orderings that matter are kept explicit: fetch-pr before everything (it creates the worktree and the plan), the roster after the rules load (it bakes the rules into every brief). * refactor(core): move review skill incident narratives to DESIGN.md SKILL.md is injected wholesale into the review orchestrator's context on every /review run and re-billed on each of its turns, and ~16KB of it was incident narrative — accounts of past dogfood failures and measurements that justify rules but are not themselves instructions. Move 50 such narrative blocks into a new 'Measured incidents (moved from SKILL.md)' section of DESIGN.md (47 anchors, not loaded at runtime), leaving every rule in place with a short '(measured; DESIGN.md — <anchor>)' pointer. Force-bearing figures stay inline where the number is the argument (e.g. the ~161s cold npm ci, the 41% test-code median, the PR #6457 one-of-five checklist measurement). No instruction, gate, format, flag, threshold, or ordering changed; the YAML frontmatter and all 35 fenced code blocks are byte-identical, and the MUST / Do not / never imperative counts are unchanged outside the moved narrative text (verified by script). SKILL.md: 237,847 -> 228,266 bytes; DESIGN.md: 106,708 -> 125,184 bytes. * fix(review): keep DESIGN.md out of the runtime bundle and pin pointers The slim refactor left DESIGN.md shipped beside SKILL.md in dist/bundled/, so one curious read_file of the 125 KB maintainer document would cost more context than the refactor saves. The bundle copy now skips DESIGN.md, and SKILL.md gains a one-line guard telling the orchestrator the pointers are for humans auditing a rule. Also addresses review feedback: a test pins both directions of the SKILL.md incident-pointer mapping, the transcribed-argument narrative keeps its referent after the move, the incidents section title loses its changelog suffix, and Step 2 no longer asks for a base fetch that fetch-pr already performed. * fix(review): gate setup batching by effort and consolidate incident blocks Address round-1 review feedback on the skill-slim PR: - Gate the ONE-response setup batch and the comment-status call to high and medium effort, matching Step 2's low-effort skip. - Scope the Step 6 lint/test-plan batching to same-repo PR reviews. - Merge same-run incident blocks (self-composed Approve into the paraphrased roster prompt; archive verdict into the narrated-away cap), cross-reference the roster-size and relocated-Critical tellings, and state the #8368 path in its block plus the pointer it was missing. - Pointer-ize the last inline QQChannel narrative and fix the scripts-nobody-ran summary to match its block. - Extend the DESIGN.md exclusion to copy_files.js so the transpiled dist/src build and the published core tarball stop shipping it. - Pin the no-read_file guard and the batch ordering constraints in SKILL.test.ts, and fail loudly on pointers the regex cannot parse. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
205 lines
7.2 KiB
JavaScript
205 lines
7.2 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { copyFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
|
|
import { dirname, join, basename, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { glob } from 'glob';
|
|
import fs from 'node:fs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const defaultRoot = join(__dirname, '..');
|
|
const BUNDLED_SKILL_TEST_FILE_RE =
|
|
/\.(?:test|spec)\.(?:d\.)?[cm]?[jt]sx?(?:\.map)?$/;
|
|
|
|
export function copyBundleAssets({ root = defaultRoot } = {}) {
|
|
const distDir = join(root, 'dist');
|
|
const coreVendorDir = join(root, 'packages', 'core', 'vendor');
|
|
|
|
// Create the dist directory if it doesn't exist
|
|
if (!existsSync(distDir)) {
|
|
mkdirSync(distDir);
|
|
}
|
|
|
|
// Find and copy all .sb files from packages to the root of the dist directory
|
|
const sbFiles = glob.sync('packages/**/*.sb', { cwd: root });
|
|
for (const file of sbFiles) {
|
|
copyFileSync(join(root, file), join(distDir, basename(file)));
|
|
}
|
|
|
|
console.log('Copied sandbox profiles to dist/');
|
|
|
|
// Copy vendor directory (contains ripgrep binaries)
|
|
console.log('Copying vendor directory...');
|
|
if (existsSync(coreVendorDir)) {
|
|
const destVendorDir = join(distDir, 'vendor');
|
|
copyRecursiveSync(coreVendorDir, destVendorDir);
|
|
console.log('Copied vendor directory to dist/');
|
|
} else {
|
|
console.warn(`Warning: Vendor directory not found at ${coreVendorDir}`);
|
|
}
|
|
|
|
// Copy bundled skills (e.g. /review) so they are available at runtime.
|
|
// In the esbuild bundle, import.meta.url resolves to dist/cli.js, so
|
|
// SkillManager looks for bundled skills at dist/bundled/.
|
|
const bundledSkillsDir = join(
|
|
root,
|
|
'packages',
|
|
'core',
|
|
'src',
|
|
'skills',
|
|
'bundled',
|
|
);
|
|
if (existsSync(bundledSkillsDir)) {
|
|
const destBundledDir = join(distDir, 'bundled');
|
|
fs.rmSync(destBundledDir, { recursive: true, force: true });
|
|
copyRecursiveSync(bundledSkillsDir, destBundledDir, {
|
|
// 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',
|
|
});
|
|
console.log('Copied bundled skills to dist/bundled/');
|
|
} else {
|
|
console.warn(
|
|
`Warning: Bundled skills directory not found at ${bundledSkillsDir}`,
|
|
);
|
|
}
|
|
|
|
// Copy user docs into qc-helper bundled skill so it can reference them at runtime.
|
|
// The qc-helper skill reads docs from a `docs/` subdirectory relative to its own
|
|
// directory. In the esbuild bundle this becomes dist/bundled/qc-helper/docs/.
|
|
const userDocsDir = join(root, 'docs', 'users');
|
|
if (existsSync(userDocsDir)) {
|
|
const destDocsDir = join(distDir, 'bundled', 'qc-helper', 'docs');
|
|
copyRecursiveSync(userDocsDir, destDocsDir);
|
|
console.log('Copied docs/users/ to dist/bundled/qc-helper/docs/');
|
|
} else {
|
|
console.warn(`Warning: User docs directory not found at ${userDocsDir}`);
|
|
}
|
|
|
|
// Copy builtin locales so bundled dist/cli.js can load UI translations at runtime.
|
|
// Published packages already include these via prepare-package.js; bundle output
|
|
// should mirror that behavior for local `node dist/cli.js` runs.
|
|
const localesDir = join(root, 'packages', 'cli', 'src', 'i18n', 'locales');
|
|
if (existsSync(localesDir)) {
|
|
const destLocalesDir = join(distDir, 'locales');
|
|
copyRecursiveSync(localesDir, destLocalesDir);
|
|
console.log('Copied builtin locales to dist/locales/');
|
|
} else {
|
|
console.warn(`Warning: Locales directory not found at ${localesDir}`);
|
|
}
|
|
|
|
// Copy extension templates so bundled dist/cli.js can scaffold
|
|
// `/extensions new` from the runtime examples directory.
|
|
const extensionExamplesDir = join(
|
|
root,
|
|
'packages',
|
|
'cli',
|
|
'src',
|
|
'commands',
|
|
'extensions',
|
|
'examples',
|
|
);
|
|
if (existsSync(extensionExamplesDir)) {
|
|
const destExtensionExamplesDir = join(distDir, 'examples');
|
|
copyRecursiveSync(extensionExamplesDir, destExtensionExamplesDir);
|
|
console.log('Copied extension examples to dist/examples/');
|
|
} else {
|
|
console.warn(
|
|
`Warning: Extension examples directory not found at ${extensionExamplesDir}`,
|
|
);
|
|
}
|
|
|
|
// Copy the built Web Shell SPA (index.html + assets/) so the bundled
|
|
// `qwen serve` can serve the browser UI at its root path. The library
|
|
// build outputs (dist/index.js, dist/types) are for npm consumers and are
|
|
// intentionally NOT copied. Source only exists after the web-shell
|
|
// workspace is built (npm run build); when absent (e.g. a --cli-only
|
|
// build, or bundling without a prior full build) we warn and skip so the
|
|
// bundle step never fails — the daemon then runs API-only at runtime.
|
|
const webShellDistDir = join(root, 'packages', 'web-shell', 'dist');
|
|
const webShellIndexHtml = join(webShellDistDir, 'index.html');
|
|
const webShellAssetsDir = join(webShellDistDir, 'assets');
|
|
if (existsSync(webShellIndexHtml) && existsSync(webShellAssetsDir)) {
|
|
const destWebShellDir = join(distDir, 'web-shell');
|
|
mkdirSync(destWebShellDir, { recursive: true });
|
|
copyFileSync(webShellIndexHtml, join(destWebShellDir, 'index.html'));
|
|
copyRecursiveSync(webShellAssetsDir, join(destWebShellDir, 'assets'));
|
|
console.log('Copied Web Shell UI to dist/web-shell/');
|
|
} else {
|
|
console.warn(
|
|
`Warning: Web Shell assets not found at ${webShellDistDir}; ` +
|
|
'dist/web-shell/ will be absent and `qwen serve` runs API-only. ' +
|
|
'Run a full `npm run build` before bundling to include the UI.',
|
|
);
|
|
}
|
|
|
|
console.log('\n✅ All bundle assets copied to dist/');
|
|
}
|
|
|
|
if (isDirectRun()) {
|
|
copyBundleAssets();
|
|
}
|
|
|
|
function isDirectRun() {
|
|
return process.argv[1]
|
|
? fileURLToPath(import.meta.url) === resolve(process.argv[1])
|
|
: false;
|
|
}
|
|
|
|
/**
|
|
* Recursively copy directory
|
|
*/
|
|
function copyRecursiveSync(src, dest, options = {}) {
|
|
if (!existsSync(src)) {
|
|
return;
|
|
}
|
|
|
|
const stats = statSync(src);
|
|
|
|
if (stats.isDirectory()) {
|
|
if (!existsSync(dest)) {
|
|
mkdirSync(dest, { recursive: true });
|
|
}
|
|
|
|
const entries = fs.readdirSync(src);
|
|
for (const entry of entries) {
|
|
if (entry === '.DS_Store' || options.skipEntry?.(entry)) {
|
|
continue;
|
|
}
|
|
|
|
const srcPath = join(src, entry);
|
|
const destPath = join(dest, entry);
|
|
copyRecursiveSync(srcPath, destPath, options);
|
|
}
|
|
} else {
|
|
copyFileSync(src, dest);
|
|
// Preserve execute permissions for binaries
|
|
const srcStats = statSync(src);
|
|
if (srcStats.mode & 0o111) {
|
|
fs.chmodSync(dest, srcStats.mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isBundledSkillTestFile(fileName) {
|
|
return BUNDLED_SKILL_TEST_FILE_RE.test(fileName);
|
|
}
|