@h-rig/product-ad-website 0.0.6-alpha.283

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 (38) hide show
  1. package/README.md +3 -0
  2. package/package.json +52 -0
  3. package/site/assets/favicon.svg +1 -0
  4. package/site/assets/og-card.png +0 -0
  5. package/site/assets/rig.css +199 -0
  6. package/site/assets/rig.js +66 -0
  7. package/site/assets/search-index.json +1 -0
  8. package/site/assets/site.css +46 -0
  9. package/site/assets/site.js +510 -0
  10. package/site/cli/index.html +88 -0
  11. package/site/config/index.html +256 -0
  12. package/site/docs/agent-tools/index.html +129 -0
  13. package/site/docs/architecture/index.html +42 -0
  14. package/site/docs/capabilities/index.html +84 -0
  15. package/site/docs/entities/index.html +68 -0
  16. package/site/docs/environment/index.html +62 -0
  17. package/site/docs/faq/index.html +17 -0
  18. package/site/docs/fleet-irc/index.html +44 -0
  19. package/site/docs/gate/index.html +128 -0
  20. package/site/docs/getting-started/index.html +50 -0
  21. package/site/docs/glossary/index.html +31 -0
  22. package/site/docs/index.html +68 -0
  23. package/site/docs/operator/index.html +49 -0
  24. package/site/docs/packages/index.html +36 -0
  25. package/site/docs/pi-extensions/index.html +15 -0
  26. package/site/docs/recipes/index.html +28 -0
  27. package/site/docs/runs/index.html +35 -0
  28. package/site/docs/security/index.html +29 -0
  29. package/site/docs/server/index.html +27 -0
  30. package/site/docs/swarm-commander/index.html +41 -0
  31. package/site/docs/task-sources/index.html +129 -0
  32. package/site/docs/troubleshooting/index.html +23 -0
  33. package/site/docs/validators/index.html +177 -0
  34. package/site/index.html +1653 -0
  35. package/site/plugins/examples/index.html +419 -0
  36. package/site/plugins/index.html +193 -0
  37. package/site/skills/index.html +140 -0
  38. package/site/variants/deck/index.html +1 -0
