@juicesharp/rpiv-args 1.13.0 → 1.14.0

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.
Files changed (2) hide show
  1. package/args.ts +38 -68
  2. package/package.json +1 -1
package/args.ts CHANGED
@@ -27,9 +27,8 @@
27
27
  * template literal below.
28
28
  */
29
29
 
30
- import { existsSync, readFileSync } from "node:fs";
31
- import { homedir } from "node:os";
32
- import { dirname, join, resolve } from "node:path";
30
+ import { readFileSync } from "node:fs";
31
+ import { dirname } from "node:path";
33
32
  import {
34
33
  type BeforeAgentStartEvent,
35
34
  type BeforeAgentStartEventResult,
@@ -39,12 +38,9 @@ import {
39
38
  type ExtensionAPI,
40
39
  type ExtensionContext,
41
40
  formatSize,
42
- getAgentDir,
43
41
  type InputEvent,
44
42
  type InputEventResult,
45
- loadSkills,
46
43
  parseFrontmatter,
47
- type Skill,
48
44
  stripFrontmatter,
49
45
  type TruncationResult,
50
46
  truncateTail,
@@ -315,72 +311,41 @@ export function invalidateSkillIndex(): void {
315
311
  skillIndex = null;
316
312
  }
317
313
 
318
- function findGitRepoRoot(startDir: string): string | null {
319
- let dir = resolve(startDir);
320
- while (true) {
321
- if (existsSync(join(dir, ".git"))) return dir;
322
- const parent = dirname(dir);
323
- if (parent === dir) return null;
324
- dir = parent;
325
- }
326
- }
327
-
328
- function collectAncestorAgentsSkillDirs(startDir: string): string[] {
329
- const skillDirs: string[] = [];
330
- const gitRepoRoot = findGitRepoRoot(startDir);
331
- let dir = resolve(startDir);
332
- while (true) {
333
- skillDirs.push(join(dir, ".agents", "skills"));
334
- if (gitRepoRoot && dir === gitRepoRoot) break;
335
- const parent = dirname(dir);
336
- if (parent === dir) break;
337
- dir = parent;
338
- }
339
- return skillDirs;
340
- }
341
-
342
- function addExistingPath(paths: string[], seen: Set<string>, path: string): void {
343
- const resolved = resolve(path);
344
- if (!existsSync(resolved) || seen.has(resolved)) return;
345
- seen.add(resolved);
346
- paths.push(resolved);
347
- }
348
-
349
- /** Collect Pi's default skill locations in collision-precedence order. */
350
- export function collectDefaultSkillPaths(cwd: string, agentDir: string): string[] {
351
- const paths: string[] = [];
352
- const seen = new Set<string>();
353
- const userAgentsSkillsDir = join(homedir(), ".agents", "skills");
354
-
355
- addExistingPath(paths, seen, join(resolve(cwd), ".pi", "skills"));
356
- for (const dir of collectAncestorAgentsSkillDirs(cwd)) {
357
- if (resolve(dir) !== resolve(userAgentsSkillsDir)) addExistingPath(paths, seen, dir);
358
- }
359
- addExistingPath(paths, seen, join(agentDir, "skills"));
360
- addExistingPath(paths, seen, userAgentsSkillsDir);
361
-
362
- return paths;
363
- }
364
-
365
- /** Build the name→path index by asking Pi for its default skill locations. */
366
- function buildSkillIndex(): Map<string, SkillIndexEntry> {
367
- const cwd = process.cwd();
368
- const agentDir = getAgentDir();
369
- const { skills } = loadSkills({
370
- cwd,
371
- agentDir,
372
- skillPaths: collectDefaultSkillPaths(cwd, agentDir),
373
- includeDefaults: false,
374
- });
314
+ /**
315
+ * Build the name→path index from Pi's command registry. `pi.getCommands()`
316
+ * returns every slash command the agent can see — including skills sourced
317
+ * from extension package manifests (`pi.skills: [...]`), not just the
318
+ * filesystem-walked defaults. This is the authoritative source: whatever Pi
319
+ * knows about as `/skill:<name>`, this index recognizes.
320
+ *
321
+ * Critically, this fixes programmatic `sendUserMessage("/skill:<name> …")`
322
+ * from other extensions (e.g. the `/rpiv` workflow runner). Those calls go
323
+ * through `prompt({expandPromptTemplates: false})`, so Pi's built-in
324
+ * `_expandSkillCommand` is skipped — `rpiv-args` is the only expander on that
325
+ * path. If the index doesn't recognize the skill, the raw `/skill:<name> …`
326
+ * reaches the LLM unwrapped.
327
+ */
328
+ function buildSkillIndex(pi: ExtensionAPI): Map<string, SkillIndexEntry> {
375
329
  const index = new Map<string, SkillIndexEntry>();
376
- for (const s of skills as Skill[]) {
377
- index.set(s.name, { name: s.name, filePath: s.filePath, baseDir: s.baseDir });
330
+ for (const cmd of pi.getCommands()) {
331
+ if (cmd.source !== "skill") continue;
332
+ // Pi prefixes skill-source commands with "skill:" (agent-session.js:1699).
333
+ const name = cmd.name.startsWith("skill:") ? cmd.name.slice("skill:".length) : cmd.name;
334
+ const filePath = cmd.sourceInfo.path;
335
+ // `cmd.sourceInfo.baseDir` cannot be used here: for skills sourced from
336
+ // an extension manifest (`pi.skills: [...]`), resource-loader.js:358-362
337
+ // overrides `skill.sourceInfo` with the *extension package's* baseDir
338
+ // (e.g. `packages/rpiv-pi`), not the skill folder. Pi's own internal
339
+ // `Skill.baseDir` is set to `dirname(filePath)` at skills.js:214,237 —
340
+ // which is what `${SKILL_DIR}` substitutions in skill bodies expect.
341
+ const baseDir = dirname(filePath);
342
+ index.set(name, { name, filePath, baseDir });
378
343
  }
379
344
  return index;
380
345
  }
381
346
 
382
- function getSkillIndex(): Map<string, SkillIndexEntry> {
383
- if (!skillIndex) skillIndex = buildSkillIndex();
347
+ function getSkillIndex(pi: ExtensionAPI): Map<string, SkillIndexEntry> {
348
+ if (!skillIndex) skillIndex = buildSkillIndex(pi);
384
349
  return skillIndex;
385
350
  }
386
351
 
@@ -424,7 +389,7 @@ export async function handleInput(
424
389
  const skillName = spaceIndex === -1 ? text.slice(SKILL_PREFIX.length) : text.slice(SKILL_PREFIX.length, spaceIndex);
425
390
  const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
426
391
 
427
- const entry = getSkillIndex().get(skillName);
392
+ const entry = getSkillIndex(pi).get(skillName);
428
393
  if (!entry) return { action: "continue" }; // unknown skill — let Pi handle it
429
394
 
430
395
  let content: string;
@@ -484,6 +449,11 @@ export function registerArgsHandler(pi: ExtensionAPI): void {
484
449
  pi.on("input", async (event, ctx) => handleInput(event, ctx, pi));
485
450
  pi.on("before_agent_start", (event) => handleBeforeAgentStart(event));
486
451
  pi.on("session_start", (event) => {
452
+ // Pi fires session_start for every session including programmatic
453
+ // spawns. Re-enumerating the skill set per spawn is cheap (one
454
+ // readdir per declared skillPath); the prior workflow-aware gate
455
+ // shaved that work but coupled this package to rpiv-workflow's
456
+ // internal Symbol. Drop the gate — correctness > microseconds.
487
457
  if (event.reason === "reload" || event.reason === "startup") {
488
458
  invalidateSkillIndex();
489
459
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juicesharp/rpiv-args",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Pi extension. Shell-style $1 / $ARGUMENTS placeholders and !`cmd` / ```! shell substitution, expanded into your Pi skills at invocation.",
5
5
  "keywords": [
6
6
  "pi-package",