1st-pi 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,15 @@
8
8
 
9
9
  ## 安装
10
10
 
11
- 需要 Pi `>=1.1.0`;`@tintinweb/pi-subagents` 0.20 起要求该版本,`pi-btw` 0.7 起要求 `>=0.85.1`。
11
+ 需要 Pi `>=1.1.0`;`@tintinweb/pi-subagents` 0.20 起要求该版本,`pi-btw` 0.7 起要求 `>=0.85.1`。`pi-web-access` 0.44 要求 Node `>=22.19.0`。
12
+
13
+ macOS 上 `pi-readseek` 的原生二进制动态链接 Homebrew 库,缺少任意一个时 `read`/`edit`/`write` 会因 dyld 报错失败(0.11 起新增 `ftxui`,0.12.1 起新增 `re2`):
14
+
15
+ ```bash
16
+ brew install abseil fmt ftxui gettext glib openssl@4 re2 vips xxhash
17
+ ```
18
+
19
+ preset 的 readseek adapter 会在启动时读取该二进制的 Mach-O load commands,若有 dylib 缺失,就在 session 开始时给出 warning 和对应的 `brew install` 命令(会遵循 `READSEEK_BINARY` 覆盖)。
12
20
 
13
21
  ### GitHub
14
22
 
@@ -131,7 +139,7 @@ Preset 默认开启以下 TUI 增强,session 中持久化的个人选择优先
131
139
  | [@juicesharp/rpiv-ask-user-question](https://www.npmjs.com/package/@juicesharp/rpiv-ask-user-question) | 多问题、选项预览和自由输入的结构化提问 UI |
132
140
  | [@narumitw/pi-goal](https://www.npmjs.com/package/@narumitw/pi-goal) | Session-scoped `/goal` 持续执行与完成/阻塞门禁;可选 experimental ordered-goal queue |
133
141
  | [@ff-labs/pi-fff](https://www.npmjs.com/package/@ff-labs/pi-fff) | 本地 Rust 模糊文件/内容搜索与 `@` 自动补全;`find`/`grep` 支持 workspace-relative 约束和 workspace 外的绝对路径 |
134
- | [pi-readseek](https://www.npmjs.com/package/pi-readseek) | Parser-backed code map/search、PDF index/view、definition/reference、hashline edit 与 workspace rename;其 edit 可进行带警告的 fuzzy anchor relocation,不等同于严格 stale rejection |
142
+ | [pi-readseek](https://www.npmjs.com/package/pi-readseek) | Parser-backed code map/search、PDF index/view、definition/reference、syntax check、hashline edit 与 workspace rename;caption/OCR/object detection 只在显式调用对应工具时使用本地已安装模型,不会自动下载;其 edit 可进行带警告的 fuzzy anchor relocation,不等同于严格 stale rejection |
135
143
  | [pi-btw](https://www.npmjs.com/package/pi-btw) | 不污染主上下文的并行 `/btw` 旁路对话 |
136
144
  | [@mrclrchtr/supi-context](https://www.npmjs.com/package/@mrclrchtr/supi-context) | `/supi-context` 上下文组成与 token 占用报告 |
137
145
  | [@tintinweb/pi-subagents](https://www.npmjs.com/package/@tintinweb/pi-subagents) | Claude Code 风格 sub-agents |
@@ -140,7 +148,7 @@ Preset 默认开启以下 TUI 增强,session 中持久化的个人选择优先
140
148
 
141
149
  这些包位于普通 npm `dependencies`,安装 preset 时由 npm 获取,不会塞入 `1st-pi` tarball。`extensions/packages/*.ts` 是只负责调用 dependency extension entrypoint 的 thin adapters;因此依赖可以正常 hoist,同时 Pi manifest 只引用本包内稳定路径。FFF 也会由 npm 在 macOS、Linux 和 Windows 上选择正确的原生 binary。
142
150
 
143
- 当前 dependency extension 版本以 `package.json` 与 lockfile 为准;维护时使用 npm `latest` 稳定标签,并通过 `npm outdated` 确认没有落后版本。
151
+ 当前 dependency extension 版本以 `package.json` 与 lockfile 为准;维护时使用 npm `latest` 稳定标签,并运行 `npm run check:outdated` 确认没有落后版本。该检查会指出 `package.json` 范围挡住最新版的依赖(例如 `^0.20.0` 不包含 0.21.0,`npm install` 不会提示),以及 lockfile 落后于范围的依赖。
144
152
 
145
153
  Themes 以及 `pi-init` skill 是发布所需的静态资源,会从锁定依赖同步到本包。`pi-web-access` 从 0.14.0 起不再提供 `librarian` skill,preset 同步移除该过期副本,保留 extension 自身的搜索、抓取与 source-check surface。更新 dependencies 后运行:
146
154
 
@@ -190,6 +198,14 @@ Tool Call Markers 默认分组相邻调用并折叠工具详情;可用 `PI_TOO
190
198
 
191
199
  该 SOP 覆盖 license/compatibility/ownership gate、thin adapter、README 同步、hoisted npm 安装、native binary、tarball size 与 clean-room smoke tests。
192
200
 
201
+ `npm run smoke` 在隔离的 `PI_CODING_AGENT_DIR` 中通过 Pi SDK 加载 preset,不访问网络、不调用模型,检查:
202
+
203
+ - 扩展加载无错误且与 `package.json#pi.extensions` 一致;工具无重名 owner;`read/edit/write`、`grep/find`、`bash/ls` 的 owner 符合上文
204
+ - 全新安装时写入 preset 默认值,已有的显式 `subagents.json` / workflow 设置保持不变
205
+ - `pi-tasks` 与 `pi-subagents` 的 RPC 协议版本匹配
206
+ - readseek 在临时文件上执行 read → anchored edit → write,以及 macOS dylib 检测与 `otool -L` 一致
207
+ - 工具与命令集合与 `scripts/smoke-surface.json` 快照一致;有意变更时用 `npm run smoke -- --update` 更新快照,并在 diff 中审阅
208
+
193
209
  ## 发布
194
210
 
195
211
  ```bash
@@ -197,9 +213,11 @@ npm login
197
213
  npm publish
198
214
  ```
199
215
 
200
- 发布前建议检查包内容:
216
+ 发布前建议运行检查并确认包内容:
201
217
 
202
218
  ```bash
219
+ npm run check:outdated
220
+ npm run smoke
203
221
  npm pack --dry-run
204
222
  ```
205
223
 
@@ -0,0 +1,109 @@
1
+ import { closeSync, existsSync, openSync, readSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+
5
+ const MH_MAGIC_64 = 0xfeedfacf;
6
+ const FAT_MAGIC = 0xcafebabe;
7
+ const CPU_TYPE_BY_ARCH: Record<string, number> = { arm64: 0x0100000c, x64: 0x01000007 };
8
+ const LC_LOAD_DYLIB = 0x0c;
9
+ const LC_LOAD_WEAK_DYLIB = 0x80000018;
10
+ const LC_REEXPORT_DYLIB = 0x8000001f;
11
+ const LC_LOAD_UPWARD_DYLIB = 0x80000023;
12
+ const DYLIB_COMMANDS = new Set([LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_LOAD_UPWARD_DYLIB]);
13
+ const BREW_FORMULA = /^(?:\/opt\/homebrew|\/usr\/local)\/opt\/([^/]+)\//;
14
+
15
+ function readAt(fd: number, position: number, length: number): Buffer {
16
+ const buffer = Buffer.alloc(length);
17
+ return buffer.subarray(0, readSync(fd, buffer, 0, length, position));
18
+ }
19
+
20
+ /**
21
+ * Reads only the 64-bit Mach-O header and load commands for `arch`, selecting
22
+ * the matching slice of a universal binary. Returns undefined for other formats.
23
+ */
24
+ export function readMachOLoadCommands(path: string, arch: string = process.arch): Buffer | undefined {
25
+ const fd = openSync(path, "r");
26
+ try {
27
+ let offset = 0;
28
+ const head = readAt(fd, 0, 4096);
29
+ if (head.length >= 8 && head.readUInt32BE(0) === FAT_MAGIC) {
30
+ const count = head.readUInt32BE(4);
31
+ const slice = Array.from({ length: count }, (_, i) => 8 + i * 20)
32
+ .find((at) => at + 20 <= head.length && head.readUInt32BE(at) === CPU_TYPE_BY_ARCH[arch]);
33
+ if (slice === undefined) return undefined;
34
+ offset = head.readUInt32BE(slice + 8);
35
+ }
36
+ const header = readAt(fd, offset, 32);
37
+ if (header.length < 32 || header.readUInt32LE(0) !== MH_MAGIC_64) return undefined;
38
+ return readAt(fd, offset, 32 + header.readUInt32LE(20));
39
+ } finally {
40
+ closeSync(fd);
41
+ }
42
+ }
43
+
44
+ /** Returns the dylib install names linked by a thin 64-bit Mach-O header plus its load commands. */
45
+ export function machODylibs(binary: Buffer): string[] {
46
+ if (binary.length < 32 || binary.readUInt32LE(0) !== MH_MAGIC_64) return [];
47
+
48
+ const commandCount = binary.readUInt32LE(16);
49
+ const dylibs: string[] = [];
50
+ let at = 32;
51
+ for (let i = 0; i < commandCount && at + 8 <= binary.length; i++) {
52
+ const cmd = binary.readUInt32LE(at);
53
+ const size = binary.readUInt32LE(at + 4);
54
+ if (size < 8) break;
55
+ if (DYLIB_COMMANDS.has(cmd)) {
56
+ const nameStart = at + binary.readUInt32LE(at + 8);
57
+ const nameEnd = binary.indexOf(0, nameStart);
58
+ dylibs.push(binary.toString("utf8", nameStart, nameEnd < 0 || nameEnd > at + size ? at + size : nameEnd));
59
+ }
60
+ at += size;
61
+ }
62
+ return dylibs;
63
+ }
64
+
65
+ /**
66
+ * Dylibs that dyld would fail to load. System libraries live in the dyld shared
67
+ * cache rather than on disk, and `@rpath`-style names are relative, so only
68
+ * other absolute paths can be checked.
69
+ */
70
+ export function missingDylibs(dylibs: string[], exists: (path: string) => boolean = existsSync): string[] {
71
+ return dylibs.filter(
72
+ (dylib) => dylib.startsWith("/") && !dylib.startsWith("/usr/lib/") && !dylib.startsWith("/System/") && !exists(dylib),
73
+ );
74
+ }
75
+
76
+ export function missingDylibsMessage(missing: string[]): string {
77
+ const formulas = [...new Set(missing.map((dylib) => BREW_FORMULA.exec(dylib)?.[1]).filter((f): f is string => !!f))];
78
+ const unknown = missing.filter((dylib) => !BREW_FORMULA.test(dylib));
79
+ const lines = ["pi-readseek's native binary is missing shared libraries, so read/edit/write will fail until they are installed."];
80
+ if (formulas.length > 0) lines.push(`Run: brew install ${formulas.join(" ")}`);
81
+ if (unknown.length > 0) lines.push(`Missing: ${unknown.join(", ")}`);
82
+ return lines.join("\n");
83
+ }
84
+
85
+ /** Mirrors pi-readseek's own lookup: READSEEK_BINARY, then the platform package beside @jarkkojs/readseek. */
86
+ export function resolveReadseekBinary(): string | undefined {
87
+ const override = process.env.READSEEK_BINARY;
88
+ if (override) return override;
89
+ if (process.platform !== "darwin") return undefined;
90
+ try {
91
+ const require = createRequire(createRequire(import.meta.url).resolve("pi-readseek"));
92
+ const metaDir = dirname(require.resolve("@jarkkojs/readseek/package.json"));
93
+ const platformJson = require.resolve(`@jarkkojs/readseek-darwin-${process.arch}/package.json`, { paths: [metaDir] });
94
+ return join(dirname(platformJson), "bin", "readseek");
95
+ } catch {
96
+ // pi-readseek reports a missing platform package itself.
97
+ return undefined;
98
+ }
99
+ }
100
+
101
+ /** Returns a warning when the readseek binary cannot load on this machine, otherwise undefined. */
102
+ export function readseekDylibWarning(): string | undefined {
103
+ if (process.platform !== "darwin") return undefined;
104
+ const binary = resolveReadseekBinary();
105
+ if (!binary || !existsSync(binary)) return undefined;
106
+ const commands = readMachOLoadCommands(binary);
107
+ const missing = commands ? missingDylibs(machODylibs(commands)) : [];
108
+ return missing.length > 0 ? missingDylibsMessage(missing) : undefined;
109
+ }
@@ -1,10 +1,21 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import registerReadseek from "pi-readseek";
3
+ import { readseekDylibWarning } from "./readseek-dylibs.ts";
3
4
  import { registerWithToolTransform } from "./tool-registration-transform.ts";
4
5
 
5
6
  const READSEEK_GREP_PREFERENCE = "Prefer readSeek_grep over grep when both are available;";
6
7
 
7
8
  export default function readseekAdapter(pi: ExtensionAPI): void {
9
+ // The macOS binary links Homebrew dylibs it does not ship; surface the exact
10
+ // brew command up front instead of a dyld crash on the first read.
11
+ const dylibWarning = readseekDylibWarning();
12
+ if (dylibWarning) {
13
+ pi.on("session_start", async (_event, ctx) => {
14
+ if (ctx.hasUI) ctx.ui.notify(dylibWarning, "warning");
15
+ else console.warn(dylibWarning);
16
+ });
17
+ }
18
+
8
19
  registerWithToolTransform(pi, registerReadseek, (tool) => {
9
20
  if (tool.name !== "readSeek_grep") return tool;
10
21
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "1st-pi",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "est7's Pi coding agent preset — skills, extensions, prompts, and themes.",
5
5
  "license": "MIT",
6
6
  "author": "est7",
@@ -33,23 +33,25 @@
33
33
  "homepage": "https://github.com/est7/1st-pi#readme",
34
34
  "dependencies": {
35
35
  "@ff-labs/pi-fff": "^0.11.0",
36
- "@juicesharp/rpiv-ask-user-question": "^2.12.0",
37
- "@mrclrchtr/supi-context": "^8.0.1",
38
- "@narumitw/pi-goal": "^0.54.11",
36
+ "@juicesharp/rpiv-ask-user-question": "^2.15.0",
37
+ "@mrclrchtr/supi-context": "^11.0.1",
38
+ "@narumitw/pi-goal": "^0.54.13",
39
39
  "@pi-kaush/pi-tool-call-markers": "^0.3.19",
40
40
  "@quintinshaw/pi-dynamic-workflows": "^3.14.0",
41
- "@tintinweb/pi-subagents": "^0.20.0",
42
- "@tintinweb/pi-tasks": "^0.9.0",
43
- "pi-btw": "^0.7.1",
41
+ "@tintinweb/pi-subagents": "^0.22.0",
42
+ "@tintinweb/pi-tasks": "^0.9.1",
43
+ "pi-btw": "^0.8.0",
44
44
  "pi-everforest-tui": "^0.2.1",
45
45
  "pi-init": "^1.0.0",
46
- "pi-readseek": "^0.10.4",
46
+ "pi-readseek": "^0.12.3",
47
47
  "pi-session-name": "^0.1.7",
48
- "pi-web-access": "^0.38.0"
48
+ "pi-web-access": "^0.44.0"
49
49
  },
50
50
  "scripts": {
51
51
  "sync:resources": "node scripts/sync-package-resources.mjs",
52
52
  "check:resources": "node scripts/sync-package-resources.mjs --check",
53
+ "check:outdated": "node scripts/check-outdated.mjs",
54
+ "smoke": "node scripts/smoke.mjs",
53
55
  "prepack": "npm run check:resources",
54
56
  "pack:dry-run": "npm pack --dry-run --json"
55
57
  },
@@ -67,11 +67,15 @@ Run:
67
67
 
68
68
  ```bash
69
69
  npm run check:resources
70
+ npm run check:outdated
71
+ npm run smoke
70
72
  npm audit --omit=dev
71
73
  npm pack --dry-run --json
72
74
  git diff --check
73
75
  ```
74
76
 
77
+ `npm run smoke` fails when the tool or command surface differs from `scripts/smoke-surface.json`. Review the reported additions and removals against the intended change, then run `npm run smoke -- --update` and commit the snapshot with the dependency change. When adding a preset default or ownership rule, extend `scripts/smoke.mjs` with the fresh-install and explicit-preference cases.
78
+
75
79
  Then verify:
76
80
 
77
81
  1. local source: `pi --no-extensions -e .`