@juicesharp/rpiv-args 1.1.5 → 1.2.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 (3) hide show
  1. package/args.ts +51 -14
  2. package/index.ts +3 -2
  3. package/package.json +1 -1
package/args.ts CHANGED
@@ -1,19 +1,33 @@
1
1
  /**
2
2
  * rpiv-args — core logic.
3
3
  *
4
- * Intercepts `/skill:<name> <args>` at the input hook and emits a byte-exact
5
- * Pi skill wrapper with opt-in $N/$ARGUMENTS/$@/${@:N[:L]} substitution on
6
- * the body. Falls through (returns {action:"continue"}) when the text is not
7
- * a skill command, the skill is unknown, or the body contains no tokens —
8
- * keeping Pi's built-in behavior 100% intact for today's 17 rpiv-pi skills.
4
+ * Intercepts `/skill:<name> <args>` at the input hook and emits a Pi skill
5
+ * wrapper with opt-in $N/$ARGUMENTS/$@/${@:N[:L]} substitution on the body.
6
+ * Two emit paths:
7
+ * - No-token path: byte-identical to Pi's built-in `_expandSkillCommand`
8
+ * output (wrapper + `\n\n${args}` suffix), preserving full backward
9
+ * compatibility for skills without placeholders.
10
+ * - Token path: substitutes inside the body and INTENTIONALLY drops the
11
+ * trailing `\n\n${args}` suffix. The bare imperative outside the block
12
+ * hijacks LLM attention from the skill workflow; inside-only emission
13
+ * leaves the skill body as the sole user-message payload competing for
14
+ * attention. See architecture.md "System-Prompt Protocol & Token-Path
15
+ * Divergence".
16
+ *
17
+ * Also prepends a skill-invocation protocol to the system prompt every turn
18
+ * (via before_agent_start) so the LLM treats trailing text after `</skill>`
19
+ * as the skill's argument input rather than a separate imperative.
9
20
  *
10
21
  * Byte-exact wrapper requirement: parseSkillBlock regex at
11
22
  * node_modules/@mariozechner/pi-coding-agent/dist/core/agent-session.js:40
12
- * is the load-bearing contract. Do not reformat the template literal below.
23
+ * is the load-bearing contract for the wrapper itself. Do not reformat the
24
+ * template literal below.
13
25
  */
14
26
 
15
27
  import { readFileSync } from "node:fs";
