@signalridge/pi-welcome 1.3.4 → 1.3.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.5
4
+ ### Patch Changes
5
+
6
+ - c1b1741: Durability and containment fixes found in a repository-wide scan.
7
+
8
+ `pi-gpt-fast` rewrote pi's own global `settings.json` with a plain
9
+ `writeFileSync`. That file is shared with pi and every other extension, and this
10
+ one is the only writer of it; a torn write would have left the user with no pi
11
+ configuration at all rather than just no gpt-fast setting. It now writes a temp
12
+ file and renames, matching every other settings writer in the repository.
13
+ `pi-goal` gets the same treatment for its cross-project goal state file, which
14
+ its own settings module already did.
15
+
16
+ `pi-ralph-wiggum` resolved `/ralph start <path>` straight against the session
17
+ cwd, so `../../notes.md` would create directories and a file outside the project
18
+ the user opened — silently, before the loop started. This extension drives long
19
+ unattended loops, so that command is as likely to come from a model as from a
20
+ person. Task-file paths are now required to stay inside the workspace, on the
21
+ command, on the `ralph_start` tool, and on the paths read back out of persisted
22
+ state, so a state file written earlier or edited by hand cannot pull a file in
23
+ from outside either.
24
+
25
+ The containment test canonicalizes both sides before comparing, because a purely
26
+ lexical one is defeated by a symlink: every segment of `linked/plan.md` reads as
27
+ inside the workspace when `linked` is a door out of it. `realpathSync` throws on
28
+ a path that does not exist yet — the ordinary case, since `start` is usually
29
+ creating the file — so it resolves the deepest existing ancestor and re-attaches
30
+ the not-yet-created suffix, which cannot itself be a link. The traversal test
31
+ matches a `..` segment rather than a `..` prefix; the prefix form also rejected
32
+ `..notes.md`, an ordinary filename sitting in the workspace.
33
+
34
+ Every remaining bare `catch {}` now says why the error is safe to drop, matching
35
+ the convention the rest of the repository already follows.
36
+
3
37
  ## 1.3.4
4
38
  ### Patch Changes
5
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalridge/pi-welcome",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "description": "Startup resource and repository summary card for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -221,14 +221,19 @@ function collectExtensionNames(cwd: string): string[] {
221
221
  const bare = (spec.startsWith("@") ? (spec.split("/")[1] ?? spec) : basename(spec)).replace(/@.*$/u, "");
222
222
  names.push(bare.replace(/^pi-/u, ""));
223
223
  }
224
- } catch {}
224
+ } catch {
225
+ // Discovery is best-effort: a malformed manifest contributes no names
226
+ // rather than blanking the whole card.
227
+ }
225
228
  }
226
229
  for (const directory of [join(getAgentDir(), "extensions"), join(cwd, ".pi", "extensions")]) {
227
230
  try {
228
231
  for (const entry of readdirSync(directory)) {
229
232
  names.push(entry.replace(/\.[cm]?[jt]s$/u, "").replace(/^pi-/u, ""));
230
233
  }
231
- } catch {}
234
+ } catch {
235
+ // An extensions directory that does not exist is the normal case.
236
+ }
232
237
  }
233
238
  return tidy(names);
234
239
  }
@@ -271,7 +276,9 @@ export function collectResources(pi: ExtensionAPI, cwd: string): [string, string
271
276
  // Several scopes can contribute the same filename (a project AGENTS.md and
272
277
  // a global one); the card names kinds of context, not paths.
273
278
  push("Context", nameList(tidy(files.map((file) => basename(file.path)))));
274
- } catch {}
279
+ } catch {
280
+ // One unreadable source drops its row; the card still renders the rest.
281
+ }
275
282
 
276
283
  try {
277
284
  const commands = pi.getCommands?.() ?? [];
@@ -279,7 +286,9 @@ export function collectResources(pi: ExtensionAPI, cwd: string): [string, string
279
286
  tidy(commands.filter((command) => command.source === source).map((command) => `${prefix}${command.name}`));
280
287
  push("Skills", nameList(named("skill")));
281
288
  push("Prompts", nameList(named("prompt", "/")));
282
- } catch {}
289
+ } catch {
290
+ // A host without these accessors simply contributes no rows.
291
+ }
283
292
 
284
293
  push("Extensions", nameList(collectExtensionNames(cwd)));
285
294
  push("Themes", nameList(collectThemeNames()));
@@ -288,7 +297,9 @@ export function collectResources(pi: ExtensionAPI, cwd: string): [string, string
288
297
  const all = pi.getAllTools?.() ?? [];
289
298
  const active = pi.getActiveTools?.() ?? [];
290
299
  if (all.length) push("Tools", `${active.length} active of ${all.length}`);
291
- } catch {}
300
+ } catch {
301
+ // A host without these accessors simply contributes no rows.
302
+ }
292
303
 
293
304
  return rows;
294
305
  }
@@ -501,7 +512,10 @@ export default function (pi: ExtensionAPI) {
501
512
  return;
502
513
  }
503
514
  }
504
- } catch {}
515
+ } catch {
516
+ // Unreadable entries mean we cannot prove the card was already written.
517
+ // Falling through re-appends it, which beats never showing it at all.
518
+ }
505
519
 
506
520
  try {
507
521
  pi.appendEntry<WelcomeData>(ENTRY_TYPE, collect(pi, ctx));