@nailuogg/pi-find-packages 0.1.3 → 0.1.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
@@ -7,7 +7,7 @@ A [pi](https://pi.dev) extension providing a local catalog of the pi package eco
7
7
  - **Offline catalog**: syncs the full `keywords:pi-package` corpus (~5k+ packages) from npm to local JSONL; search with jq/grep over descriptions, names, and keywords — no dependence on npm's keyword-matched search
8
8
  - **`/find-packages <need>`**: retrieve candidates → read-only source analysis in a sandbox → comparison table and recommendation against criteria (feature overlap / peer compatibility / maintenance activity / supply-chain signals)
9
9
  - **Cold start**: the package ships a bundled catalog snapshot (`data/catalog.jsonl.gz`); install and use immediately with zero network requests on first run
10
- - **Docker isolation by default**: candidate repos are cloned and unpacked inside a credential-free, non-root container; isolation can be disabled explicitly (a risk warning is shown on every use — not recommended)
10
+ - **Docker isolation by default**: candidate repos are fetched and unpacked inside a credential-free, non-root container (read-only root, no capabilities, no-new-privileges, tmpfs work dirs; no host source mount); isolation can be disabled explicitly (a risk warning is shown on every use — not recommended)
11
11
 
12
12
  ## Install
13
13
 
@@ -39,9 +39,7 @@ qmd collection add "$HOME/.pi/agent/data/pi-find-packages/readmes" --name pi-pkg
39
39
  qmd collection show pi-pkg-readmes # confirm the path matches your data directory
40
40
  ```
41
41
 
42
- Substitute your real data directory when `PI_CODING_AGENT_DIR` is set. Afterwards
43
- `/find-packages` keeps the corpus current on its own: each reviewed candidate's README is
44
- saved into `readmes/` and indexed with `qmd update && qmd embed`.
42
+ Substitute your real data directory when `PI_CODING_AGENT_DIR` is set. Afterwards `/find-packages` uses qmd results only as leads: verify each candidate against its current npm version and the cached README's first line, then overwrite that README after review and index with `qmd update && qmd embed`. It does not bulk-update READMEs.
45
43
 
46
44
  ## Refreshing the catalog
47
45
 
@@ -69,7 +67,7 @@ the latest npm publish and therefore updates on release cadence.
69
67
  docker build -t pi-find-packages-analysis -f docker/Dockerfile.analysis docker
70
68
  ```
71
69
 
72
- The image contains no pi and no credentials: it exists solely to clone/unpack/read third-party source. Never execute a candidate package's install scripts or build artifacts in any environment.
70
+ The image contains no pi and no credentials: it exists solely to fetch, verify, unpack and read third-party source. Runtime must use a read-only root, `--cap-drop=ALL`, `--security-opt=no-new-privileges`, the non-root analyst user, and tmpfs `/analysis` and `/tmp` mounts (`mode=1777`); never bind-mount candidate source from the host. Allow required npm registry/GitHub access. Only analysis text returns to the host. Never execute a candidate package's install scripts or build artifacts in any environment.
73
71
 
74
72
  ## Releasing
75
73
 
@@ -36,7 +36,14 @@ export default function activate(pi: ExtensionAPI) {
36
36
  mkdirSync(dataDir, { recursive: true });
37
37
  if (existsSync(bundled)) {
38
38
  writeFileSync(catalog, gunzipSync(readFileSync(bundled)));
39
- pi.appendEntry("find-packages", { event: "cold-start-extract", from: bundled });
39
+ // pi only accepts registration calls while a factory runs; action methods
40
+ // such as appendEntry throw until pi commits the extension, and a throw here
41
+ // makes pi reject the whole extension (breaking every first run). Pure fs work
42
+ // is fine, so the audit entry is deferred to a session event instead.
43
+ const extracted = { event: "cold-start-extract", from: bundled };
44
+ pi.on("session_start", () => {
45
+ pi.appendEntry("find-packages", extracted);
46
+ });
40
47
  }
41
48
  }
42
49
 
@@ -171,18 +178,14 @@ export default function activate(pi: ExtensionAPI) {
171
178
  `**${query}**`,
172
179
  "",
173
180
  "Steps:",
174
- "1. Search: run multiple jq/grep keyword passes over `" + catalog + "` against description/keywords/name, trying synonyms as needed; "
175
- + (semanticEnabled()
176
- ? "also run `qmd query --collection pi-pkg-readmes` for semantic search over cached READMEs (skip if empty); "
177
- : "")
178
- + "pick the 3-5 most relevant candidates.",
179
- "2. Review each candidate: `npm view <pkg>` for version/deps/peer, plus the README and registry metadata on the host; judge maintenance activity, dependency surface, and supply-chain signals. Any cloning or unpacking of candidate source happens in step 3's sandbox only — never fetch candidate source on the host. "
180
- + "After reviewing, save the package README to `" + join(dataDir, "readmes") + "/<name with / replaced by __>.md` (first line `# <name> <version> <date>`) "
181
- + (semanticEnabled() ? "then refresh the semantic index with `qmd update && qmd embed`" : "") + ".",
181
+ `1. Search: run multiple jq/grep keyword passes over \`${catalog}\` against description/keywords/name, trying synonyms as needed; `
182
+ + (semanticEnabled() ? "also run `qmd query --collection pi-pkg-readmes` for semantic search over cached READMEs (skip if empty); " : "")
183
+ + `for broad jq searches, count matches first, then narrow terms or page results; never shortlist from truncated output. ${semanticEnabled() ? "Treat qmd hits only as leads: verify the current npm version against the cached README's first line, then after review overwrite that README with the verified version/date and run `qmd update && qmd embed`; never bulk-update. " : ""}Pick the 3-5 most relevant candidates.`,
184
+ `2. Review each candidate: npm view <pkg> for version/deps/peer, plus the README and registry metadata on the host; judge maintenance activity, dependency surface, and supply-chain signals. Any cloning or unpacking of candidate source happens in step 3's sandbox only — never fetch candidate source on the host. Save the reviewed README to \`${join(dataDir, "readmes")}/<name with / replaced by __>.md\` (first line \`# <name> <version> <date>\`)${semanticEnabled() ? " and refresh the semantic index with qmd update && qmd embed" : ""}.`,
182
185
  `3. Execution environment: ${iso === "docker"
183
- ? "all cloning/unpacking/source analysis must run inside the Docker container (see " + join(pkgDir, "docker/Dockerfile.analysis") + "); the host only receives analysis text; never run a candidate's install scripts."
186
+ ? "All source analysis must run inside the Docker container with --read-only --cap-drop=ALL --security-opt=no-new-privileges, as the image's non-root analyst user, and tmpfs mounts for /analysis and /tmp with mode=1777 (no host candidate-source bind mount). Allow network access needed for npm registry/GitHub. Fetch, verify, unpack and analyze candidate source only inside the container; only analysis text may reach the host. Never run candidate scripts or build artifacts."
184
187
  : "not isolated — analyze read-only on the host (see the risk note above)."}`,
185
- "4. Criteria: feature overlap with the current setup / installed packages (Unix philosophy: features must not cross); pi compatibility (peer ranges); maintenance activity; dependency and supply-chain safety.",
188
+ "4. Criteria: feature overlap with the current setup / installed packages (Unix philosophy: features must not cross); pi compatibility (check actual pi --version and the candidate peer range; note installed package-directory versus running-version drift rather than treating drift alone as incompatibility); maintenance activity; dependency and supply-chain safety.",
186
189
  "5. Output: a candidate comparison table (name/version/activity/fit/risk) + a clear recommendation with reasons. The analysis report is not install authorization — I decide whether to integrate.",
187
190
  riskNote,
188
191
  ].filter(Boolean).join("\n");
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@nailuogg/pi-find-packages",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Local catalog of the pi package ecosystem with /find-packages: offline search, cold-start data, and sandboxed source analysis for integration review",
5
5
  "type": "module",
6
+ "scripts": {
7
+ "test": "node --test"
8
+ },
6
9
  "keywords": [
7
10
  "pi-package",
8
11
  "pi",
@@ -48,6 +48,8 @@ jq -r 'select((.name + " " + (.description // "") + " " + ((.keywords // [])|joi
48
48
  | [.name, .version, (.publisher // ""), .date] | @tsv' "$CAT"
49
49
  ```
50
50
 
51
+ For broad searches, count matches first, then narrow the terms or page through results. Never choose a shortlist from truncated output.
52
+
51
53
  Expected output — tab-separated, one candidate per line:
52
54
 
53
55
  ```text
@@ -60,7 +62,7 @@ Expected output — tab-separated, one candidate per line:
60
62
  **Semantic search (optional).** Only when `qmd` is installed and `semantic` is not `"off"` — see
61
63
  Configuration. It searches cached READMEs of previously reviewed packages only, so check the collection
62
64
  first and skip semantic search while it is empty — `qmd query` spends an LLM expansion pass per call,
63
- `qmd ls` is free:
65
+ `qmd ls` is free. Treat results only as leads; verify current facts against npm:
64
66
 
65
67
  ```bash
66
68
  qmd ls pi-pkg-readmes # "No files found" → skip
@@ -89,15 +91,30 @@ Run every candidate through all four checks; record the evidence for each one.
89
91
  | Check | How to verify | Verdict |
90
92
  |---|---|---|
91
93
  | **Feature overlap** (hard rule) | Compare against pi built-ins and the packages installed in the `packages` array of `$PI_CODING_AGENT_DIR/settings.json` and of the project `.pi/settings.json` | Any overlap → recommend against, whatever the other checks say |
92
- | **pi compatibility** | `npm view <pkg> peerDependencies` | Peer range must cover the current pi version |
94
+ | **pi compatibility** | Check actual `pi --version` and `npm view <pkg> peerDependencies` | Compare the peer range with the running version. If the installed package-directory copy reports another version, record that drift; do not call it incompatible on drift alone |
93
95
  | **Maintenance activity** | Catalog `date` (last publish) plus the last commit in `repo` | Stale on both → note as risk, not a blocker |
94
96
  | **Supply chain** | `npm view <pkg> maintainers dependencies scripts` | Flag `install`/`preinstall` scripts, large dependency trees, and any path that sends data off-host |
95
97
 
96
98
  ## 4. Analyze sources in the sandbox
97
99
 
98
100
  Source analysis runs in Docker by default, using `../../docker/Dockerfile.analysis` (node24-slim + git +
99
- ripgrep, no credentials). Clone, unpack and read candidate code **inside the container**; only analysis
100
- text reaches the host.
101
+ ripgrep, no credentials). Do not mount candidate source from the host. Run read-only with
102
+ `--cap-drop=ALL --security-opt=no-new-privileges`, as the non-root analyst user, and tmpfs mounts for
103
+ `/analysis` and `/tmp` with `mode=1777`. Allow network access needed for npm registry/GitHub. Fetch,
104
+ verify, unpack and read candidate code **inside the container**; only analysis text reaches the host.
105
+
106
+ Before analysis, replace `<image>` with the built analysis image name and run this preflight to confirm
107
+ non-root execution and writable workdirs:
108
+
109
+ ```bash
110
+ docker run --rm --read-only --cap-drop=ALL --security-opt=no-new-privileges \
111
+ --tmpfs /analysis:rw,mode=1777 --tmpfs /tmp:rw,mode=1777 \
112
+ <image> -c 'test "$(id -u)" -ne 0 && touch /analysis/.probe /tmp/.probe'
113
+ ```
114
+
115
+ This preflight only checks the runtime. Fetching candidate source, verifying its integrity, and
116
+ unpacking it must still happen inside a container with the same security options; never bind-mount
117
+ candidate source from the host.
101
118
 
102
119
  **Never execute a candidate's install scripts or build artifacts** — not on the host and not in the
103
120
  container. Reading `package.json`, the README or registry metadata needs no container, so shallow
@@ -119,9 +136,10 @@ Compare the shortlist in a table, then give one recommendation with reasons:
119
136
 
120
137
  ## README cache
121
138
 
122
- After reviewing a candidate, save its README to
123
- `<catalog dir>/readmes/<name with / replaced by __>.md`, first line `# <name> <version> <date>`, then
124
- refresh the semantic index:
139
+ After reviewing a candidate, verify the current npm version and compare it with the first line of any
140
+ cached README; qmd hits are leads only. Overwrite that candidate's README at
141
+ `<catalog dir>/readmes/<name with / replaced by __>.md`, first line `# <name> <verified version> <date>`,
142
+ then refresh the semantic index. Never bulk-update README files:
125
143
 
126
144
  ```bash
127
145
  qmd update && qmd embed