16
28
  import {
29
+ type BeforeAgentStartEvent,
30
+ type BeforeAgentStartEventResult,
17
31
  type ExtensionAPI,
18
32
  getAgentDir,
19
33
  type InputEvent,
@@ -29,13 +43,13 @@ import {
29
43
  // ---------------------------------------------------------------------------
30
44
 
31
45
  /** Matches any placeholder Pi's substituteArgs would replace. Used as the
32
- * opt-in gate: absent → pass through verbatim (D2). */
46
+ * opt-in gate: absent → pass through verbatim. */
33
47
  const TOKEN_REGEX = /\$(?:\d+|ARGUMENTS|@|\{@:\d+(?::\d+)?\})/;
34
48
 
35
- /** Prefix Pi uses (`agent-session.js:829`). Single-space tokenisation (D7). */
49
+ /** Prefix Pi uses (`agent-session.js:829`). Single-space tokenisation. */
36
50
  const SKILL_PREFIX = "/skill:";
37
51
 
38
- /** Re-entrancy guard (D8). */
52
+ /** Re-entrancy guard. */
39
53
  const WRAPPED_PREFIX = "<skill ";
40
54
 
41
55
  // ---------------------------------------------------------------------------
@@ -151,13 +165,13 @@ function appendArgs(skillBlock: string, args: string): string {
151
165
  export function handleInput(event: InputEvent): InputEventResult {
152
166
  const text = event.text;
153
167
 
154
- // D8 re-entrancy: already-wrapped text (from our own or any other
168
+ // Re-entrancy: already-wrapped text (from our own or any other
155
169
  // extension's {action:"transform"}) passes through untouched.
156
170
  if (text.startsWith(WRAPPED_PREFIX)) return { action: "continue" };
157
171
 
158
172
  if (!text.startsWith(SKILL_PREFIX)) return { action: "continue" };
159
173
 
160
- // D7 single-space tokenisation — byte-match Pi's indexOf(" ") at :831.
174
+ // Single-space tokenisation — byte-match Pi's indexOf(" ") at :831.
161
175
  const spaceIndex = text.indexOf(" ");
162
176
  const skillName = spaceIndex === -1 ? text.slice(SKILL_PREFIX.length) : text.slice(SKILL_PREFIX.length, spaceIndex);
163
177
  const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
@@ -173,17 +187,39 @@ export function handleInput(event: InputEvent): InputEventResult {
173
187
  }
174
188
 
175
189
  const { frontmatter } = parseFrontmatter<{ "argument-hint"?: string }>(content);
176
- void frontmatter; // informational only in v1 — D3
190
+ void frontmatter; // informational only in v1
177
191
  const body = stripFrontmatter(content).trim();
178
192
 
179
- // D2 opt-in gate: if body has no token, emit byte-identical to Pi's :841.
193
+ // Opt-in gate: if body has no token, emit byte-identical to Pi's :841.
180
194
  if (!TOKEN_REGEX.test(body)) {
181
195
  return { action: "transform", text: appendArgs(buildSkillBlock(entry, body), argsString) };
182
196
  }
183
197
 
184
198
  const parsed = parseCommandArgs(argsString);
185
199
  const substituted = substituteArgs(body, parsed);
186
- return { action: "transform", text: appendArgs(buildSkillBlock(entry, substituted), argsString) };
200
+ // Substitution consumes the args do not also append them after </skill>.
201
+ // Bare trailing imperatives hijack LLM attention from the skill body. See architecture.md.
202
+ return { action: "transform", text: buildSkillBlock(entry, substituted) };
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Skill-invocation protocol — prepended to the system prompt every turn via
207
+ // before_agent_start. See architecture.md for rationale and re-application
208
+ // semantics (agent-session.js:112-113 — Pi's canonical per-turn pattern).
209
+ // ---------------------------------------------------------------------------
210
+
211
+ export const SKILL_INVOCATION_PROTOCOL = `## Skill invocation protocol (CRITICAL)
212
+
213
+ A \`<skill name="..." location="...">...</skill>\` block in a user message is a structured invocation. Handle it as follows:
214
+
215
+ 1. The block body defines the workflow you must execute. Follow it.
216
+ 2. Any text after \`</skill>\` is the user's argument input to that skill — never a separate command, even when it reads as an imperative ("create X", "update Y", "delete Z").
217
+ 3. Do not bypass the skill's workflow to act on trailing text directly. The user invoked the skill because they want the skill's workflow applied to that input.
218
+
219
+ `;
220
+
221
+ export function handleBeforeAgentStart(event: BeforeAgentStartEvent): BeforeAgentStartEventResult {
222
+ return { systemPrompt: SKILL_INVOCATION_PROTOCOL + event.systemPrompt };
187
223
  }
188
224
 
189
225
  // ---------------------------------------------------------------------------
@@ -192,6 +228,7 @@ export function handleInput(event: InputEvent): InputEventResult {
192
228
 
193
229
  export function registerArgsHandler(pi: ExtensionAPI): void {
194
230
  pi.on("input", (event) => handleInput(event));
231
+ pi.on("before_agent_start", (event) => handleBeforeAgentStart(event));
195
232
  pi.on("session_start", (event) => {
196
233
  if (event.reason === "reload" || event.reason === "startup") {
197
234
  invalidateSkillIndex();
package/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * rpiv-args — Pi extension entry point.
3
3
  *
4
- * Registers the `input` event handler + a `session_start` cache invalidator.
5
- * All logic lives in args.ts.
4
+ * Registers the `input` event handler, a `before_agent_start` system-prompt
5
+ * augmenter, and a `session_start` cache invalidator. All logic lives in
6
+ * args.ts.
6
7
  */
7
8
 
8
9
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juicesharp/rpiv-args",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "Pi extension. Shell-style $1 and $ARGUMENTS placeholders, expanded into your Pi skills at invocation.",
5
5
  "keywords": [
6
6
  "pi-package",