jfl 0.5.0 → 0.6.1

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 (135) hide show
  1. package/dist/commands/context-hub.d.ts +1 -0
  2. package/dist/commands/context-hub.d.ts.map +1 -1
  3. package/dist/commands/context-hub.js +246 -2
  4. package/dist/commands/context-hub.js.map +1 -1
  5. package/dist/commands/peter.d.ts +2 -0
  6. package/dist/commands/peter.d.ts.map +1 -1
  7. package/dist/commands/peter.js +242 -52
  8. package/dist/commands/peter.js.map +1 -1
  9. package/dist/commands/setup.d.ts +12 -0
  10. package/dist/commands/setup.d.ts.map +1 -0
  11. package/dist/commands/setup.js +322 -0
  12. package/dist/commands/setup.js.map +1 -0
  13. package/dist/commands/train.d.ts +33 -0
  14. package/dist/commands/train.d.ts.map +1 -0
  15. package/dist/commands/train.js +510 -0
  16. package/dist/commands/train.js.map +1 -0
  17. package/dist/commands/verify.d.ts +14 -0
  18. package/dist/commands/verify.d.ts.map +1 -0
  19. package/dist/commands/verify.js +276 -0
  20. package/dist/commands/verify.js.map +1 -0
  21. package/dist/dashboard-static/assets/index-CW9ZxqX8.css +1 -0
  22. package/dist/dashboard-static/assets/index-DNN__p4K.js +121 -0
  23. package/dist/dashboard-static/index.html +2 -2
  24. package/dist/index.js +99 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/agent-session.d.ts.map +1 -1
  27. package/dist/lib/agent-session.js +12 -4
  28. package/dist/lib/agent-session.js.map +1 -1
  29. package/dist/lib/eval-snapshot.js +1 -1
  30. package/dist/lib/eval-snapshot.js.map +1 -1
  31. package/dist/lib/pi-sky/bridge.d.ts +55 -0
  32. package/dist/lib/pi-sky/bridge.d.ts.map +1 -0
  33. package/dist/lib/pi-sky/bridge.js +264 -0
  34. package/dist/lib/pi-sky/bridge.js.map +1 -0
  35. package/dist/lib/pi-sky/cost-monitor.d.ts +21 -0
  36. package/dist/lib/pi-sky/cost-monitor.d.ts.map +1 -0
  37. package/dist/lib/pi-sky/cost-monitor.js +126 -0
  38. package/dist/lib/pi-sky/cost-monitor.js.map +1 -0
  39. package/dist/lib/pi-sky/eval-sweep.d.ts +27 -0
  40. package/dist/lib/pi-sky/eval-sweep.d.ts.map +1 -0
  41. package/dist/lib/pi-sky/eval-sweep.js +141 -0
  42. package/dist/lib/pi-sky/eval-sweep.js.map +1 -0
  43. package/dist/lib/pi-sky/event-router.d.ts +32 -0
  44. package/dist/lib/pi-sky/event-router.d.ts.map +1 -0
  45. package/dist/lib/pi-sky/event-router.js +176 -0
  46. package/dist/lib/pi-sky/event-router.js.map +1 -0
  47. package/dist/lib/pi-sky/experiment.d.ts +9 -0
  48. package/dist/lib/pi-sky/experiment.d.ts.map +1 -0
  49. package/dist/lib/pi-sky/experiment.js +83 -0
  50. package/dist/lib/pi-sky/experiment.js.map +1 -0
  51. package/dist/lib/pi-sky/index.d.ts +16 -0
  52. package/dist/lib/pi-sky/index.d.ts.map +1 -0
  53. package/dist/lib/pi-sky/index.js +16 -0
  54. package/dist/lib/pi-sky/index.js.map +1 -0
  55. package/dist/lib/pi-sky/stratus-gate.d.ts +28 -0
  56. package/dist/lib/pi-sky/stratus-gate.d.ts.map +1 -0
  57. package/dist/lib/pi-sky/stratus-gate.js +61 -0
  58. package/dist/lib/pi-sky/stratus-gate.js.map +1 -0
  59. package/dist/lib/pi-sky/swarm.d.ts +28 -0
  60. package/dist/lib/pi-sky/swarm.d.ts.map +1 -0
  61. package/dist/lib/pi-sky/swarm.js +208 -0
  62. package/dist/lib/pi-sky/swarm.js.map +1 -0
  63. package/dist/lib/pi-sky/types.d.ts +139 -0
  64. package/dist/lib/pi-sky/types.d.ts.map +1 -0
  65. package/dist/lib/pi-sky/types.js +2 -0
  66. package/dist/lib/pi-sky/types.js.map +1 -0
  67. package/dist/lib/pi-sky/voice-bridge.d.ts +20 -0
  68. package/dist/lib/pi-sky/voice-bridge.d.ts.map +1 -0
  69. package/dist/lib/pi-sky/voice-bridge.js +91 -0
  70. package/dist/lib/pi-sky/voice-bridge.js.map +1 -0
  71. package/dist/lib/policy-head.d.ts +16 -1
  72. package/dist/lib/policy-head.d.ts.map +1 -1
  73. package/dist/lib/policy-head.js +117 -19
  74. package/dist/lib/policy-head.js.map +1 -1
  75. package/dist/lib/predictor.d.ts +10 -0
  76. package/dist/lib/predictor.d.ts.map +1 -1
  77. package/dist/lib/predictor.js +46 -7
  78. package/dist/lib/predictor.js.map +1 -1
  79. package/dist/lib/setup/agent-generator.d.ts +18 -0
  80. package/dist/lib/setup/agent-generator.d.ts.map +1 -0
  81. package/dist/lib/setup/agent-generator.js +114 -0
  82. package/dist/lib/setup/agent-generator.js.map +1 -0
  83. package/dist/lib/setup/context-analyzer.d.ts +16 -0
  84. package/dist/lib/setup/context-analyzer.d.ts.map +1 -0
  85. package/dist/lib/setup/context-analyzer.js +112 -0
  86. package/dist/lib/setup/context-analyzer.js.map +1 -0
  87. package/dist/lib/setup/doc-auditor.d.ts +54 -0
  88. package/dist/lib/setup/doc-auditor.d.ts.map +1 -0
  89. package/dist/lib/setup/doc-auditor.js +629 -0
  90. package/dist/lib/setup/doc-auditor.js.map +1 -0
  91. package/dist/lib/setup/domain-generator.d.ts +7 -0
  92. package/dist/lib/setup/domain-generator.d.ts.map +1 -0
  93. package/dist/lib/setup/domain-generator.js +58 -0
  94. package/dist/lib/setup/domain-generator.js.map +1 -0
  95. package/dist/lib/setup/smart-eval-generator.d.ts +38 -0
  96. package/dist/lib/setup/smart-eval-generator.d.ts.map +1 -0
  97. package/dist/lib/setup/smart-eval-generator.js +378 -0
  98. package/dist/lib/setup/smart-eval-generator.js.map +1 -0
  99. package/dist/lib/setup/smart-recommender.d.ts +63 -0
  100. package/dist/lib/setup/smart-recommender.d.ts.map +1 -0
  101. package/dist/lib/setup/smart-recommender.js +329 -0
  102. package/dist/lib/setup/smart-recommender.js.map +1 -0
  103. package/dist/lib/setup/spec-generator.d.ts +63 -0
  104. package/dist/lib/setup/spec-generator.d.ts.map +1 -0
  105. package/dist/lib/setup/spec-generator.js +310 -0
  106. package/dist/lib/setup/spec-generator.js.map +1 -0
  107. package/dist/lib/setup/violation-agent-generator.d.ts +32 -0
  108. package/dist/lib/setup/violation-agent-generator.d.ts.map +1 -0
  109. package/dist/lib/setup/violation-agent-generator.js +255 -0
  110. package/dist/lib/setup/violation-agent-generator.js.map +1 -0
  111. package/package.json +1 -1
  112. package/packages/pi/extensions/context.ts +88 -55
  113. package/packages/pi/extensions/hub-resolver.ts +63 -0
  114. package/packages/pi/extensions/index.ts +16 -3
  115. package/packages/pi/extensions/memory-tool.ts +9 -4
  116. package/packages/pi/extensions/session.ts +68 -16
  117. package/packages/pi/extensions/tool-renderers.ts +23 -8
  118. package/scripts/train/requirements.txt +5 -0
  119. package/scripts/train/train-policy-head.py +477 -0
  120. package/scripts/train/v2/dataset.py +81 -0
  121. package/scripts/train/v2/domain.json +18 -0
  122. package/scripts/train/v2/eval.py +196 -0
  123. package/scripts/train/v2/generate_data.py +219 -0
  124. package/scripts/train/v2/infer.py +188 -0
  125. package/scripts/train/v2/model.py +112 -0
  126. package/scripts/train/v2/precompute.py +132 -0
  127. package/scripts/train/v2/train.py +302 -0
  128. package/scripts/train/v2/transform_buffer.py +227 -0
  129. package/scripts/train/v2/validate_data.py +115 -0
  130. package/template/.claude/settings.json +2 -15
  131. package/template/scripts/session/session-cleanup.sh +2 -11
  132. package/template/scripts/session/session-end-hub.sh +72 -0
  133. package/template/scripts/session/session-start-hub.sh +105 -0
  134. package/dist/dashboard-static/assets/index-B6b867Pv.js +0 -121
  135. package/dist/dashboard-static/assets/index-Y4BrqxV-.css +0 -1
