@fiale-plus/pi-rogue 0.2.1 → 0.2.2

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 (32) hide show
  1. package/README.md +2 -1
  2. package/node_modules/@fiale-plus/pi-rogue-context-broker/README.md +4 -3
  3. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.test.ts +38 -4
  4. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.ts +52 -6
  5. package/node_modules/@fiale-plus/pi-rogue-router/README.md +32 -0
  6. package/node_modules/@fiale-plus/pi-rogue-router/package.json +30 -0
  7. package/node_modules/@fiale-plus/pi-rogue-router/src/checkpoints.test.ts +84 -0
  8. package/node_modules/@fiale-plus/pi-rogue-router/src/checkpoints.ts +355 -0
  9. package/node_modules/@fiale-plus/pi-rogue-router/src/cli.ts +277 -0
  10. package/node_modules/@fiale-plus/pi-rogue-router/src/completions.ts +34 -0
  11. package/node_modules/@fiale-plus/pi-rogue-router/src/config-extension.test.ts +133 -0
  12. package/node_modules/@fiale-plus/pi-rogue-router/src/config.ts +168 -0
  13. package/node_modules/@fiale-plus/pi-rogue-router/src/dataset.ts +154 -0
  14. package/node_modules/@fiale-plus/pi-rogue-router/src/decision-ledger.test.ts +148 -0
  15. package/node_modules/@fiale-plus/pi-rogue-router/src/decision.ts +138 -0
  16. package/node_modules/@fiale-plus/pi-rogue-router/src/extension.ts +139 -0
  17. package/node_modules/@fiale-plus/pi-rogue-router/src/git-features.ts +119 -0
  18. package/node_modules/@fiale-plus/pi-rogue-router/src/hash.ts +19 -0
  19. package/node_modules/@fiale-plus/pi-rogue-router/src/index.ts +15 -0
  20. package/node_modules/@fiale-plus/pi-rogue-router/src/learning.test.ts +241 -0
  21. package/node_modules/@fiale-plus/pi-rogue-router/src/learning.ts +382 -0
  22. package/node_modules/@fiale-plus/pi-rogue-router/src/ledger.ts +94 -0
  23. package/node_modules/@fiale-plus/pi-rogue-router/src/observe.ts +119 -0
  24. package/node_modules/@fiale-plus/pi-rogue-router/src/outcomes.ts +128 -0
  25. package/node_modules/@fiale-plus/pi-rogue-router/src/progress.ts +93 -0
  26. package/node_modules/@fiale-plus/pi-rogue-router/src/session-reader.ts +217 -0
  27. package/node_modules/@fiale-plus/pi-rogue-router/src/subagents.ts +178 -0
  28. package/node_modules/@fiale-plus/pi-rogue-router/src/types.ts +150 -0
  29. package/node_modules/@fiale-plus/pi-rogue-router/src/v1-telemetry.test.ts +293 -0
  30. package/package.json +5 -3
  31. package/src/extension.test.ts +1 -0
  32. package/src/extension.ts +2 -0
