mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
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:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
e996f59469
commit
a254d4fd68
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
- 系统提示词单一所有权:`extensions/system-prompt.ts` 排在 `pi.extensions` 最后,是唯一允许在 `before_agent_start` 返回 `systemPrompt` 的扩展;其他扩展只能经 `systemPromptOptions.sections.<name>` 贡献分区,第三方扩展必须经 `extensions/` wrapper + `prompt-guard.ts` 加载,新增工具必须在 `config/prompt/tools.jsonc` 登记。宿主传入 `customPrompt`(子 Agent / SYSTEM.md)时不覆盖其开场白;子 Agent 有独立提示词画像 `config/prompt/subagent/`(preamble + 内置 Agent),判定规则与路由 scope/`subagentExclude` 细则见 `extensions/AGENTS.md`。
|
- 系统提示词单一所有权:`extensions/system-prompt.ts` 排在 `pi.extensions` 最后,是唯一允许在 `before_agent_start` 返回 `systemPrompt` 的扩展;其他扩展只能经 `systemPromptOptions.sections.<name>` 贡献分区,第三方扩展必须经 `extensions/` wrapper + `prompt-guard.ts` 加载,新增工具必须在 `config/prompt/tools.jsonc` 登记。宿主传入 `customPrompt`(子 Agent / SYSTEM.md)时不覆盖其开场白;子 Agent 有独立提示词画像 `config/prompt/subagent/`(preamble + 内置 Agent),判定规则与路由 scope/`subagentExclude` 细则见 `extensions/AGENTS.md`。
|
||||||
- 加载顺序:`permission-system` → `subagents` → `pi-ssh` → `pi-chrome` → `pi-tool-search` → `system-prompt`(最后);`pi-hermes-memory` 必须排在 `pi-compaction` 之前(`session_before_compact` 取消语义),`pi-compaction` 在 `pi-minimal-footer` 与 `context-view` 之前。
|
- 加载顺序:`permission-system` → `subagents` → `pi-ssh` → `pi-chrome` → `pi-tool-search` → `system-prompt`(最后);`pi-hermes-memory` 必须排在 `pi-compaction` 之前(`session_before_compact` 取消语义),`pi-compaction` 在 `pi-minimal-footer` 与 `context-view` 之前。
|
||||||
- 依赖:外部依赖在根 `package.json` 用精确版本,自维护扩展一律 `file:` 本地依赖并更新锁文件;Pi 核心包只作 optional host peer,根 runtime deps 不含 Pi runtime,任何目录不得再装一套 Pi runtime;各子包用于测试的 `@earendil-works/*` devDependencies 一律写 `"latest"`,经 `scripts/update-pi-devdeps.sh` 统一刷新;原生构建依赖只按锁定版本进根 `allowScripts`(当前仅 `better-sqlite3@12.11.1`;pi-hermes-memory 的 `better-sqlite3@13` 用包内 N-API 预编译,不新增放行)。不要用仓库级 `.pi/settings.json` 代替组合包依赖。
|
- 依赖:外部依赖在根 `package.json` 用精确版本,自维护扩展一律 `file:` 本地依赖并更新锁文件;Pi 核心包只作 optional host peer,根 runtime deps 不含 Pi runtime,任何目录不得再装一套 Pi runtime;各子包用于测试的 `@earendil-works/*` devDependencies 一律写 `"latest"`,经 `scripts/update-pi-devdeps.sh` 统一刷新;原生构建依赖只按锁定版本进根 `allowScripts`(当前仅 `better-sqlite3@12.11.1`;pi-hermes-memory 的 `better-sqlite3@13` 用包内 N-API 预编译,不新增放行)。不要用仓库级 `.pi/settings.json` 代替组合包依赖。根 `files` 对子包按运行时子路径白名单列出(源码、package.json、README/LICENSE/CHANGELOG/UPSTREAM),不得整目录列入,以免打入嵌套 node_modules、dist 和测试;由 `tests/package-files.test.ts` 守护。
|
||||||
- 配置:`config/` 下全部文件是权威源、必须提交;不得读写 `settings.json` 自定义段、agent 目录独立配置文件或以环境变量作持久默认值;`config/` 只放机器无关值,路径类默认值在代码中运行时计算。`<agentDir>/my-pi/` 只放可丢弃运行时状态(日志、缓存),连同旧 `extensions/pi-permission-system/` 残留、构建产物与依赖目录都不提交;用户数据(记忆、会话、`auth.json` 等)不属于组合包配置,也不得放入该目录。
|
- 配置:`config/` 下全部文件是权威源、必须提交;不得读写 `settings.json` 自定义段、agent 目录独立配置文件或以环境变量作持久默认值;`config/` 只放机器无关值,路径类默认值在代码中运行时计算。`<agentDir>/my-pi/` 只放可丢弃运行时状态(日志、缓存),连同旧 `extensions/pi-permission-system/` 残留、构建产物与依赖目录都不提交;用户数据(记忆、会话、`auth.json` 等)不属于组合包配置,也不得放入该目录。
|
||||||
- 组合包不加载任何 MCP adapter/SDK:Exa 与 CodeGraph 均用自有直连客户端。
|
- 组合包不加载任何 MCP adapter/SDK:Exa 与 CodeGraph 均用自有直连客户端。
|
||||||
- 导入/同步上游:只从官方 Git 仓库的明确 tag/commit 取源码快照,禁止 npm tarball/缓存/`node_modules`/`dist/` 等预编译产物作导入源;保留上游 LICENSE、作者、测试与来源说明,移除嵌套 `.git`、`.pi` 状态、`node_modules` 与构建产物;同步前先核对本地改动再按明确范围移植,不得整体覆盖。各目录的维护约定见 `AGENTS.md`,每个自维护目录必须有中文 `UPSTREAM.md` 记录来源,上游原文文件不翻译不改写。
|
- 导入/同步上游:只从官方 Git 仓库的明确 tag/commit 取源码快照,禁止 npm tarball/缓存/`node_modules`/`dist/` 等预编译产物作导入源;保留上游 LICENSE、作者、测试与来源说明,移除嵌套 `.git`、`.pi` 状态、`node_modules` 与构建产物;同步前先核对本地改动再按明确范围移植,不得整体覆盖。各目录的维护约定见 `AGENTS.md`,每个自维护目录必须有中文 `UPSTREAM.md` 记录来源,上游原文文件不翻译不改写。
|
||||||
|
|||||||
+75
-11
@@ -10,15 +10,69 @@
|
|||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"extensions",
|
"extensions",
|
||||||
"pi-ask-user",
|
"pi-ask-user/index.ts",
|
||||||
"pi-compaction",
|
"pi-ask-user/src",
|
||||||
"pi-chrome",
|
"pi-ask-user/package.json",
|
||||||
"pi-hermes-memory",
|
"pi-ask-user/README.md",
|
||||||
"pi-rtk-optimizer",
|
"pi-ask-user/LICENSE",
|
||||||
"pi-minimal-footer",
|
"pi-ask-user/UPSTREAM.md",
|
||||||
"pi-subagents",
|
"pi-compaction/index.ts",
|
||||||
"pi-notify",
|
"pi-compaction/src",
|
||||||
"pi-ssh",
|
"pi-compaction/package.json",
|
||||||
|
"pi-compaction/README.md",
|
||||||
|
"pi-compaction/CHANGELOG.md",
|
||||||
|
"pi-compaction/LICENSES",
|
||||||
|
"pi-compaction/UPSTREAM.md",
|
||||||
|
"pi-chrome/extensions",
|
||||||
|
"pi-chrome/src",
|
||||||
|
"pi-chrome/package.json",
|
||||||
|
"pi-chrome/README.md",
|
||||||
|
"pi-chrome/CHANGELOG.md",
|
||||||
|
"pi-chrome/SECURITY.md",
|
||||||
|
"pi-chrome/LICENSE",
|
||||||
|
"pi-chrome/UPSTREAM.md",
|
||||||
|
"pi-hermes-memory/src",
|
||||||
|
"pi-hermes-memory/package.json",
|
||||||
|
"pi-hermes-memory/README.md",
|
||||||
|
"pi-hermes-memory/CHANGELOG.md",
|
||||||
|
"pi-hermes-memory/LICENSE",
|
||||||
|
"pi-hermes-memory/UPSTREAM.md",
|
||||||
|
"pi-rtk-optimizer/index.ts",
|
||||||
|
"pi-rtk-optimizer/src",
|
||||||
|
"pi-rtk-optimizer/package.json",
|
||||||
|
"pi-rtk-optimizer/README.md",
|
||||||
|
"pi-rtk-optimizer/CHANGELOG.md",
|
||||||
|
"pi-rtk-optimizer/LICENSE",
|
||||||
|
"pi-rtk-optimizer/UPSTREAM.md",
|
||||||
|
"pi-minimal-footer/index.ts",
|
||||||
|
"pi-minimal-footer/status.ts",
|
||||||
|
"pi-minimal-footer/settings.ts",
|
||||||
|
"pi-minimal-footer/assets",
|
||||||
|
"pi-minimal-footer/package.json",
|
||||||
|
"pi-minimal-footer/README.md",
|
||||||
|
"pi-minimal-footer/LICENSE",
|
||||||
|
"pi-minimal-footer/UPSTREAM.md",
|
||||||
|
"pi-subagents/src",
|
||||||
|
"pi-subagents/package.json",
|
||||||
|
"pi-subagents/README.md",
|
||||||
|
"pi-subagents/CHANGELOG.md",
|
||||||
|
"pi-subagents/SECURITY.md",
|
||||||
|
"pi-subagents/LICENSE",
|
||||||
|
"pi-subagents/UPSTREAM.md",
|
||||||
|
"pi-notify/src",
|
||||||
|
"pi-notify/package.json",
|
||||||
|
"pi-notify/README.md",
|
||||||
|
"pi-notify/LICENSE",
|
||||||
|
"pi-notify/UPSTREAM.md",
|
||||||
|
"pi-ssh/index.ts",
|
||||||
|
"pi-ssh/permission-integration.ts",
|
||||||
|
"pi-ssh/src",
|
||||||
|
"pi-ssh/scripts",
|
||||||
|
"pi-ssh/package.json",
|
||||||
|
"pi-ssh/README.md",
|
||||||
|
"pi-ssh/CHANGELOG.md",
|
||||||
|
"pi-ssh/LICENSE",
|
||||||
|
"pi-ssh/UPSTREAM.md",
|
||||||
"pi-tool-search/extensions",
|
"pi-tool-search/extensions",
|
||||||
"pi-tool-search/docs",
|
"pi-tool-search/docs",
|
||||||
"pi-tool-search/package.json",
|
"pi-tool-search/package.json",
|
||||||
@@ -26,8 +80,18 @@
|
|||||||
"pi-tool-search/CHANGELOG.md",
|
"pi-tool-search/CHANGELOG.md",
|
||||||
"pi-tool-search/LICENSE",
|
"pi-tool-search/LICENSE",
|
||||||
"pi-tool-search/UPSTREAM.md",
|
"pi-tool-search/UPSTREAM.md",
|
||||||
"pi-web-search",
|
"pi-web-search/index.ts",
|
||||||
"pi-worktree",
|
"pi-web-search/auth.ts",
|
||||||
|
"pi-web-search/router.ts",
|
||||||
|
"pi-web-search/clients",
|
||||||
|
"pi-web-search/package.json",
|
||||||
|
"pi-web-search/README.md",
|
||||||
|
"pi-web-search/LICENSE",
|
||||||
|
"pi-worktree/index.ts",
|
||||||
|
"pi-worktree/src",
|
||||||
|
"pi-worktree/package.json",
|
||||||
|
"pi-worktree/README.md",
|
||||||
|
"pi-worktree/LICENSE",
|
||||||
"pi-permission-system/LICENSES",
|
"pi-permission-system/LICENSES",
|
||||||
"pi-permission-system/docs/auto-review",
|
"pi-permission-system/docs/auto-review",
|
||||||
"pi-permission-system/src",
|
"pi-permission-system/src",
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.
|
|
||||||
|
|
||||||
Available tools:
|
|
||||||
- read: Read file contents
|
|
||||||
- bash: Execute bash commands (ls, grep, find, etc.)
|
|
||||||
- edit: Make precise file edits with exact text replacement, including multiple disjoint edits in one call
|
|
||||||
- write: Create or overwrite files
|
|
||||||
|
|
||||||
In addition to the tools above, you may have access to other custom tools depending on the project.
|
|
||||||
|
|
||||||
Guidelines:
|
|
||||||
- Use bash for file operations like ls, rg, find
|
|
||||||
- Use read to examine files instead of cat or sed.
|
|
||||||
- You can inspect PI_* environment variables for current model and session details.
|
|
||||||
- Use edit for precise changes (edits[].oldText must match exactly)
|
|
||||||
- When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls
|
|
||||||
- Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.
|
|
||||||
- Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.
|
|
||||||
- Use write only for new files or complete rewrites.
|
|
||||||
- Be concise in your responses
|
|
||||||
- Show file paths clearly when working with files
|
|
||||||
|
|
||||||
Pi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):
|
|
||||||
- Main documentation: /Users/sia/.nvm/versions/node/v24.16.0/lib/node_modules/@earendil-works/pi-coding-agent/README.md
|
|
||||||
- Additional docs: /Users/sia/.nvm/versions/node/v24.16.0/lib/node_modules/@earendil-works/pi-coding-agent/docs
|
|
||||||
- Examples: /Users/sia/.nvm/versions/node/v24.16.0/lib/node_modules/@earendil-works/pi-coding-agent/examples (extensions, custom tools, SDK)
|
|
||||||
- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory
|
|
||||||
- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)
|
|
||||||
- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing
|
|
||||||
- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)
|
|
||||||
Current working directory: /Users/sia/code/my-pi
|
|
||||||
@@ -10,13 +10,16 @@ import test from "node:test";
|
|||||||
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
const hasPi = spawnSync("pi", ["--version"], { encoding: "utf8" }).status === 0;
|
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 () => {
|
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 {
|
const packageJson = JSON.parse(await readFile(join(repositoryRoot, "package.json"), "utf8")) as {
|
||||||
dependencies: Record<string, string>;
|
dependencies: Record<string, string>;
|
||||||
files: string[];
|
files: string[];
|
||||||
pi: { extensions: 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.equal(packageJson.dependencies["@tintinweb/pi-subagents"], "file:./pi-subagents");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
packageJson.pi.extensions.includes("./extensions/subagents.ts"),
|
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",
|
"the bundle must load the locally maintained minimal footer",
|
||||||
);
|
);
|
||||||
assert.equal(packageJson.dependencies["pi-compaction"], "file:./pi-compaction");
|
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(
|
assert.ok(
|
||||||
packageJson.pi.extensions.includes("./pi-compaction/index.ts"),
|
packageJson.pi.extensions.includes("./pi-compaction/index.ts"),
|
||||||
"the bundle must load the unified pi-compaction authority",
|
"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.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.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.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.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.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.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(
|
assert.ok(
|
||||||
packageJson.pi.extensions.includes("./pi-worktree/index.ts"),
|
packageJson.pi.extensions.includes("./pi-worktree/index.ts"),
|
||||||
"the bundle must load the locally maintained worktree commands",
|
"the bundle must load the locally maintained worktree commands",
|
||||||
);
|
);
|
||||||
assert.equal(packageJson.dependencies["pi-worktree"], "file:./pi-worktree");
|
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";
|
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.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.includes(chromeExtension), "the bundle must load the local Chrome profile bridge directly");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
packageJson.pi.extensions.indexOf(chromeExtension) <
|
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.equal(packageJson.dependencies["@smoose/pi-notify"], "file:./pi-notify");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
packageJson.files.includes("pi-notify"),
|
filesIncludePackageDir(packageJson.files, "pi-notify"),
|
||||||
"the packed bundle must include the locally maintained notification source",
|
"the packed bundle must include the locally maintained notification source",
|
||||||
);
|
);
|
||||||
assert.equal(packageJson.dependencies["@ogulcancelik/pi-minimal-footer"], "file:./pi-minimal-footer");
|
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.equal(packageJson.dependencies["pi-hermes-memory"], "file:./pi-hermes-memory");
|
||||||
assert.ok(
|
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",
|
"the packed bundle must include the locally maintained Hermes memory source",
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -8,6 +8,9 @@ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.
|
|||||||
pi: { extensions: string[]; skills: string[] };
|
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", () => {
|
test("pi-web-search is the single search surface and reads credentials from auth.json", () => {
|
||||||
const entries = packageJson.pi.extensions;
|
const entries = packageJson.pi.extensions;
|
||||||
assert.ok(entries.includes("./pi-web-search/index.ts"), "the bundle must load pi-web-search");
|
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", () => {
|
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.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["@tavily/pi-extension"], undefined);
|
||||||
assert.equal(packageJson.dependencies["@keenable/pi-search"], 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");
|
assert.equal(packageJson.dependencies["pi-mcp-adapter"], undefined, "Exa is a direct client now — no MCP adapter");
|
||||||
|
|||||||
Reference in New Issue
Block a user