@@ -18,6 +18,7 @@ import { TrajectoryLoader } from "../lib/trajectory-loader.js";
18
18
  import { readEvals } from "../lib/eval-store.js";
19
19
  import { TrainingBuffer } from "../lib/training-buffer.js";
20
20
  import { PolicyHeadInference } from "../lib/policy-head.js";
21
+ import { PiRpcBridge, CostMonitor, EventRouter } from "../lib/pi-sky/index.js";
21
22
  function hasRalphTui() {
22
23
  try {
23
24
  execSync("which ralph-tui", { stdio: "ignore" });
@@ -27,6 +28,15 @@ function hasRalphTui() {
27
28
  return false;
28
29
  }
29
30
  }
31
+ function hasPi() {
32
+ try {
33
+ execSync("which pi", { stdio: "ignore" });
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
30
40
  function getAuthToken(projectRoot) {
31
41
  const tokenPath = path.join(projectRoot, ".jfl", "context-hub.token");
32
42
  if (fs.existsSync(tokenPath)) {
@@ -194,6 +204,108 @@ async function postHubEvent(projectRoot, eventType, data) {
194
204
  console.log(chalk.gray(" PP: Warning - could not post event to hub"));
195
205
  }
196
206
  }
207
+ async function initPiRuntime(projectRoot, options) {
208
+ const state = {
209
+ bridge: null,
210
+ costMonitor: null,
211
+ eventRouter: null,
212
+ };
213
+ // Check if Pi should be used
214
+ const shouldUsePi = options.usePi === true || (options.usePi !== false && hasPi());
215
+ if (!shouldUsePi) {
216
+ return state;
217
+ }
218
+ console.log(chalk.cyan(" Initializing Pi runtime..."));
219
+ // Create Pi RPC bridge
220
+ state.bridge = new PiRpcBridge({
221
+ cwd: projectRoot,
222
+ yolo: true,
223
+ noSession: true,
224
+ });
225
+ // Initialize cost monitor if budget is set
226
+ if (options.budget) {
227
+ const costConfig = {
228
+ maxCost: options.budget,
229
+ downgradeModel: { provider: "anthropic", modelId: "claude-haiku-4-5-20251001" },
230
+ downgradeThinkingLevel: "low",
231
+ upgradeModel: { provider: "anthropic", modelId: "claude-sonnet-4-20250514" },
232
+ upgradeThinkingLevel: "medium",
233
+ criticalKeywords: ["security", "auth", "payment", "migration", "deploy", "production"],
234
+ };
235
+ state.costMonitor = new CostMonitor(costConfig);
236
+ state.costMonitor.on("downgrade", (data) => {
237
+ console.log(chalk.yellow(` CostMonitor: Downgrading ${data.agent} to ${data.model} (budget: $${data.totalCost.toFixed(2)}/$${data.budget.toFixed(2)})`));
238
+ });
239
+ state.costMonitor.on("upgrade", (data) => {
240
+ console.log(chalk.cyan(` CostMonitor: Upgrading ${data.agent} to ${data.model} (critical path: ${data.reason})`));
241
+ });
242
+ state.costMonitor.on("cost_update", (data) => {
243
+ if (data.remaining < data.totalCost * 0.2) {
244
+ console.log(chalk.yellow(` CostMonitor: Budget low — $${data.remaining.toFixed(2)} remaining`));
245
+ }
246
+ });
247
+ }
248
+ // Initialize event router for Context Hub SSE
249
+ const hubUrl = getProjectHubUrl(projectRoot);
250
+ state.eventRouter = new EventRouter({
251
+ hubUrl,
252
+ routes: [
253
+ {
254
+ pattern: "eval:scored",
255
+ action: "steer",
256
+ messageTemplate: "SYSTEM EVENT: Eval regression detected. Agent={{data.agent}}, delta={{data.delta}}. Investigate what caused this before continuing.",
257
+ condition: (e) => Number(e.data?.delta ?? 0) < -0.01,
258
+ },
259
+ {
260
+ pattern: "scope:impact",
261
+ action: "follow_up",
262
+ messageTemplate: "SYSTEM EVENT: Scope impact from {{data.agent}} on {{data.pattern}}. Consider if this affects your current work.",
263
+ },
264
+ ],
265
+ });
266
+ state.eventRouter.on("route", (data) => {
267
+ console.log(chalk.gray(` EventRouter: ${data.event.type} → ${data.route.action}`));
268
+ });
269
+ state.eventRouter.on("error", (err) => {
270
+ // Silent — hub may not be running
271
+ });
272
+ return state;
273
+ }
274
+ async function runTaskWithPi(bridge, task, costMonitor, timeout = 300000) {
275
+ if (!bridge.started) {
276
+ await bridge.start();
277
+ }
278
+ // Check critical path for model upgrade if cost monitor is active
279
+ if (costMonitor) {
280
+ await costMonitor.checkCriticalPath("experiment", task);
281
+ }
282
+ // Create a promise that resolves when agent is done
283
+ const agentDone = new Promise((resolve) => {
284
+ bridge.once("agent_end", () => resolve());
285
+ });
286
+ // Send prompt
287
+ await bridge.prompt(task);
288
+ // Wait for completion with timeout
289
+ const timeoutPromise = new Promise((_, reject) => {
290
+ setTimeout(() => reject(new Error("Pi task timeout")), timeout);
291
+ });
292
+ try {
293
+ await Promise.race([agentDone, timeoutPromise]);
294
+ }
295
+ catch {
296
+ await bridge.abort().catch(() => { });
297
+ }
298
+ // Update cost tracking
299
+ if (costMonitor) {
300
+ await costMonitor.checkCost("experiment");
301
+ }
302
+ }
303
+ async function shutdownPiRuntime(state) {
304
+ state.eventRouter?.stop();
305
+ if (state.bridge && !state.bridge.exited) {
306
+ await state.bridge.shutdown().catch(() => { });
307
+ }
308
+ }
197
309
  async function runWithPR(projectRoot, task) {
198
310
  if (!task) {
199
311
  console.log(chalk.yellow("\n --task is required for pr mode"));
@@ -612,20 +724,52 @@ Respond with ONLY a JSON array, no other text.`;
612
724
  console.log(chalk.cyan(" Dispatching to Peter Parker PR workflow...\n"));
613
725
  await runWithPR(projectRoot, proposal.task);
614
726
  }
615
- async function runAutoresearch(projectRoot, rounds) {
727
+ async function runAutoresearch(projectRoot, options) {
728
+ const { rounds, budget, usePi } = options;
616
729
  console.log(chalk.bold(`\n Peter Parker - Autoresearch Mode (${rounds} rounds)\n`));
617
730
  console.log(chalk.gray(" Pattern: branch → change → eval → keep|revert → repeat"));
618
- console.log(chalk.gray(" Only the winning experiment gets a PR.\n"));
619
- if (!hasRalphTui()) {
620
- console.log(chalk.yellow(" ralph-tui is not installed"));
731
+ console.log(chalk.gray(" Only the winning experiment gets a PR."));
732
+ if (budget) {
733
+ console.log(chalk.gray(` Budget: $${budget.toFixed(2)}`));
734
+ }
735
+ if (usePi || (usePi !== false && hasPi())) {
736
+ console.log(chalk.cyan(" Runtime: Pi (RPC)"));
737
+ }
738
+ else {
739
+ console.log(chalk.gray(" Runtime: claude CLI"));
740
+ }
741
+ console.log();
742
+ // Initialize Pi runtime if available
743
+ const piState = await initPiRuntime(projectRoot, { budget, usePi });
744
+ const usePiRuntime = piState.bridge !== null;
745
+ // Start event router to listen for eval regressions
746
+ if (piState.eventRouter && piState.bridge) {
747
+ piState.eventRouter.registerBridge("experiment", piState.bridge);
748
+ try {
749
+ await piState.eventRouter.startSse();
750
+ }
751
+ catch {
752
+ // Fall back to polling if SSE not available
753
+ await piState.eventRouter.startPolling(5000);
754
+ }
755
+ }
756
+ // Register bridge with cost monitor
757
+ if (piState.costMonitor && piState.bridge) {
758
+ piState.costMonitor.registerBridge("experiment", piState.bridge);
759
+ }
760
+ // Only require ralph-tui if not using Pi
761
+ if (!usePiRuntime && !hasRalphTui()) {
762
+ console.log(chalk.yellow(" ralph-tui is not installed (and Pi not available)"));
621
763
  console.log(chalk.gray(" Install: bun install -g ralph-tui\n"));
622
764
  return;
623
765
  }
624
- const configPath = path.join(projectRoot, ".ralph-tui", "config.toml");
625
- if (!fs.existsSync(configPath)) {
626
- console.log(chalk.yellow(" No Peter Parker config found"));
627
- console.log(chalk.gray(" Run: jfl peter setup\n"));
628
- return;
766
+ if (!usePiRuntime) {
767
+ const configPath = path.join(projectRoot, ".ralph-tui", "config.toml");
768
+ if (!fs.existsSync(configPath)) {
769
+ console.log(chalk.yellow(" No Peter Parker config found"));
770
+ console.log(chalk.gray(" Run: jfl peter setup\n"));
771
+ return;
772
+ }
629
773
  }
630
774
  const loader = new TrajectoryLoader(projectRoot);
631
775
  const evals = readEvals(projectRoot);
@@ -667,9 +811,26 @@ async function runAutoresearch(projectRoot, rounds) {
667
811
  }))), 15);
668
812
  const policyHead = new PolicyHeadInference(projectRoot);
669
813
  const useMultiProposal = policyHead.isLoaded;
814
+ // v2 policy head: get recommended action type before generating proposals
815
+ let actionGuidance = "";
816
+ if (policyHead.isLoaded && policyHead.version >= 2) {
817
+ try {
818
+ const rlState = buildRLState(evals, round, results.slice(-5).map(r => r.delta));
819
+ const selection = await policyHead.selectAction(rlState, "Improve codebase quality");
820
+ actionGuidance = `\nPolicy head recommends action type: "${selection.action}" (confidence: ${(selection.confidence * 100).toFixed(0)}%).`;
821
+ if (selection.alternatives.length > 0) {
822
+ actionGuidance += ` Alternatives: ${selection.alternatives.map(a => `${a.action}(${(a.confidence * 100).toFixed(0)}%)`).join(", ")}.`;
823
+ }
824
+ actionGuidance += `\nFavor proposals that align with the "${selection.action}" action type.`;
825
+ console.log(chalk.magenta(` Policy head v2 recommends: ${selection.action} (${(selection.confidence * 100).toFixed(0)}%)`));
826
+ }
827
+ catch {
828
+ // v2 not available, continue without guidance
829
+ }
830
+ }
670
831
  const prompt = useMultiProposal
671
832
  ? `Autoresearch round ${round}/${rounds}. Suggest 3 specific improvements ranked by expected impact.
672
-
833
+ ${actionGuidance}
673
834
  Eval history:
674
835
  ${evalSummary}
675
836
 
@@ -681,7 +842,7 @@ ${results.map(r => `- Round ${r.round}: "${r.task}" → delta=${r.delta > 0 ? "+
681
842
 
682
843
  Respond with ONLY a JSON array of 3 objects: [{"task": "...", "predicted_delta": 0.0-1.0, "reasoning": "...", "risk": "..."}, ...]`
683
844
  : `Autoresearch round ${round}/${rounds}. Suggest ONE specific improvement.
684
-
845
+ ${actionGuidance}
685
846
  Eval history:
686
847
  ${evalSummary}
687
848
 
@@ -758,44 +919,56 @@ Suggest the SINGLE highest-value change. JSON format:
758
919
  console.log(chalk.red(` Failed to create branch ${branchName}`));
759
920
  continue;
760
921
  }
761
- await new Promise((resolve) => {
762
- const ralphDir = path.join(projectRoot, ".ralph-tui");
763
- if (!fs.existsSync(ralphDir))
764
- fs.mkdirSync(ralphDir, { recursive: true });
765
- const prdPath = path.join(ralphDir, "autoresearch-task.json");
766
- const prd = {
767
- name: "Autoresearch Task",
768
- branchName: `ralph/autoresearch-${Date.now()}`,
769
- description: proposal.task,
770
- userStories: [{
771
- id: "US-001",
772
- title: proposal.task.slice(0, 80),
773
- description: proposal.task,
774
- acceptanceCriteria: ["Task completed"],
775
- priority: 1, passes: false, notes: "", dependsOn: [],
776
- }],
777
- metadata: { updatedAt: new Date().toISOString() },
778
- };
779
- fs.writeFileSync(prdPath, JSON.stringify(prd, null, 2));
780
- const env = { ...process.env };
781
- delete env.CLAUDECODE;
782
- delete env.CLAUDE_CODE;
783
- const child = spawn("claude", [
784
- "--dangerously-skip-permissions",
785
- "-p", proposal.task,
786
- "--output-format", "text",
787
- ], {
788
- cwd: projectRoot, stdio: "inherit", env,
789
- });
790
- child.on("error", () => { try {
791
- fs.unlinkSync(prdPath);
922
+ // Run the task using Pi runtime or claude CLI
923
+ if (usePiRuntime && piState.bridge) {
924
+ try {
925
+ await runTaskWithPi(piState.bridge, proposal.task, piState.costMonitor, 300000);
792
926
  }
793
- catch { } resolve(); });
794
- child.on("exit", () => { try {
795
- fs.unlinkSync(prdPath);
927
+ catch (err) {
928
+ console.log(chalk.yellow(` Pi task failed: ${err.message}`));
796
929
  }
797
- catch { } resolve(); });
798
- });
930
+ }
931
+ else {
932
+ // Fall back to claude CLI
933
+ await new Promise((resolve) => {
934
+ const ralphDir = path.join(projectRoot, ".ralph-tui");
935
+ if (!fs.existsSync(ralphDir))
936
+ fs.mkdirSync(ralphDir, { recursive: true });
937
+ const prdPath = path.join(ralphDir, "autoresearch-task.json");
938
+ const prd = {
939
+ name: "Autoresearch Task",
940
+ branchName: `ralph/autoresearch-${Date.now()}`,
941
+ description: proposal.task,
942
+ userStories: [{
943
+ id: "US-001",
944
+ title: proposal.task.slice(0, 80),
945
+ description: proposal.task,
946
+ acceptanceCriteria: ["Task completed"],
947
+ priority: 1, passes: false, notes: "", dependsOn: [],
948
+ }],
949
+ metadata: { updatedAt: new Date().toISOString() },
950
+ };
951
+ fs.writeFileSync(prdPath, JSON.stringify(prd, null, 2));
952
+ const env = { ...process.env };
953
+ delete env.CLAUDECODE;
954
+ delete env.CLAUDE_CODE;
955
+ const child = spawn("claude", [
956
+ "--dangerously-skip-permissions",
957
+ "-p", proposal.task,
958
+ "--output-format", "text",
959
+ ], {
960
+ cwd: projectRoot, stdio: "inherit", env,
961
+ });
962
+ child.on("error", () => { try {
963
+ fs.unlinkSync(prdPath);
964
+ }
965
+ catch { } resolve(); });
966
+ child.on("exit", () => { try {
967
+ fs.unlinkSync(prdPath);
968
+ }
969
+ catch { } resolve(); });
970
+ });
971
+ }
799
972
  const diffCheck = gitExec(["diff", "--quiet", "HEAD"], projectRoot);
800
973
  const untrackedResult = spawnSync("git", ["ls-files", "--others", "--exclude-standard"], {
801
974
  cwd: projectRoot, encoding: "utf-8", stdio: "pipe",
@@ -961,6 +1134,14 @@ Suggest the SINGLE highest-value change. JSON format:
961
1134
  }
962
1135
  }
963
1136
  gitExec(["stash", "pop"], projectRoot);
1137
+ // Shutdown Pi runtime
1138
+ if (piState.bridge || piState.eventRouter) {
1139
+ console.log(chalk.gray(" Shutting down Pi runtime..."));
1140
+ await shutdownPiRuntime(piState);
1141
+ if (piState.costMonitor) {
1142
+ console.log(chalk.gray(` Total cost: $${piState.costMonitor.totalCost.toFixed(4)}`));
1143
+ }
1144
+ }
964
1145
  console.log();
965
1146
  }
966
1147
  // ============================================================================
@@ -1060,7 +1241,8 @@ async function agentRun(projectRoot, agentName, roundsOverride) {
1060
1241
  // Allow override via CLI for debugging/testing
1061
1242
  const rounds = roundsOverride ?? config.rounds ?? 50;
1062
1243
  // Get scope_files for display
1063
- const displayScopeFiles = config.constraints.scope_files || config.constraints.files_in_scope;
1244
+ const rawScopeFiles = config.constraints.scope_files || config.constraints.files_in_scope || [];
1245
+ const displayScopeFiles = Array.isArray(rawScopeFiles) ? rawScopeFiles : [String(rawScopeFiles)];
1064
1246
  const scopeFilesDisplay = displayScopeFiles.slice(0, 3).join(", ") + (displayScopeFiles.length > 3 ? "..." : "");
1065
1247
  console.log(chalk.bold(`\n Running Scoped Agent: ${agentName} (${rounds} rounds)\n`));
1066
1248
  console.log(chalk.gray(` Metric: ${config.metric} (${config.direction})`));
@@ -1091,7 +1273,8 @@ async function agentRun(projectRoot, agentName, roundsOverride) {
1091
1273
  ? new StratusClient({ apiKey: process.env.STRATUS_API_KEY })
1092
1274
  : null;
1093
1275
  // Get scope_files from config (Karpathy pattern: focused file list)
1094
- const scopeFiles = config.constraints.scope_files || config.constraints.files_in_scope || [];
1276
+ const rawScope = config.constraints.scope_files || config.constraints.files_in_scope || [];
1277
+ const scopeFiles = Array.isArray(rawScope) ? rawScope : [String(rawScope)];
1095
1278
  const scopeFilesStr = scopeFiles.slice(0, 5).join(", ");
1096
1279
  for (let round = 1; round <= rounds; round++) {
1097
1280
  console.log(chalk.bold(`\n ── Round ${round}/${rounds} ${"─".repeat(40)}\n`));
@@ -1757,7 +1940,8 @@ export async function peterCommand(action, options = {}) {
1757
1940
  case "experiment": {
1758
1941
  if (options.mode === "autoresearch") {
1759
1942
  const rounds = parseInt(options.rounds || "5", 10);
1760
- await runAutoresearch(projectRoot, rounds);
1943
+ const budget = options.budget ? parseFloat(options.budget) : undefined;
1944
+ await runAutoresearch(projectRoot, { rounds, budget, usePi: options.pi });
1761
1945
  }
1762
1946
  else {
1763
1947
  await runExperiment(projectRoot);
@@ -1766,7 +1950,8 @@ export async function peterCommand(action, options = {}) {
1766
1950
  }
1767
1951
  case "autoresearch": {
1768
1952
  const rounds = parseInt(options.rounds || "5", 10);
1769
- await runAutoresearch(projectRoot, rounds);
1953
+ const budget = options.budget ? parseFloat(options.budget) : undefined;
1954
+ await runAutoresearch(projectRoot, { rounds, budget, usePi: options.pi });
1770
1955
  break;
1771
1956
  }
1772
1957
  case "telemetry": {
@@ -1805,10 +1990,15 @@ export async function peterCommand(action, options = {}) {
1805
1990
  console.log(" jfl peter run [--task <task>] Run orchestrator");
1806
1991
  console.log(" jfl peter pr --task <task> Run + branch + PR");
1807
1992
  console.log(" jfl peter experiment Proactive: pick + execute next experiment");
1808
- console.log(" jfl peter autoresearch [--rounds N] Shortcut for autoresearch mode");
1993
+ console.log(" jfl peter autoresearch [--rounds N] [--budget $] [--pi]");
1994
+ console.log(" Autoresearch mode with Pi runtime");
1809
1995
  console.log(" jfl peter status Show status + recent events");
1810
1996
  console.log(" jfl peter dashboard Live event stream dashboard");
1811
1997
  console.log();
1998
+ console.log(chalk.bold(" Pi-sky Runtime Options:\n"));
1999
+ console.log(" --budget <amount> Cost budget in USD (auto-downgrades model when exceeded)");
2000
+ console.log(" --pi Force Pi runtime (default: auto-detect)");
2001
+ console.log();
1812
2002
  console.log(chalk.bold(" Telemetry & RL:\n"));
1813
2003
  console.log(" jfl peter telemetry Run telemetry agent (platform digest)");
1814
2004
  console.log(" jfl peter sentinel Run Sentinel nightly review");