@@ -0,0 +1,150 @@
1
+ export const ROUTER_CHECKPOINT_SCHEMA = "pi-router.checkpoint.v1" as const;
2
+ export const RAW_SESSION_REF_SCHEMA = "pi-router.raw-session-ref.v1" as const;
3
+
4
+ export type SessionRole = "user" | "assistant" | "toolResult" | "system" | "unknown";
5
+ export type RouteAction =
6
+ | "continue_current"
7
+ | "continue_local"
8
+ | "summarize_context"
9
+ | "run_verifier"
10
+ | "ask_micro_hint"
11
+ | "escalate_plan_critique"
12
+ | "escalate_debug_diagnosis"
13
+ | "escalate_diff_review"
14
+ | "delegate_full_step"
15
+ | "spawn_subagent"
16
+ | "merge_subagent_result"
17
+ | "stop_and_ask_user";
18
+
19
+ export type SubagentRole = "explore" | "debug_diagnose" | "implement" | "review" | "verify";
20
+ export type SubagentToolPolicy = "read_only" | "test_only" | "edit_in_worktree" | "edit_main";
21
+ export type SubagentReturnContract = "evidence_summary_v1";
22
+ export type TaskStatus = "success" | "partial" | "failed" | "abandoned" | "unknown";
23
+ export type TaskType = "implementation" | "debug" | "review" | "research" | "ops" | "planning" | "unknown";
24
+
25
+ export type AdviceShape =
26
+ | "none"
27
+ | "micro_hint"
28
+ | "plan_critique"
29
+ | "debug_diagnosis"
30
+ | "diff_review"
31
+ | "full_delegation";
32
+
33
+ export type ContextPolicy =
34
+ | "none"
35
+ | "minimal"
36
+ | "recent_events"
37
+ | "focused_error_and_diff"
38
+ | "diff_only"
39
+ | "session_summary"
40
+ | "full_context";
41
+
42
+ export interface RawSessionRef {
43
+ schema: typeof RAW_SESSION_REF_SCHEMA;
44
+ path: string;
45
+ fromEvent: number;
46
+ toEvent: number;
47
+ fromByte: number;
48
+ toByte: number;
49
+ contentHash: string;
50
+ }
51
+
52
+ export interface SessionEventPointer {
53
+ index: number;
54
+ byteStart: number;
55
+ byteEnd: number;
56
+ id?: string;
57
+ timestamp?: string;
58
+ type: string;
59
+ role: SessionRole;
60
+ }
61
+
62
+ export interface SessionCommandEvent {
63
+ eventIndex: number;
64
+ toolCallId?: string;
65
+ toolName: string;
66
+ commandHash?: string;
67
+ normalizedCommandHash?: string;
68
+ isVerifier: boolean;
69
+ }
70
+
71
+ export interface SessionToolResultEvent {
72
+ eventIndex: number;
73
+ toolCallId?: string;
74
+ toolName?: string;
75
+ isError: boolean;
76
+ outputHash?: string;
77
+ normalizedOutputHash?: string;
78
+ errorHash?: string;
79
+ errorFingerprintHash?: string;
80
+ exitCode?: number;
81
+ failingTestHash?: string;
82
+ }
83
+
84
+ export interface DiffStats {
85
+ filesChanged: number;
86
+ linesAdded: number;
87
+ linesDeleted: number;
88
+ totalLines: number;
89
+ fileHashes: string[];
90
+ shortStatHash?: string;
91
+ }
92
+
93
+ export interface ProgressSignals {
94
+ sameCommandRepeatedCount: number;
95
+ sameErrorRepeatedCount: number;
96
+ errorChanged: boolean;
97
+ testsImproved: boolean | null;
98
+ filesTouched: number;
99
+ diffLines: number;
100
+ diffFilesChanged: number;
101
+ diffLinesAdded: number;
102
+ diffLinesDeleted: number;
103
+ diffChurnScore: number;
104
+ toolThrashScore: number;
105
+ goalDriftScore: number;
106
+ loopScore: number;
107
+ progressScore: number;
108
+ verifierUsed: boolean;
109
+ noVerifierUsed: boolean;
110
+ toolCallsLast10Turns: number;
111
+ }
112
+
113
+ export interface RouterCheckpoint {
114
+ schema: typeof ROUTER_CHECKPOINT_SCHEMA;
115
+ sessionId: string;
116
+ checkpointId: string;
117
+ createdAt: string;
118
+ rawSessionRef: RawSessionRef;
119
+ harness: "pi";
120
+ repoHash?: string;
121
+ goalHash?: string;
122
+ phase: "planning" | "implementation" | "debug" | "review" | "research" | "ops" | "unknown";
123
+ activeModel?: string;
124
+ provider?: string;
125
+ features: ProgressSignals & {
126
+ turnIndex: number;
127
+ contextTokensApprox: number | null;
128
+ gitDirty: boolean | null;
129
+ };
130
+ recent: {
131
+ lastUserGoalHash?: string;
132
+ lastCommandHash?: string;
133
+ lastErrorHash?: string;
134
+ lastErrorFingerprintHash?: string;
135
+ touchedFileHashes: string[];
136
+ diffFileHashes?: string[];
137
+ };
138
+ sourceEvent: SessionEventPointer;
139
+ }
140
+
141
+ export interface RouteDecision {
142
+ schema: "pi-router.decision.v1";
143
+ checkpointId: string;
144
+ action: RouteAction;
145
+ adviceShape: AdviceShape;
146
+ contextPolicy: ContextPolicy;
147
+ confidence: number;
148
+ reason: string;
149
+ policyVersion: string;
150
+ }
@@ -0,0 +1,293 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, expect, it } from "vitest";
6
+ import { rebuildCheckpointsFromSession, writeSessionCheckpointsJsonl } from "./checkpoints.js";
7
+ import { buildTrainingRows, writeTrainingRows } from "./dataset.js";
8
+ import { decideRoute } from "./decision.js";
9
+ import { buildRouteEvent } from "./ledger.js";
10
+ import { readGitDiffStats } from "./git-features.js";
11
+ import { generateTeacherPromptRequests } from "./learning.js";
12
+ import { buildUnknownOutcome, inferOutcomes, writeInferredOutcomes } from "./outcomes.js";
13
+ import { buildSubagentLedgerEvent, recommendSubagentDecision } from "./subagents.js";
14
+ import type { RouterCheckpoint } from "./types.js";
15
+
16
+ function tempFile(name: string): string {
17
+ return join(mkdtempSync(join(tmpdir(), "pi-router-v1-")), name);
18
+ }
19
+
20
+ function writeFixture(lines: Array<Record<string, unknown>>): string {
21
+ const path = tempFile("session.jsonl");
22
+ writeFileSync(path, lines.map((line) => JSON.stringify(line)).join("\n") + "\n");
23
+ return path;
24
+ }
25
+
26
+ function checkpoint(overrides: Partial<Omit<RouterCheckpoint, "features" | "recent">> & { features?: Partial<RouterCheckpoint["features"]> } = {}): RouterCheckpoint {
27
+ const base: RouterCheckpoint = {
28
+ schema: "pi-router.checkpoint.v1",
29
+ sessionId: "session-1",
30
+ checkpointId: "session-1:event-10",
31
+ createdAt: "2026-06-12T00:00:00.000Z",
32
+ rawSessionRef: { schema: "pi-router.raw-session-ref.v1", path: "/tmp/session.jsonl", fromEvent: 1, toEvent: 10, fromByte: 100, toByte: 200, contentHash: "hash-only" },
33
+ harness: "pi",
34
+ phase: "debug",
35
+ activeModel: "qwen3.6-35b-a3b-128k",
36
+ provider: "local",
37
+ features: {
38
+ turnIndex: 10,
39
+ sameCommandRepeatedCount: 1,
40
+ sameErrorRepeatedCount: 2,
41
+ errorChanged: false,
42
+ testsImproved: null,
43
+ filesTouched: 1,
44
+ diffLines: 40,
45
+ diffFilesChanged: 2,
46
+ diffLinesAdded: 25,
47
+ diffLinesDeleted: 15,
48
+ diffChurnScore: 0.033,
49
+ toolThrashScore: 0.1,
50
+ goalDriftScore: 0,
51
+ loopScore: 0.52,
52
+ progressScore: 0.48,
53
+ verifierUsed: false,
54
+ noVerifierUsed: true,
55
+ toolCallsLast10Turns: 4,
56
+ contextTokensApprox: 1200,
57
+ gitDirty: null,
58
+ },
59
+ recent: { lastErrorHash: "error", lastErrorFingerprintHash: "fingerprint", touchedFileHashes: ["file"], diffFileHashes: ["diff-file"] },
60
+ sourceEvent: { index: 10, byteStart: 100, byteEnd: 200, type: "message", role: "toolResult" },
61
+ };
62
+ return { ...base, ...overrides, features: { ...base.features, ...(overrides.features ?? {}) } };
63
+ }
64
+
65
+ describe("router v1 outcome and feature telemetry", () => {
66
+ it("normalizes noisy repeated errors into stable fingerprints", () => {
67
+ const sessionPath = writeFixture([
68
+ { type: "session", id: "s", cwd: "/repo" },
69
+ { type: "message", message: { role: "user", content: [{ type: "text", text: "debug failing test" }] } },
70
+ { type: "message", message: { role: "toolResult", isError: true, content: [{ type: "text", text: "FAIL /tmp/a/foo.test.ts:10:2\nError: boom at 2026-06-12T00:00:00Z" }] } },
71
+ { type: "message", message: { role: "toolResult", isError: true, content: [{ type: "text", text: "FAIL /var/b/foo.test.ts:99:8\nError: boom at 2026-06-13T00:00:00Z" }] } },
72
+ ]);
73
+
74
+ const last = rebuildCheckpointsFromSession(sessionPath).at(-1);
75
+
76
+ expect(last?.features.sameErrorRepeatedCount).toBe(2);
77
+ expect(last?.features.errorChanged).toBe(false);
78
+ expect(last?.recent.lastErrorFingerprintHash).toBeTruthy();
79
+ });
80
+
81
+ it("reads staged and untracked git diff counts without raw paths", () => {
82
+ const repo = mkdtempSync(join(tmpdir(), "pi-router-git-"));
83
+ execFileSync("git", ["init", "-q"], { cwd: repo });
84
+ execFileSync("git", ["config", "user.email", "router@example.invalid"], { cwd: repo });
85
+ execFileSync("git", ["config", "user.name", "router"], { cwd: repo });
86
+ writeFileSync(join(repo, "tracked.txt"), "a\n");
87
+ execFileSync("git", ["add", "tracked.txt"], { cwd: repo });
88
+ execFileSync("git", ["commit", "-q", "-m", "init"], { cwd: repo });
89
+ writeFileSync(join(repo, "tracked.txt"), "a\nb\n");
90
+ execFileSync("git", ["add", "tracked.txt"], { cwd: repo });
91
+ writeFileSync(join(repo, "new-file.txt"), "secret-ish content\n");
92
+
93
+ const stats = readGitDiffStats(repo);
94
+ const excluded = readGitDiffStats(repo, { excludePaths: [join(repo, "new-file.txt")] });
95
+
96
+ expect(stats.filesChanged).toBeGreaterThanOrEqual(2);
97
+ expect(stats.linesAdded).toBeGreaterThanOrEqual(1);
98
+ expect(stats.fileHashes).toHaveLength(stats.filesChanged);
99
+ expect(JSON.stringify(stats)).not.toContain("tracked.txt");
100
+ expect(JSON.stringify(stats)).not.toContain("new-file.txt");
101
+ expect(excluded.filesChanged).toBe(1);
102
+ });
103
+
104
+ it("reads untracked files from repo root when launched in a subdirectory", () => {
105
+ const repo = mkdtempSync(join(tmpdir(), "pi-router-git-subdir-"));
106
+ execFileSync("git", ["init", "-q"], { cwd: repo });
107
+ execFileSync("git", ["config", "user.email", "router@example.invalid"], { cwd: repo });
108
+ execFileSync("git", ["config", "user.name", "router"], { cwd: repo });
109
+ writeFileSync(join(repo, "tracked.txt"), "a\n");
110
+ execFileSync("git", ["add", "tracked.txt"], { cwd: repo });
111
+ execFileSync("git", ["commit", "-q", "-m", "init"], { cwd: repo });
112
+ execFileSync("mkdir", ["-p", "nested"], { cwd: repo });
113
+ writeFileSync(join(repo, "root-new.txt"), "one\n");
114
+
115
+ expect(readGitDiffStats(join(repo, "nested")).filesChanged).toBe(1);
116
+ });
117
+
118
+ it("reads untracked diff counts in repositories without HEAD", () => {
119
+ const repo = mkdtempSync(join(tmpdir(), "pi-router-git-no-head-"));
120
+ execFileSync("git", ["init", "-q"], { cwd: repo });
121
+ writeFileSync(join(repo, "new-file.txt"), "one\ntwo\n");
122
+ execFileSync("git", ["add", "new-file.txt"], { cwd: repo });
123
+ writeFileSync(join(repo, "new-file.txt"), "one\ntwo\nthree\n");
124
+
125
+ const stats = readGitDiffStats(repo);
126
+
127
+ expect(stats.filesChanged).toBe(1);
128
+ expect(stats.linesAdded).toBe(3);
129
+ });
130
+
131
+ it("infers conservative outcome skeletons linked to route events", () => {
132
+ const item = checkpoint();
133
+ const event = buildRouteEvent(item, decideRoute(item), "2026-06-12T00:00:00.000Z");
134
+ const outcome = buildUnknownOutcome(event, item, "2026-06-12T00:00:01.000Z");
135
+
136
+ expect(outcome).toMatchObject({
137
+ schema: "pi-router.outcome.v1",
138
+ sessionId: item.sessionId,
139
+ checkpointId: item.checkpointId,
140
+ routeEventId: event.eventId,
141
+ taskStatus: "unknown",
142
+ finalFilesTouched: 2,
143
+ finalDiffLines: 40,
144
+ reworkTurns: 1,
145
+ evidence: { source: "inferred" },
146
+ });
147
+ expect(buildUnknownOutcome(event, checkpoint({ features: { diffFilesChanged: 0, filesTouched: 1 } }))).toMatchObject({ finalFilesTouched: 1 });
148
+ expect(JSON.stringify(outcome)).not.toContain("Error: boom");
149
+ });
150
+
151
+ it("writes inferred outcomes from checkpoint and route-event files", () => {
152
+ const item = checkpoint();
153
+ const checkpointPath = tempFile("checkpoints.jsonl");
154
+ const eventsPath = tempFile("events.jsonl");
155
+ const outputPath = tempFile("outcomes.jsonl");
156
+ writeFileSync(checkpointPath, `${JSON.stringify(item)}\n`);
157
+ writeFileSync(eventsPath, `${JSON.stringify(buildRouteEvent(item, decideRoute(item)))}\n`);
158
+
159
+ const summary = writeInferredOutcomes({ checkpointPath, eventsPath, outputPath });
160
+
161
+ expect(summary.outcomes).toBe(1);
162
+ expect(readFileSync(outputPath, "utf8")).toContain("pi-router.outcome.v1");
163
+ expect(() => writeInferredOutcomes({ checkpointPath, eventsPath: join(tmpdir(), "missing-router-events.jsonl"), outputPath: tempFile("bad.jsonl") })).toThrow(/required route events file not found/);
164
+ });
165
+
166
+ it("rejects workspace diff annotation for multi-session rebuilds", async () => {
167
+ await expect(writeSessionCheckpointsJsonl([tempFile("one.jsonl"), tempFile("two.jsonl")], tempFile("out.jsonl"), { workspaceDiff: true }))
168
+ .rejects.toThrow(/exactly one current session/);
169
+ });
170
+ });
171
+
172
+ describe("router v1 teacher requests and trainable gate dataset", () => {
173
+ it("generates explicit teacher prompt requests without raw transcript content", () => {
174
+ const item = checkpoint();
175
+ const requests = generateTeacherPromptRequests([item], "openai-codex/gpt-5.5");
176
+
177
+ expect(requests[0]).toMatchObject({
178
+ schema: "pi-router.teacher-prompt.v1",
179
+ teacher: "openai-codex/gpt-5.5",
180
+ checkpointId: item.checkpointId,
181
+ });
182
+ expect(requests[0].allowedActions).toContain("spawn_subagent");
183
+ expect(JSON.stringify(requests[0])).not.toContain("npm test");
184
+ });
185
+
186
+ it("exports binary gate rows and excludes local-rule labels as ground truth by default", () => {
187
+ const item = checkpoint();
188
+ const event = buildRouteEvent(item, decideRoute(item));
189
+ const outcome = inferOutcomes([event], [item])[0];
190
+ const localRuleLabel = {
191
+ schema: "pi-router.teacher-label.v1" as const,
192
+ labelId: "label-1",
193
+ generatedAt: "2026-06-12T00:00:00.000Z",
194
+ teacher: "local-rule",
195
+ checkpointId: item.checkpointId,
196
+ sessionId: item.sessionId,
197
+ rawSessionRef: item.rawSessionRef,
198
+ suggestedAction: "run_verifier" as const,
199
+ confidence: 0.8,
200
+ rationale: "local rule",
201
+ source: "local-rule" as const,
202
+ };
203
+
204
+ const oldCheckpoint = { ...item, features: { ...item.features, diffFilesChanged: undefined as unknown as number, diffChurnScore: undefined as unknown as number } };
205
+ const oldRows = buildTrainingRows({ checkpoints: [oldCheckpoint] });
206
+ expect(oldRows[0].features).toMatchObject({ diffFilesChanged: 0, diffChurnScore: 0 });
207
+
208
+ const rows = buildTrainingRows({ checkpoints: [item], routeEvents: [event], outcomes: [outcome], labels: [localRuleLabel] });
209
+
210
+ expect(rows[0].labels.binaryGate).toBe("unknown");
211
+ expect(rows[0].provenance.excludedLocalRuleAsTruth).toBe(true);
212
+ expect(rows[0].provenance.localRuleAction).toBe(decideRoute(item).action);
213
+
214
+ const routeOnlyOutcome = { ...outcome, checkpointId: undefined, taskStatus: "partial" as const };
215
+ const rowsFromRouteEventOutcome = buildTrainingRows({ checkpoints: [item], routeEvents: [event], outcomes: [routeOnlyOutcome] });
216
+ expect(rowsFromRouteEventOutcome[0].outcome.taskStatus).toBe("partial");
217
+
218
+ const includedRows = buildTrainingRows({ checkpoints: [item], labels: [localRuleLabel], includeLocalRuleLabels: true });
219
+ expect(includedRows[0].labels).toMatchObject({ binaryGate: "intervene", source: "local-rule" });
220
+ });
221
+
222
+ it("writes labeled dataset rows when teacher-output labels are provided", () => {
223
+ const item = checkpoint();
224
+ const checkpointPath = tempFile("checkpoints.jsonl");
225
+ const labelsPath = tempFile("labels.jsonl");
226
+ const outputPath = tempFile("training.jsonl");
227
+ writeFileSync(checkpointPath, `${JSON.stringify(item)}\n`);
228
+ writeFileSync(labelsPath, `${JSON.stringify({
229
+ schema: "pi-router.teacher-label.v1",
230
+ labelId: "label-2",
231
+ generatedAt: "2026-06-12T00:00:00.000Z",
232
+ teacher: "openai-codex/gpt-5.5",
233
+ checkpointId: item.checkpointId,
234
+ sessionId: item.sessionId,
235
+ rawSessionRef: item.rawSessionRef,
236
+ suggestedAction: "run_verifier",
237
+ confidence: 0.81,
238
+ rationale: "needs verifier",
239
+ source: "teacher-output",
240
+ })}\n`);
241
+
242
+ const summary = writeTrainingRows({ checkpointPath, labelsPath, outputPath });
243
+
244
+ expect(summary).toMatchObject({ rows: 1, labeledRows: 1 });
245
+ expect(readFileSync(outputPath, "utf8")).toContain("pi-router.training-row.v1");
246
+ expect(() => writeTrainingRows({ checkpointPath, outcomesPath: join(tmpdir(), "missing-outcomes.jsonl"), outputPath: tempFile("bad-training.jsonl") })).toThrow(/outcomes file not found/);
247
+ expect(() => writeTrainingRows({ checkpointPath, eventsPath: join(tmpdir(), "missing-events.jsonl"), outputPath: tempFile("bad-events-training.jsonl") })).toThrow(/route events file not found/);
248
+ });
249
+ });
250
+
251
+ describe("router v1 subagent-aware observation telemetry", () => {
252
+ it("recommends observe-only subagent decisions with evidence-summary contracts", () => {
253
+ const decision = recommendSubagentDecision(checkpoint(), { worker: "qwen", smart: "gpt-5.5" });
254
+
255
+ expect(decision).toMatchObject({
256
+ schema: "pi-router.subagent-decision.v1",
257
+ action: "spawn_subagent",
258
+ subagentRole: "debug_diagnose",
259
+ targetModel: "gpt-5.5",
260
+ toolPolicy: "read_only",
261
+ returnContract: "evidence_summary_v1",
262
+ });
263
+ });
264
+
265
+ it("builds parent-child subagent ledger events", () => {
266
+ const event = buildSubagentLedgerEvent({
267
+ parentSessionId: "parent",
268
+ childSessionId: "child",
269
+ parentCheckpointId: "parent:event-1",
270
+ subagentRole: "review",
271
+ model: "openai-codex/gpt-5.5",
272
+ toolPolicy: "read_only",
273
+ contextPolicy: "diff_only",
274
+ inputSummaryHash: "input-hash",
275
+ outputSummaryHash: "output-hash",
276
+ acceptedIntoParent: null,
277
+ useful: null,
278
+ causedRework: null,
279
+ returnContract: "evidence_summary_v1",
280
+ recordedAt: "2026-06-12T00:00:00.000Z",
281
+ });
282
+
283
+ expect(event).toMatchObject({
284
+ schema: "pi-router.subagent-ledger-event.v1",
285
+ parentSessionId: "parent",
286
+ childSessionId: "child",
287
+ acceptedIntoParent: null,
288
+ useful: null,
289
+ causedRework: null,
290
+ });
291
+ expect(event.eventId).toBeTruthy();
292
+ });
293
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiale-plus/pi-rogue",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Public Pi-Rogue package for bundled advisor, orchestration, and context broker logic.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,13 +37,15 @@
37
37
  "@fiale-plus/pi-core": "^0.1.0",
38
38
  "@fiale-plus/pi-rogue-advisor": "^0.1.0",
39
39
  "@fiale-plus/pi-rogue-context-broker": "^0.1.0",
40
- "@fiale-plus/pi-rogue-orchestration": "^0.1.0"
40
+ "@fiale-plus/pi-rogue-orchestration": "^0.1.0",
41
+ "@fiale-plus/pi-rogue-router": "^0.1.0"
41
42
  },
42
43
  "bundledDependencies": [
43
44
  "@fiale-plus/pi-core",
44
45
  "@fiale-plus/pi-rogue-advisor",
45
46
  "@fiale-plus/pi-rogue-context-broker",
46
- "@fiale-plus/pi-rogue-orchestration"
47
+ "@fiale-plus/pi-rogue-orchestration",
48
+ "@fiale-plus/pi-rogue-router"
47
49
  ],
48
50
  "publishConfig": {
49
51
  "access": "public"
@@ -40,6 +40,7 @@ describe("bundle extension defaults", () => {
40
40
  await registerBundle(pi);
41
41
 
42
42
  expect(commands.has("context")).toBe(true);
43
+ expect(commands.has("router")).toBe(true);
43
44
  });
44
45
 
45
46
  it("keeps an explicit env kill switch for context broker rollout", async () => {
package/src/extension.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { registerAdvisor } from "@fiale-plus/pi-rogue-advisor";
3
3
  import { registerOrchestration } from "@fiale-plus/pi-rogue-orchestration";
4
+ import { registerRouter } from "@fiale-plus/pi-rogue-router/extension";
4
5
 
5
6
  const DISABLED_VALUES = new Set(["0", "false", "no", "off"]);
6
7
 
@@ -20,6 +21,7 @@ export async function registerBundle(pi: ExtensionAPI): Promise<void> {
20
21
 
21
22
  registerAdvisor(pi);
22
23
  registerOrchestration(pi);
24
+ registerRouter(pi);
23
25
  }
24
26
 
25
27
  export default function bundleExtension(pi: ExtensionAPI): Promise<void> {