fix: 根 files 改为子包运行时白名单,并删除过期提示词快照

整目录列入子包时,pi-subagents 上游 .npmignore 覆盖 .gitignore,导致
嵌套 node_modules(含第二套 Pi runtime)与 dist 被打包:64.9MB/19,570
文件降至 1.2MB/471 文件。新增 tests/package-files.test.ts 守护:无嵌套
node_modules/dist、扩展入口与 file: 依赖清单在包内、包内源码相对引用闭包。
同时删除无引用的 pi-default-system-prompt.md 旧快照(可用 /dump-system-prompt)。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
云服务部-叶林立
2026-10-21 19:16:47 -07:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent e996f59469
commit a254d4fd68
6 changed files with 185 additions and 52 deletions
+11 -8
View File
@@ -10,13 +10,16 @@ import test from "node:test";
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const hasPi = spawnSync("pi", ["--version"], { encoding: "utf8" }).status === 0;
const filesIncludePackageDir = (files: string[], packageDir: string): boolean =>
files.some((entry) => entry === packageDir || entry.startsWith(`${packageDir}/`));
test("enabled subagents wrapper loads with its bundle-owned mandatory permission entry", { skip: !hasPi }, async () => {
const packageJson = JSON.parse(await readFile(join(repositoryRoot, "package.json"), "utf8")) as {
dependencies: Record<string, string>;
files: string[];
pi: { extensions: string[] };
};
assert.ok(packageJson.files.includes("pi-subagents"));
assert.ok(filesIncludePackageDir(packageJson.files, "pi-subagents"));
assert.equal(packageJson.dependencies["@tintinweb/pi-subagents"], "file:./pi-subagents");
assert.ok(
packageJson.pi.extensions.includes("./extensions/subagents.ts"),
@@ -74,7 +77,7 @@ test("all package extensions load together without global registration conflicts
"the bundle must load the locally maintained minimal footer",
);
assert.equal(packageJson.dependencies["pi-compaction"], "file:./pi-compaction");
assert.ok(packageJson.files.includes("pi-compaction"), "the packed bundle must include maintained pi-compaction source");
assert.ok(filesIncludePackageDir(packageJson.files, "pi-compaction"), "the packed bundle must include maintained pi-compaction source");
assert.ok(
packageJson.pi.extensions.includes("./pi-compaction/index.ts"),
"the bundle must load the unified pi-compaction authority",
@@ -97,20 +100,20 @@ test("all package extensions load together without global registration conflicts
assert.equal(packageJson.dependencies["pi-ssh"], "file:./pi-ssh");
assert.match(packageJson.dependencies.ssh2 ?? "", /^\d+\.\d+\.\d+$/, "the packed bundle must install pi-ssh's runtime transport at an exact version");
assert.match(packageJson.dependencies.jiti ?? "", /^\d+\.\d+\.\d+$/, "the packed helper must load local TypeScript outside Pi at an exact version");
assert.ok(packageJson.files.includes("pi-ssh"), "the packed bundle must include the locally maintained SSH source");
assert.ok(filesIncludePackageDir(packageJson.files, "pi-ssh"), "the packed bundle must include the locally maintained SSH source");
assert.ok(packageJson.files.includes("configure.sh"), "the packed bundle must include the search/SSH config helper");
assert.ok(packageJson.pi.extensions.includes("./pi-ask-user/index.ts"), "the bundle must load pi-ask-user");
assert.equal(packageJson.dependencies["pi-ask-user"], "file:./pi-ask-user");
assert.ok(packageJson.files.includes("pi-ask-user"), "the packed bundle must include pi-ask-user source");
assert.ok(filesIncludePackageDir(packageJson.files, "pi-ask-user"), "the packed bundle must include pi-ask-user source");
assert.ok(
packageJson.pi.extensions.includes("./pi-worktree/index.ts"),
"the bundle must load the locally maintained worktree commands",
);
assert.equal(packageJson.dependencies["pi-worktree"], "file:./pi-worktree");
assert.ok(packageJson.files.includes("pi-worktree"), "the packed bundle must include pi-worktree source");
assert.ok(filesIncludePackageDir(packageJson.files, "pi-worktree"), "the packed bundle must include pi-worktree source");
const chromeExtension = "./pi-chrome/extensions/chrome-profile-bridge/index.ts";
assert.equal(packageJson.dependencies["pi-chrome"], "file:./pi-chrome", "the browser bridge must use the locally maintained source");
assert.ok(packageJson.files.includes("pi-chrome"), "the packed bundle must include the locally maintained pi-chrome source");
assert.ok(filesIncludePackageDir(packageJson.files, "pi-chrome"), "the packed bundle must include the locally maintained pi-chrome source");
assert.ok(packageJson.pi.extensions.includes(chromeExtension), "the bundle must load the local Chrome profile bridge directly");
assert.ok(
packageJson.pi.extensions.indexOf(chromeExtension) <
@@ -167,7 +170,7 @@ test("all package extensions load together without global registration conflicts
);
assert.equal(packageJson.dependencies["@smoose/pi-notify"], "file:./pi-notify");
assert.ok(
packageJson.files.includes("pi-notify"),
filesIncludePackageDir(packageJson.files, "pi-notify"),
"the packed bundle must include the locally maintained notification source",
);
assert.equal(packageJson.dependencies["@ogulcancelik/pi-minimal-footer"], "file:./pi-minimal-footer");
@@ -227,7 +230,7 @@ test("all package extensions load together without global registration conflicts
}
assert.equal(packageJson.dependencies["pi-hermes-memory"], "file:./pi-hermes-memory");
assert.ok(
packageJson.files.includes("pi-hermes-memory"),
filesIncludePackageDir(packageJson.files, "pi-hermes-memory"),
"the packed bundle must include the locally maintained Hermes memory source",
);
assert.ok(
+94
View File
@@ -0,0 +1,94 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { dirname, join, posix, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = JSON.parse(readFileSync(join(repositoryRoot, "package.json"), "utf8")) as {
dependencies: Record<string, string>;
pi: { extensions: string[] };
};
const packOutput = execFileSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], {
cwd: repositoryRoot,
encoding: "utf8",
});
const packResult = JSON.parse(packOutput.slice(packOutput.indexOf("["))) as Array<{
files: Array<{ path: string }>;
}>;
const packedFiles = new Set(packResult[0].files.map((entry) => entry.path));
test("the packed bundle ships no nested node_modules or build output", () => {
assert.ok(packedFiles.size > 0, "npm pack --dry-run must report a non-empty file list");
for (const filePath of packedFiles) {
const segments = filePath.split("/");
assert.equal(segments.includes("node_modules"), false, `${filePath} must not ship a nested node_modules tree`);
assert.equal(segments.includes("dist"), false, `${filePath} must not ship build output`);
}
});
test("every declared extension entry and file: dependency manifest is inside the packed bundle", () => {
for (const entry of packageJson.pi.extensions) {
const bundled = entry.replace(/^\.\//u, "");
assert.ok(packedFiles.has(bundled), `extension entry ${entry} must be inside the packed bundle`);
}
for (const [name, specifier] of Object.entries(packageJson.dependencies)) {
const match = /^file:\.\/(.+)$/u.exec(specifier);
if (!match) continue;
const manifest = `${match[1]}/package.json`;
assert.ok(packedFiles.has(manifest), `${name} is a file: dependency — ${manifest} must be packed`);
}
});
test("relative import specifiers inside packed sources resolve to packed files", () => {
const specifierPatterns = [
/\bfrom\s+["'](\.{1,2}\/[^"']+)["']/gu,
/(?<![\w$.])import\s*\(\s*["'](\.{1,2}\/[^"']+)["']\s*\)/gu,
/\bnew\s+URL\s*\(\s*["'](\.{1,2}\/[^"']+)["']\s*,\s*import\.meta\.url\s*\)/gu,
];
const isPacked = (resolved: string): boolean => {
if (packedFiles.has(resolved)) return true;
const candidates = [`${resolved}.ts`, `${resolved}.js`, `${resolved}.mjs`, `${resolved}/index.ts`];
if (candidates.some((candidate) => packedFiles.has(candidate))) return true;
// TypeScript sources import sibling ".js"/".mjs" specifiers that map to ".ts" files.
const swapped = resolved.replace(/\.(js|mjs|cjs)$/u, ".ts");
if (swapped !== resolved && packedFiles.has(swapped)) return true;
// new URL("../config/prompt/", import.meta.url) style directory references.
for (const filePath of packedFiles) {
if (filePath.startsWith(`${resolved}/`)) return true;
}
return false;
};
const unresolved: string[] = [];
for (const filePath of packedFiles) {
if (!/\.(ts|js|mjs)$/u.test(filePath)) continue;
const segments = filePath.split("/");
if (/\.test\.(ts|js|mjs)$/u.test(filePath) || segments.includes("test") || segments.includes("tests")) continue;
const source = readFileSync(join(repositoryRoot, filePath), "utf8");
for (const pattern of specifierPatterns) {
pattern.lastIndex = 0;
for (const match of source.matchAll(pattern)) {
const resolved = posix.normalize(posix.join(posix.dirname(filePath), match[1])).replace(/\/$/u, "");
if (resolved === ".." || resolved.startsWith("../")) continue;
if (resolved.split("/").includes("node_modules")) continue;
if (!isPacked(resolved)) unresolved.push(`${filePath} -> ${match[1]}`);
}
}
}
assert.deepEqual(unresolved, [], "packed sources reference files missing from the bundle");
});
test("prompt config and lifecycle scripts are inside the packed bundle", () => {
const tracked = execFileSync("git", ["ls-files", "config/prompt"], {
cwd: repositoryRoot,
encoding: "utf8",
})
.split("\n")
.filter(Boolean);
for (const filePath of [...tracked, "install.sh", "update.sh", "uninstall.sh", "configure.sh", "scripts/lib.sh"]) {
assert.ok(packedFiles.has(filePath), `${filePath} must be inside the packed bundle`);
}
});
+4 -1
View File
@@ -8,6 +8,9 @@ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.
pi: { extensions: string[]; skills: string[] };
};
const filesIncludePackageDir = (files: string[], packageDir: string): boolean =>
files.some((entry) => entry === packageDir || entry.startsWith(`${packageDir}/`));
test("pi-web-search is the single search surface and reads credentials from auth.json", () => {
const entries = packageJson.pi.extensions;
assert.ok(entries.includes("./pi-web-search/index.ts"), "the bundle must load pi-web-search");
@@ -27,7 +30,7 @@ test("pi-web-search is the single search surface and reads credentials from auth
test("pi-web-search ships as a maintained file: dependency without upstream search packages", () => {
assert.equal(packageJson.dependencies["pi-web-search"], "file:./pi-web-search");
assert.ok(packageJson.files.includes("pi-web-search"));
assert.ok(filesIncludePackageDir(packageJson.files, "pi-web-search"));
assert.equal(packageJson.dependencies["@tavily/pi-extension"], undefined);
assert.equal(packageJson.dependencies["@keenable/pi-search"], undefined);
assert.equal(packageJson.dependencies["pi-mcp-adapter"], undefined, "Exa is a direct client now — no MCP adapter");