@@ -0,0 +1,419 @@
1
+ <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Plugin examples // RIG</title><meta property="og:image" content="https://how.rig-does.work/assets/og-card.png"><meta name="twitter:card" content="summary_large_image"><meta name="description" content="Three complete Rig extension packages: a validator, a task-source adapter, and an OMP/Pi session package pinned through runtime.pi.packages."><meta property="og:title" content="Plugin examples // RIG"><meta property="og:description" content="Copyable validator, task-source, and OMP/Pi package examples with wiring, verification, and source seams."><link rel="stylesheet" href="../../assets/rig.css?v=v9"><link rel="stylesheet" href="../../assets/site.css?v=v9"><link rel="icon" type="image/svg+xml" href="../../assets/favicon.svg"></head><body data-doc-theme="pluginlab"><canvas id="docfield"></canvas><nav class="nav"><div class="nav-in"><a class="brand" href="../../index.html"><span class="mk"></span><span class="blk">Rig</span></a><button class="burger" onclick="document.getElementById('nl').classList.toggle('open')">menu</button><div class="nav-links" id="nl"><button class="nsearch" type="button" onclick="rigSearchOpen()">search<kbd>&#8984;K</kbd></button><a href="../../index.html" class="">overview</a><a href="../../docs/index.html" class="">docs</a><a href="../../plugins/index.html" class="active">plugins</a><a href="../../skills/index.html" class="">skills</a><a href="../../cli/index.html" class="">cli</a><a href="../../config/index.html" class="">config</a><a href="https://github.com/humanity-org/rig" class="ext">github</a></div></div></nav><div class="docwrap"><aside class="sidebar"><div class="grp">START</div><a href="../../docs/index.html" class="">docs home</a><a href="../../docs/capabilities/index.html" class="">capabilities</a><a href="../../docs/getting-started/index.html" class="">getting started</a><div class="grp">RUN</div><a href="../../docs/runs/index.html" class="">the run lifecycle</a><a href="../../docs/operator/index.html" class="">operator guide</a><a href="../../docs/swarm-commander/index.html" class="">swarm commander</a><a href="../../docs/fleet-irc/index.html" class="">drone messages</a><a href="../../docs/agent-tools/index.html" class="">agent tools</a><a href="../../docs/gate/index.html" class="">the merge gate</a><a href="../../docs/troubleshooting/index.html" class="">troubleshooting</a><div class="grp">ARCHITECTURE</div><a href="../../docs/architecture/index.html" class="">how rig works</a><a href="../../docs/entities/index.html" class="">entities & data model</a><a href="../../docs/server/index.html" class="">OMP relay/web</a><a href="../../docs/security/index.html" class="">security & trust</a><a href="../../docs/environment/index.html" class="">environment variables</a><div class="grp">EXTEND</div><a href="../../plugins/index.html" class="">plugin authoring</a><a href="../../plugins/examples/index.html" class="active">plugin examples</a><a href="../../docs/pi-extensions/index.html" class="">OMP/Pi extensions</a><a href="../../skills/index.html" class="">skills</a><a href="../../docs/task-sources/index.html" class="">task sources</a><a href="../../docs/validators/index.html" class="">validators & hooks</a><div class="grp">OPERATE</div><a href="../../cli/index.html" class="">cli reference</a><a href="../../config/index.html" class="">rig.config.ts</a><a href="../../docs/recipes/index.html" class="">recipes</a><a href="../../docs/glossary/index.html" class="">glossary</a><a href="../../docs/faq/index.html" class="">faq</a><div class="grp">ENGINE</div><a href="../../docs/packages/index.html" class="">packages</a></aside><main class="doc"><h1>Plugin examples</h1>
2
+ <p class="lede">Three copyable packages. Each one shows the real load path: executable Rig contributions ride in <code>.rig/rig.config.ts</code>; live OMP/Pi packages are pinned as package data through <code>runtime.pi.packages</code> and materialized into <code>.pi/settings.json</code>.</p>
3
+
4
+ <h2 id="map">Sortie map</h2>
5
+ <table class="t"><thead><tr><th>package</th><th>what it proves</th><th>where it enters Rig</th></tr></thead><tbody>
6
+ <tr><td><a href="#validator-plugin">validator plugin</a></td><td>A custom check is dispatched by id inside the validation gate.</td><td><code>plugins: [scopeGuard()]</code> in <code>.rig/rig.config.ts</code>; task records name the validator id.</td></tr>
7
+ <tr><td><a href="#task-source-plugin">task-source plugin</a></td><td>A custom <code>taskSource.kind</code> resolves through the same factory path as standard adapters.</td><td><code>plugins: [fileQueue()]</code> plus <code>taskSource: { kind, options }</code>.</td></tr>
8
+ <tr><td><a href="#pi-extension-package">OMP/Pi package</a></td><td>A live-session tool is installed by OMP/Pi, not by the Rig plugin registry.</td><td><code>runtime.pi.packages</code> in <code>rig.config.ts</code> or <code>.rig/rigfig.toml</code>.</td></tr>
9
+ </tbody></table>
10
+
11
+ <h2 id="validator-plugin">Validator plugin <span class="ns">// validation gate check</span></h2>
12
+ <p>Use this when the merge sortie needs a local rule that is not a subprocess: scope fences, release-note policy, owned-file checks. The validator contributes metadata and an executable <code>run(ctx)</code> on the same object; the lifecycle validator dispatches by check id.</p>
13
+
14
+ <h3 id="validator-files">Files</h3>
15
+ <p><code>packages/rig-scope-guard/package.json</code></p>
16
+ <pre class="code">{
17
+ "name": "@acme/rig-scope-guard",
18
+ "version": "0.1.0",
19
+ "type": "module",
20
+ "exports": { ".": "./src/index.ts" },
21
+ "dependencies": {
22
+ "@rig/core": "workspace:*"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.8.0"
26
+ }
27
+ }</pre>
28
+
29
+ <p><code>packages/rig-scope-guard/src/index.ts</code></p>
30
+ <pre class="code">import { definePlugin, type ValidatorContext, type ValidatorResult } from "@rig/core";
31
+
32
+ const VALIDATOR_ID = "acme-scope-guard:scope-boundary";
33
+
34
+ export type ScopeGuardOptions = {
35
+ forbidden?: readonly string[];
36
+ };
37
+
38
+ function blockedFiles(scope: readonly string[], forbidden: readonly string[]): string[] {
39
+ return scope.filter((file) =&gt; forbidden.some((prefix) =&gt; file === prefix || file.startsWith(`${prefix}/`)));
40
+ }
41
+
42
+ export default function scopeGuardPlugin(options: ScopeGuardOptions = {}) {
43
+ const forbidden = options.forbidden ?? ["secrets", "infra/prod"];
44
+
45
+ return definePlugin({
46
+ name: "acme-scope-guard",
47
+ version: "0.1.0",
48
+ contributes: {
49
+ validators: [
50
+ {
51
+ id: VALIDATOR_ID,
52
+ category: "boundary",
53
+ description: "Fails when task scope crosses forbidden directories.",
54
+ async run(ctx: ValidatorContext): Promise&lt;ValidatorResult&gt; {
55
+ const blocked = blockedFiles(ctx.scope, forbidden);
56
+ if (blocked.length === 0) {
57
+ return { id: VALIDATOR_ID, passed: true, summary: "scope stays inside the allowed fence" };
58
+ }
59
+ return {
60
+ id: VALIDATOR_ID,
61
+ passed: false,
62
+ summary: `scope crosses ${blocked.length} forbidden path(s)`,
63
+ details: blocked.map((file) =&gt; `blocked: ${file}`).join("\n"),
64
+ };
65
+ },
66
+ },
67
+ ],
68
+ },
69
+ });
70
+ }</pre>
71
+
72
+ <p><code>.rig/rig.config.ts</code></p>
73
+ <pre class="code">import { defineConfig } from "@rig/core/config";
74
+ import scopeGuard from "@acme/rig-scope-guard";
75
+
76
+ export default defineConfig({
77
+ project: { name: "ground-control" },
78
+ workspace: { mainRepo: "." },
79
+ plugins: [scopeGuard({ forbidden: ["secrets", "infra/prod"] })],
80
+ taskSource: { kind: "github-issues", owner: "acme", repo: "ground-control" },
81
+ });</pre>
82
+
83
+ <p>Task-source records opt into the gate by id. A GitHub issue body, Linear adapter projection, or custom source record can carry:</p>
84
+ <pre class="code">{
85
+ "id": "GC-417",
86
+ "status": "ready",
87
+ "deps": [],
88
+ "scope": ["src/control-plane/dispatch.ts"],
89
+ "validation": ["acme-scope-guard:scope-boundary"]
90
+ }</pre>
91
+
92
+ <h3 id="validator-verify">Verify the load</h3>
93
+ <ul>
94
+ <li><code>rig plugin list</code> should show <code>acme-scope-guard</code> and validator <code>acme-scope-guard:scope-boundary</code>.</li>
95
+ <li><code>rig plugin validate --task GC-417</code> or the run closeout validator should dispatch the same id. A task scoped to <code>secrets/api.env</code> fails with the validator summary.</li>
96
+ <li>Unit-test the function directly: construct the plugin, take <code>plugin.contributes.validators[0]</code>, and call <code>run({ taskId, workspaceRoot, scope })</code>. There is no second runtime channel to mock.</li>
97
+ </ul>
98
+
99
+ <h3 id="validator-seams">How it works</h3>
100
+ <ul>
101
+ <li><code>definePlugin(plugin)</code> decodes metadata and returns the original object, so executable functions remain attached.</li>
102
+ <li><code>ValidatorRegistration</code> is <code>{ id, category, description? }</code>; <code>@rig/core</code> extends it with <code>run(ctx)</code> for executable validators.</li>
103
+ <li>The plugin host lists executable validators, builds the validator registry for the harness, and lifecycle validation dispatches configured check ids through that registry before falling back to subprocess checks.</li>
104
+ </ul>
105
+ <div class="note"><b>Limit:</b> validators only run when a task or closeout path asks for their id. Registering a validator is not a global policy switch.</div>
106
+
107
+ <h2 id="task-source-plugin">Task-source plugin <span class="ns">// custom kind, same factory path</span></h2>
108
+ <p>Use this when ground control has a task system Rig does not ship: a JSON queue, an internal tracker, a database-backed board. The important seam is <code>taskSource.kind</code>. Standard and custom adapters both resolve by <code>kind</code> through the plugin host, then receive the full <code>TaskSourceConfig</code> and <code>{ projectRoot }</code>.</p>
109
+
110
+ <h3 id="task-source-files">Files</h3>
111
+ <p><code>packages/rig-file-queue-source/package.json</code></p>
112
+ <pre class="code">{
113
+ "name": "@acme/rig-file-queue-source",
114
+ "version": "0.1.0",
115
+ "type": "module",
116
+ "exports": { ".": "./src/index.ts" },
117
+ "dependencies": {
118
+ "@rig/core": "workspace:*",
119
+ "@rig/contracts": "workspace:*"
120
+ },
121
+ "devDependencies": {
122
+ "typescript": "^5.8.0"
123
+ }
124
+ }</pre>
125
+
126
+ <p><code>packages/rig-file-queue-source/src/index.ts</code></p>
127
+ <pre class="code">import { existsSync, readFileSync } from "node:fs";
128
+ import { resolve } from "node:path";
129
+ import { definePlugin, type TaskSourceFactoryContext } from "@rig/core";
130
+ import type { RegisteredTaskSource, TaskRecord, TaskSourceConfig } from "@rig/contracts";
131
+
132
+ const PLUGIN_NAME = "acme-file-queue";
133
+ const SOURCE_ID = "acme-file-queue:source";
134
+ const SOURCE_KIND = "acme-file-queue";
135
+
136
+ type QueueTask = {
137
+ id: string;
138
+ title: string;
139
+ status?: string;
140
+ body?: string;
141
+ deps?: readonly string[];
142
+ scope?: readonly string[];
143
+ validation?: readonly string[];
144
+ };
145
+
146
+ function readQueue(path: string): QueueTask[] {
147
+ if (!existsSync(path)) return [];
148
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
149
+ if (!Array.isArray(parsed)) throw new Error(`${path} must contain a JSON array`);
150
+ return parsed.map((entry) =&gt; {
151
+ if (!entry || typeof entry !== "object") throw new Error("task entry must be an object");
152
+ const task = entry as Partial&lt;QueueTask&gt;;
153
+ if (typeof task.id !== "string" || typeof task.title !== "string") {
154
+ throw new Error("task entry requires string id and title");
155
+ }
156
+ return {
157
+ id: task.id,
158
+ title: task.title,
159
+ status: typeof task.status === "string" ? task.status : "ready",
160
+ body: typeof task.body === "string" ? task.body : "",
161
+ deps: Array.isArray(task.deps) ? task.deps.filter((value): value is string =&gt; typeof value === "string") : [],
162
+ scope: Array.isArray(task.scope) ? task.scope.filter((value): value is string =&gt; typeof value === "string") : [],
163
+ validation: Array.isArray(task.validation) ? task.validation.filter((value): value is string =&gt; typeof value === "string") : [],
164
+ };
165
+ });
166
+ }
167
+
168
+ function toRecord(task: QueueTask): TaskRecord {
169
+ return {
170
+ id: task.id,
171
+ title: task.title,
172
+ status: task.status ?? "ready",
173
+ body: task.body ?? "",
174
+ deps: task.deps ?? [],
175
+ scope: task.scope ?? [],
176
+ validation: task.validation ?? [],
177
+ source: SOURCE_KIND,
178
+ };
179
+ }
180
+
181
+ function createSource(config: TaskSourceConfig, context?: TaskSourceFactoryContext): RegisteredTaskSource {
182
+ const options = config.options ?? {};
183
+ const queuePath = typeof options.path === "string" ? options.path : ".rig/tasks.json";
184
+ const projectRoot = context?.projectRoot ?? process.cwd();
185
+ const absolutePath = resolve(projectRoot, queuePath);
186
+
187
+ return {
188
+ id: SOURCE_ID,
189
+ kind: SOURCE_KIND,
190
+ description: "JSON-backed local task queue.",
191
+ async list() {
192
+ return readQueue(absolutePath).map(toRecord);
193
+ },
194
+ async get(id: string) {
195
+ return (await this.list()).find((task) =&gt; task.id === id);
196
+ },
197
+ };
198
+ }
199
+
200
+ export default function fileQueuePlugin() {
201
+ return definePlugin({
202
+ name: PLUGIN_NAME,
203
+ version: "0.1.0",
204
+ contributes: {
205
+ taskSources: [
206
+ {
207
+ id: SOURCE_ID,
208
+ kind: SOURCE_KIND,
209
+ description: "Reads tasks from a JSON queue file.",
210
+ factory: createSource,
211
+ },
212
+ ],
213
+ },
214
+ });
215
+ }</pre>
216
+
217
+ <p><code>.rig/tasks.json</code></p>
218
+ <pre class="code">[
219
+ {
220
+ "id": "LOCAL-1",
221
+ "title": "Tighten the fiber-line reconnect path",
222
+ "status": "ready",
223
+ "deps": [],
224
+ "scope": ["packages/run-plugin/src/worker"],
225
+ "validation": ["std:typecheck"]
226
+ }
227
+ ]</pre>
228
+
229
+ <p><code>.rig/rig.config.ts</code></p>
230
+ <pre class="code">import { defineConfig } from "@rig/core/config";
231
+ import fileQueue from "@acme/rig-file-queue-source";
232
+
233
+ export default defineConfig({
234
+ project: { name: "ground-control" },
235
+ workspace: { mainRepo: "." },
236
+ plugins: [fileQueue()],
237
+ taskSource: {
238
+ kind: "acme-file-queue",
239
+ options: { path: ".rig/tasks.json" },
240
+ },
241
+ });</pre>
242
+
243
+ <h3 id="task-source-verify">Verify the load</h3>
244
+ <ul>
245
+ <li><code>rig plugin list</code> should show task source kind <code>acme-file-queue</code>.</li>
246
+ <li><code>rig-agent lookup LOCAL-1</code> should return the normalized record from <code>.rig/tasks.json</code>.</li>
247
+ <li>If <code>taskSource.kind</code> is mistyped, task IO fails before the sortie launches: <code>No task source factory registered for kind ...</code>.</li>
248
+ </ul>
249
+
250
+ <h3 id="task-source-seams">How it works</h3>
251
+ <ul>
252
+ <li><code>TaskSourceConfig</code> reserves built-in adapter fields and gives plugin adapters one extension bag: <code>options</code>.</li>
253
+ <li>The plugin host rejects duplicate task-source <code>kind</code> values. One kind, one factory.</li>
254
+ <li>Task IO calls <code>resolveTaskSourceFactoryByKind(config.taskSource.kind)</code>, then invokes <code>factory.factory(config.taskSource, { projectRoot })</code>. First-party <code>github-issues</code> and plugin kinds use the same route.</li>
255
+ </ul>
256
+ <div class="note"><b>Limit:</b> this example implements read-only <code>list</code> and <code>get</code>. Add <code>create</code> or <code>updateTask</code> only if your tracker can safely project Rig&rsquo;s write intent back to the source.</div>
257
+
258
+ <h2 id="pi-extension-package">OMP/Pi package <span class="ns">// live-session tool pin</span></h2>
259
+ <p>This is not a Rig plugin. It is an OMP/Pi package: the package manifest declares an extension, Rig pins the package in <code>runtime.pi.packages</code>, and the session asset materializer merges that package spec into <code>.pi/settings.json</code>. Pi installs and loads it when the live session starts.</p>
260
+
261
+ <h3 id="pi-package-files">Files</h3>
262
+ <p><code>packages/pi-branch-marker/package.json</code></p>
263
+ <pre class="code">{
264
+ "name": "@acme/pi-branch-marker",
265
+ "version": "0.1.0",
266
+ "type": "module",
267
+ "main": "dist/index.js",
268
+ "pi": {
269
+ "extensions": ["dist/index.js"]
270
+ },
271
+ "scripts": {
272
+ "build": "bun build src/index.ts --target bun --outdir dist"
273
+ },
274
+ "dependencies": {
275
+ "@oh-my-pi/pi-coding-agent": "^16.3.4",
276
+ "@oh-my-pi/pi-tui": "^16.3.4"
277
+ },
278
+ "devDependencies": {
279
+ "typescript": "^5.8.0"
280
+ }
281
+ }</pre>
282
+
283
+ <p><code>packages/pi-branch-marker/src/index.ts</code></p>
284
+ <pre class="code">import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
285
+ import { Text } from "@oh-my-pi/pi-tui";
286
+
287
+ type BranchMarkerParams = {
288
+ prefix?: string;
289
+ };
290
+
291
+ interface LooseTool {
292
+ name: string;
293
+ label: string;
294
+ description: string;
295
+ parameters: unknown;
296
+ approval?: "read" | "write" | "exec";
297
+ execute(
298
+ toolCallId: string,
299
+ params: BranchMarkerParams,
300
+ signal: AbortSignal | undefined,
301
+ onUpdate: unknown,
302
+ ctx: ExtensionContext,
303
+ ): Promise&lt;unknown&gt;;
304
+ renderCall?: (args: unknown) =&gt; Text;
305
+ }
306
+
307
+ export default function branchMarkerExtension(api: ExtensionAPI): void {
308
+ const z = api.zod;
309
+ const register = api.registerTool.bind(api) as unknown as (tool: LooseTool) =&gt; void;
310
+
311
+ register({
312
+ name: "branch_marker",
313
+ label: "Branch marker",
314
+ description: "Report the current git branch for this live OMP/Pi session.",
315
+ parameters: z.object({
316
+ prefix: z.string().optional().describe("Text prefix for the returned line."),
317
+ }),
318
+ approval: "read",
319
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
320
+ const cwd = ctx.cwd || process.cwd();
321
+ try {
322
+ const result = Bun.spawnSync(["git", "branch", "--show-current"], {
323
+ cwd,
324
+ stdout: "pipe",
325
+ stderr: "pipe",
326
+ });
327
+ if (result.exitCode !== 0) {
328
+ throw new Error(result.stderr.toString().trim() || `git exited ${result.exitCode}`);
329
+ }
330
+ const branch = result.stdout.toString().trim() || "detached";
331
+ return {
332
+ content: [{ type: "text", text: `${params.prefix ?? "branch"}: ${branch}` }],
333
+ isError: false,
334
+ details: { cwd, branch },
335
+ };
336
+ } catch (error) {
337
+ const message = error instanceof Error ? error.message : String(error);
338
+ return {
339
+ content: [{ type: "text", text: `branch_marker failed: ${message}` }],
340
+ isError: true,
341
+ details: { code: "COMMAND_FAILED", cwd },
342
+ };
343
+ }
344
+ },
345
+ renderCall: (args) =&gt; new Text(`branch_marker ${JSON.stringify(args)}`),
346
+ });
347
+ }</pre>
348
+
349
+ <p><code>.rig/rig.config.ts</code></p>
350
+ <pre class="code">import { defineConfig } from "@rig/core/config";
351
+
352
+ export default defineConfig({
353
+ project: { name: "ground-control" },
354
+ workspace: { mainRepo: "." },
355
+ plugins: [],
356
+ taskSource: { kind: "github-issues", owner: "acme", repo: "ground-control" },
357
+ runtime: {
358
+ harness: "pi",
359
+ pi: {
360
+ packages: ["file:./packages/pi-branch-marker"],
361
+ },
362
+ },
363
+ });</pre>
364
+
365
+ <p>Declarative repos can pin the same package in <code>.rig/rigfig.toml</code> when they do not need executable Rig plugin code:</p>
366
+ <pre class="code">[project]
367
+ name = "ground-control"
368
+
369
+ [workspace]
370
+ mainRepo = "."
371
+
372
+ [taskSource]
373
+ kind = "github-issues"
374
+ owner = "acme"
375
+ repo = "ground-control"
376
+
377
+ [runtime]
378
+ harness = "pi"
379
+
380
+ [runtime.pi]
381
+ packages = ["file:./packages/pi-branch-marker"]</pre>
382
+
383
+ <h3 id="pi-package-verify">Verify the materialization</h3>
384
+ <ul>
385
+ <li>Build the package before a run: <code>cd packages/pi-branch-marker &amp;&amp; bun run build</code>.</li>
386
+ <li>Start a Rig Pi session. Rig asks the session asset materializer to run; <code>.pi/settings.json</code> should contain the package spec from <code>runtime.pi.packages</code>.</li>
387
+ <li>In the live session, call the new tool with <code>{ "prefix": "fpv" }</code>; it returns <code>fpv: &lt;branch&gt;</code>.</li>
388
+ </ul>
389
+
390
+ <h3 id="pi-package-seams">How it works</h3>
391
+ <ul>
392
+ <li><code>runtime.pi.packages</code> is part of <code>RigConfig.runtime.pi</code>. It names npm specs, git specs, or local <code>file:</code> packages for Pi to load.</li>
393
+ <li>The plugin-host context asks the provider-owned session asset materializer to materialize both plugin skills and config Pi packages.</li>
394
+ <li>Plugin-contributed skills go under <code>.pi/skills/</code>; Pi package specs merge into <code>.pi/settings.json</code>. Rig tracks ownership separately in <code>.rig/state/pi-managed-packages.json</code>, not by adding marker fields to Pi settings.</li>
395
+ <li>The Pi loader reads package manifests from <code>omp</code> or <code>pi</code> and resolves <code>extensions</code>, <code>tools</code>, <code>hooks</code>, and <code>commands</code> from the package root.</li>
396
+ </ul>
397
+ <div class="note"><b>Limit:</b> <code>runtime.pi.packages</code> pins live-session packages. It does not register a Rig validator, task source, hook, or stage. Put workflow metadata in a <code>definePlugin</code> package and live OMP behavior in a Pi package.</div>
398
+
399
+ <h2 id="receipts">Receipts</h2>
400
+ <ul>
401
+ <li><code>packages/core/src/define-plugin.ts</code> validates plugin metadata and returns the original object with executable fields intact.</li>
402
+ <li><code>packages/core/src/plugin-host.ts</code> rejects duplicate task-source kinds; <code>packages/tasks-plugin/src/io/service.ts</code> resolves the configured kind through the plugin host and calls the factory with <code>{ projectRoot }</code>.</li>
403
+ <li><code>packages/lifecycle-plugin/src/control-plane/validation/validator.ts</code> dispatches configured check ids to registered executable validators.</li>
404
+ <li><code>packages/contracts/src/config.ts</code> defines <code>taskSource.options</code> and <code>runtime.pi.packages</code>.</li>
405
+ <li><code>packages/harness-plugin/src/session-asset-materializer-service.ts</code>, <code>skill-materializer.ts</code>, and <code>pi-settings-materializer.ts</code> define the <code>.pi/skills/</code> and <code>.pi/settings.json</code> handoff.</li>
406
+ <li><code>node_modules/@oh-my-pi/pi-coding-agent/src/extensibility/plugins/loader.ts</code> resolves package manifest extension entries for Pi.</li>
407
+ </ul>
408
+
409
+ <h2 id="see-also">See also</h2>
410
+ <ul>
411
+ <li><a href="../index.html">Plugin authoring</a> &mdash; the contribution contract and single-channel model.</li>
412
+ <li><a href="../../docs/task-sources/index.html">Task sources</a> &mdash; first-party adapter behavior and conventions.</li>
413
+ <li><a href="../../docs/validators/index.html">Validators &amp; hooks</a> &mdash; validation and hook runtime behavior.</li>
414
+ <li><a href="../../skills/index.html">Skills</a> &mdash; how <code>contributes.skills</code> becomes <code>.pi/skills/</code>.</li>
415
+ <li><a href="../../docs/pi-extensions/index.html">OMP/Pi extensions</a> &mdash; live-session package model.</li>
416
+ </ul>
417
+
418
+ <div class="nextnav"><a href="../../plugins/index.html"><span class="dir">&larr; Back</span><span class="ttl">Plugin authoring</span></a><a href="../../docs/pi-extensions/index.html"><span class="dir">Next &rarr;</span><span class="ttl">OMP/Pi extensions</span></a></div>
419
+ </main></div><footer><div class="wrap"><div class="foot"><div class="col"><h5>orchestrate</h5><a href="../../docs/getting-started/index.html">getting started</a><a href="../../docs/runs/index.html">the run lifecycle</a><a href="../../docs/operator/index.html">operator guide</a><a href="../../docs/gate/index.html">the merge gate</a></div><div class="col"><h5>extend</h5><a href="../../plugins/index.html">plugin authoring</a><a href="../../skills/index.html">skills guide</a><a href="../../docs/pi-extensions/index.html">OMP/Pi extensions</a><a href="../../docs/packages/index.html">packages</a></div><div class="col"><h5>live</h5><a href="../../index.html">the home rig</a><a href="../../cli/index.html">cli reference</a><a href="https://where.rig-does.work/install">install script</a></div><div class="sig">Rig is the OMP session extension.<br><span class="blk">You are the operator.</span><br>release: rig --version</div></div></div></footer><script src="../../assets/rig.js"></script><script src="../../assets/site.js?v=v9"></script></body></html>