pi-harness-runtime 1.1.387 → 1.1.388

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 (127) hide show
  1. package/AGENTS.md +9 -4
  2. package/index.ts +20 -18
  3. package/package.json +1 -1
  4. package/packages/a2a-adapter/package.json +1 -1
  5. package/packages/agent-policy/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/codex-watcher/package.json +1 -1
  20. package/packages/config-capture/package.json +1 -1
  21. package/packages/connection-runtime/package.json +1 -1
  22. package/packages/context-compiler/package.json +1 -1
  23. package/packages/context-discovery/package.json +1 -1
  24. package/packages/context-manager/package.json +1 -1
  25. package/packages/contracts/dist/workspace-env-format.d.ts +71 -0
  26. package/packages/contracts/dist/workspace-env-format.js +120 -0
  27. package/packages/contracts/dist/workspace-env.d.ts +7 -30
  28. package/packages/contracts/dist/workspace-env.js +18 -57
  29. package/packages/contracts/package.json +1 -1
  30. package/packages/contracts/src/workspace-env-format.ts +165 -0
  31. package/packages/contracts/src/workspace-env.ts +27 -99
  32. package/packages/contracts/test/workspace-env.test.ts +144 -0
  33. package/packages/cookie-sanitizer/package.json +1 -1
  34. package/packages/cost-optimizer/package.json +1 -1
  35. package/packages/dependency-analyzer/package.json +1 -1
  36. package/packages/django-plugin/package.json +1 -1
  37. package/packages/doc-generator/package.json +1 -1
  38. package/packages/durable-review/package.json +1 -1
  39. package/packages/e2e-tester/package.json +1 -1
  40. package/packages/evaluation-engine/package.json +1 -1
  41. package/packages/evaluation-runner/package.json +1 -1
  42. package/packages/event-bus/package.json +1 -1
  43. package/packages/event-sink/package.json +1 -1
  44. package/packages/event-store/package.json +1 -1
  45. package/packages/experience-replay/package.json +1 -1
  46. package/packages/feedback-collector/package.json +1 -1
  47. package/packages/file-copy-helper/package.json +1 -1
  48. package/packages/framework-detector/package.json +1 -1
  49. package/packages/framework-plugin-sdk/package.json +1 -1
  50. package/packages/frappe-plugin/package.json +1 -1
  51. package/packages/generic-web-plugin/package.json +1 -1
  52. package/packages/health-monitor/package.json +1 -1
  53. package/packages/intent-analyzer/package.json +1 -1
  54. package/packages/jev-judge/package.json +1 -1
  55. package/packages/knowledge-graph/package.json +1 -1
  56. package/packages/knowledge-retrieval/package.json +1 -1
  57. package/packages/laravel-plugin/package.json +1 -1
  58. package/packages/learning-engine/package.json +1 -1
  59. package/packages/mcp-adapter/package.json +1 -1
  60. package/packages/memory-engine/package.json +1 -1
  61. package/packages/milestone-manager/package.json +1 -1
  62. package/packages/model-registry/package.json +1 -1
  63. package/packages/nextjs-plugin/package.json +1 -1
  64. package/packages/notification/package.json +1 -1
  65. package/packages/observability/package.json +1 -1
  66. package/packages/okf-indexer/package.json +1 -1
  67. package/packages/paperclip-integration/package.json +1 -1
  68. package/packages/performance-optimizer/package.json +1 -1
  69. package/packages/permission-mode/package.json +1 -1
  70. package/packages/privilege-broker/package.json +1 -1
  71. package/packages/project-analyzer/package.json +1 -1
  72. package/packages/project-bootstrap/package.json +1 -1
  73. package/packages/projection-engine/package.json +1 -1
  74. package/packages/prompt-compiler/package.json +1 -1
  75. package/packages/prompt-versioning/package.json +1 -1
  76. package/packages/provider-adapter-sdk/package.json +1 -1
  77. package/packages/provider-router/package.json +1 -1
  78. package/packages/provider-selector/package.json +1 -1
  79. package/packages/providers/package.json +1 -1
  80. package/packages/qdrant-skills/package.json +1 -1
  81. package/packages/quota-manager/package.json +1 -1
  82. package/packages/rate-limiter/package.json +1 -1
  83. package/packages/react-vite-plugin/package.json +1 -1
  84. package/packages/relay/dist/index.d.ts +3 -0
  85. package/packages/relay/dist/index.js +3 -0
  86. package/packages/relay/dist/relay-endpoint.d.ts +94 -0
  87. package/packages/relay/dist/relay-endpoint.js +239 -0
  88. package/packages/relay/package.json +1 -1
  89. package/packages/relay/src/index.ts +17 -0
  90. package/packages/relay/src/relay-endpoint.ts +304 -0
  91. package/packages/relay/test/relay-endpoint.test.ts +278 -0
  92. package/packages/release-manager/package.json +1 -1
  93. package/packages/requirement-compiler/package.json +1 -1
  94. package/packages/runtime/package.json +1 -1
  95. package/packages/scheduler/package.json +1 -1
  96. package/packages/scheduler-adapter/package.json +1 -1
  97. package/packages/session/package.json +1 -1
  98. package/packages/session-api/package.json +1 -1
  99. package/packages/session-export/package.json +1 -1
  100. package/packages/shared-context/package.json +1 -1
  101. package/packages/skill-mcp-client/package.json +1 -1
  102. package/packages/skill-registry/package.json +1 -1
  103. package/packages/skills/package.json +1 -1
  104. package/packages/sprint-planner/package.json +1 -1
  105. package/packages/subscription-engine/package.json +1 -1
  106. package/packages/task-compiler/package.json +1 -1
  107. package/packages/telegram/package.json +1 -1
  108. package/packages/tencentdb-memory/package.json +1 -1
  109. package/packages/tencentdb-sync/package.json +1 -1
  110. package/packages/test-data-generator/package.json +1 -1
  111. package/packages/test-generator/package.json +1 -1
  112. package/packages/todo-bd-sync/package.json +1 -1
  113. package/packages/token-estimation/package.json +1 -1
  114. package/packages/token-optimizer/package.json +1 -1
  115. package/packages/trajectory/package.json +1 -1
  116. package/packages/tui/package.json +1 -1
  117. package/packages/tui/test/harness-footer.test.ts +1 -1
  118. package/packages/types/package.json +1 -1
  119. package/packages/voice-api/package.json +1 -1
  120. package/packages/webmcp-tester/package.json +1 -1
  121. package/packages/wiki/package.json +1 -1
  122. package/packages/workflow-events/package.json +1 -1
  123. package/packages/workspace-scanner/package.json +1 -1
  124. package/packages/worktree/package.json +1 -1
  125. package/packages/write-review/package.json +1 -1
  126. package/scripts/relay-client.ts +24 -4
  127. package/scripts/unified-webhook-server.ts +29 -22
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Workspace environment formatting helpers.
3
+ *
4
+ * Split out of workspace-env.ts so the wording can be unit tested without the
5
+ * node:sqlite dependency (bun 1.3.x has no node:sqlite module).
6
+ *
7
+ * Wording rules:
8
+ * - The mode is only reported when it is EXPLICIT (NODE_ENV). An unknown mode
9
+ * is always shown with its reason, never as a bare word next to a dev
10
+ * server state that could be read as proof of the mode.
11
+ * - A dev/build script is a CAPABILITY, not a state. The status line says
12
+ * "not running (script available)" so it cannot be mistaken for a running
13
+ * server or for evidence about the workspace mode.
14
+ */
15
+
16
+ /**
17
+ * Workspace mode with explicit states
18
+ */
19
+ export type WorkspaceMode = "development" | "production" | "test" | "unknown";
20
+
21
+ /**
22
+ * Evidence source for mode detection
23
+ */
24
+ export interface EnvEvidence {
25
+ source: "process.env" | ".env file" | "package.json" | "command" | "detection";
26
+ key: string;
27
+ value: string;
28
+ timestamp: number;
29
+ }
30
+
31
+ /**
32
+ * Workspace environment result
33
+ */
34
+ export interface WorkspaceEnvResult {
35
+ workspacePath: string;
36
+ mode: WorkspaceMode;
37
+ evidence: EnvEvidence[];
38
+ hasDevScript: boolean;
39
+ hasBuildScript: boolean;
40
+ packageManager: string | null;
41
+ devServerRunning: boolean;
42
+ buildServerRunning: boolean;
43
+ ramTotalGb: number | null;
44
+ ramAvailableGb: number | null;
45
+ ramEffectiveLimitGb: number | null;
46
+ lowRamWarning: boolean;
47
+ buildAllowed: boolean;
48
+ detectedAt: number;
49
+ }
50
+
51
+ /**
52
+ * Human readable mode label.
53
+ *
54
+ * "unknown" always carries the reason: the mode can only be proven by an
55
+ * explicit NODE_ENV (process env or .env file). When a NODE_ENV IS present
56
+ * but names a mode this detector does not know ("staging"), the label says
57
+ * exactly that instead of claiming there is no NODE_ENV at all.
58
+ */
59
+ export function describeWorkspaceMode(env: WorkspaceEnvResult): string {
60
+ if (env.mode !== "unknown") {
61
+ return env.mode.toUpperCase();
62
+ }
63
+ const nodeEnv = env.evidence.find(
64
+ (e) => e.key === "NODE_ENV" && (e.source === "process.env" || e.source === ".env file"),
65
+ );
66
+ if (nodeEnv) {
67
+ return `UNKNOWN (NODE_ENV=${nodeEnv.value} is not a known mode)`;
68
+ }
69
+ return "UNKNOWN (no explicit NODE_ENV)";
70
+ }
71
+
72
+ /**
73
+ * Describe a dev/build server as a state.
74
+ *
75
+ * "not running (script available)" means the script EXISTS, i.e. a server
76
+ * could be started. It is not evidence that a server is running and not
77
+ * evidence about the workspace mode.
78
+ */
79
+ export function describeServerState(running: boolean, hasScript: boolean): string {
80
+ if (running) return "running";
81
+ if (hasScript) return "not running (script available)";
82
+ return "no script";
83
+ }
84
+
85
+ /**
86
+ * One line status for the footer / status bar.
87
+ */
88
+ export function formatEnvStatusLine(env: WorkspaceEnvResult): string {
89
+ const mode = describeWorkspaceMode(env);
90
+ const devStatus = describeServerState(env.devServerRunning, env.hasDevScript);
91
+ const buildStatus = describeServerState(env.buildServerRunning, env.hasBuildScript);
92
+
93
+ let status = `Workspace: ${mode} | dev server: ${devStatus} | build: ${buildStatus}`;
94
+ const ramStr = formatRam(env);
95
+ if (ramStr) {
96
+ status += ` | RAM: ${ramStr}`;
97
+ }
98
+
99
+ return status;
100
+ }
101
+
102
+ /**
103
+ * RAM segment of the status line.
104
+ *
105
+ * Both values are optional - an unreadable /proc must never produce a
106
+ * "7.7/null" line.
107
+ */
108
+ function formatRam(env: WorkspaceEnvResult): string {
109
+ const total = env.ramTotalGb;
110
+ const available = env.ramAvailableGb;
111
+ if (total === null && available === null) return "";
112
+ if (total === null) return `${available}GB available`;
113
+ if (available === null) return `${total}GB total`;
114
+ return `${available}/${total}`;
115
+ }
116
+
117
+ /**
118
+ * RAM segment for the agent block.
119
+ */
120
+ function formatRamForAgent(env: WorkspaceEnvResult): string {
121
+ const total = env.ramTotalGb;
122
+ const available = env.ramAvailableGb;
123
+ if (total !== null && available !== null) return `${available}/${total}GB available`;
124
+ if (total !== null) return `${total}GB total`;
125
+ return `${available}GB available`;
126
+ }
127
+
128
+ /**
129
+ * Multi line description for the agent context.
130
+ */
131
+ export function formatEnvForAgentResult(env: WorkspaceEnvResult): string {
132
+ let message = `[Workspace Environment]\n`;
133
+ message += `Mode: ${describeWorkspaceMode(env)}\n`;
134
+
135
+ if (env.evidence.length > 0) {
136
+ const sources = env.evidence.map((e) => `${e.source}:${e.value}`).join(", ");
137
+ message += `Evidence: ${sources}\n`;
138
+ }
139
+
140
+ message += `Dev script: ${env.hasDevScript ? "yes" : "no"}\n`;
141
+ message += `Build script: ${env.hasBuildScript ? "yes" : "no"}\n`;
142
+
143
+ if (env.devServerRunning) {
144
+ message += `Dev server: running\n`;
145
+ }
146
+ if (env.buildServerRunning) {
147
+ message += `Build server: running\n`;
148
+ }
149
+
150
+ if (env.ramTotalGb !== null || env.ramAvailableGb !== null) {
151
+ message += `RAM: ${formatRamForAgent(env)}\n`;
152
+ if (env.lowRamWarning) {
153
+ message += `WARNING: Low RAM - avoid parallel build + dev\n`;
154
+ }
155
+ }
156
+
157
+ message += `\n`;
158
+ message += `Build allowed: ${env.buildAllowed ? "yes" : "no - requires explicit request"}\n`;
159
+
160
+ if (env.mode === "unknown") {
161
+ message += `\nNOTE: Environment unknown - a dev/build script only means a server CAN be started, it does not establish the workspace mode. Explicitly ask the user before build.\n`;
162
+ }
163
+
164
+ return message;
165
+ }
@@ -14,6 +14,13 @@ import { DatabaseSync } from "node:sqlite";
14
14
  import { existsSync, readFileSync, mkdirSync } from "fs";
15
15
  import { join } from "path";
16
16
  import { platform, totalmem, freemem } from "os";
17
+ import {
18
+ describeServerState,
19
+ describeWorkspaceMode,
20
+ formatEnvForAgentResult,
21
+ formatEnvStatusLine as formatStatusLine,
22
+ } from "./workspace-env-format.js";
23
+ import type { EnvEvidence, WorkspaceEnvResult, WorkspaceMode } from "./workspace-env-format.js";
17
24
 
18
25
  const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
19
26
 
@@ -63,39 +70,16 @@ function initDatabase(): DatabaseSync {
63
70
  }
64
71
 
65
72
  /**
66
- * Workspace mode with explicit states
73
+ * The mode/evidence types and every human readable rendering of the workspace
74
+ * environment live in workspace-env-format.ts. They are re-exported here so
75
+ * the existing import paths keep working.
67
76
  */
68
- export type WorkspaceMode = "development" | "production" | "test" | "unknown";
69
-
70
- /**
71
- * Evidence source for mode detection
72
- */
73
- export interface EnvEvidence {
74
- source: "process.env" | ".env file" | "package.json" | "command" | "detection";
75
- key: string;
76
- value: string;
77
- timestamp: number;
78
- }
79
-
80
- /**
81
- * Workspace environment result
82
- */
83
- export interface WorkspaceEnvResult {
84
- workspacePath: string;
85
- mode: WorkspaceMode;
86
- evidence: EnvEvidence[];
87
- hasDevScript: boolean;
88
- hasBuildScript: boolean;
89
- packageManager: string | null;
90
- devServerRunning: boolean;
91
- buildServerRunning: boolean;
92
- ramTotalGb: number | null;
93
- ramAvailableGb: number | null;
94
- ramEffectiveLimitGb: number | null;
95
- lowRamWarning: boolean;
96
- buildAllowed: boolean;
97
- detectedAt: number;
98
- }
77
+ export {
78
+ describeServerState,
79
+ describeWorkspaceMode,
80
+ formatEnvForAgentResult,
81
+ };
82
+ export type { EnvEvidence, WorkspaceEnvResult, WorkspaceMode };
99
83
 
100
84
  /**
101
85
  * Parse dotenv file without loading secrets
@@ -191,31 +175,22 @@ function determineMode(
191
175
  // Explicit process.env takes highest priority (for build processes)
192
176
  if (processEnv) {
193
177
  const env = processEnv.toLowerCase();
178
+ // The evidence is recorded for EVERY value, including one that names
179
+ // no known mode ("staging"): the label then says which NODE_ENV could
180
+ // not be interpreted instead of claiming there is none.
181
+ evidence.push({
182
+ source: "process.env",
183
+ key: "NODE_ENV",
184
+ value: env,
185
+ timestamp: Date.now(),
186
+ });
194
187
  if (env === "production") {
195
- evidence.push({
196
- source: "process.env",
197
- key: "NODE_ENV",
198
- value: env,
199
- timestamp: Date.now(),
200
- });
201
188
  return "production";
202
189
  }
203
190
  if (env === "development" || env === "dev") {
204
- evidence.push({
205
- source: "process.env",
206
- key: "NODE_ENV",
207
- value: env,
208
- timestamp: Date.now(),
209
- });
210
191
  return "development";
211
192
  }
212
193
  if (env === "test") {
213
- evidence.push({
214
- source: "process.env",
215
- key: "NODE_ENV",
216
- value: env,
217
- timestamp: Date.now(),
218
- });
219
194
  return "test";
220
195
  }
221
196
  }
@@ -474,59 +449,12 @@ export function revalidateWorkspaceEnv(workspacePath: string): WorkspaceEnvResul
474
449
  * Format workspace env as agent-readable message
475
450
  */
476
451
  export function formatEnvForAgent(workspacePath: string): string {
477
- const env = getWorkspaceEnv(workspacePath);
478
-
479
- let message = `[Workspace Environment]\n`;
480
- message += `Mode: ${env.mode.toUpperCase()}\n`;
481
-
482
- if (env.evidence.length > 0) {
483
- const sources = env.evidence.map((e) => `${e.source}:${e.value}`).join(", ");
484
- message += `Evidence: ${sources}\n`;
485
- }
486
-
487
- message += `Dev script: ${env.hasDevScript ? "yes" : "no"}\n`;
488
- message += `Build script: ${env.hasBuildScript ? "yes" : "no"}\n`;
489
-
490
- if (env.devServerRunning) {
491
- message += `Dev server: running\n`;
492
- }
493
- if (env.buildServerRunning) {
494
- message += `Build server: running\n`;
495
- }
496
-
497
- if (env.ramTotalGb !== null) {
498
- const ramStr = env.ramAvailableGb !== null
499
- ? `${env.ramAvailableGb}/${env.ramTotalGb}GB available`
500
- : `${env.ramTotalGb}GB total`;
501
- message += `RAM: ${ramStr}\n`;
502
- if (env.lowRamWarning) {
503
- message += `WARNING: Low RAM - avoid parallel build + dev\n`;
504
- }
505
- }
506
-
507
- message += `\n`;
508
- message += `Build allowed: ${env.buildAllowed ? "yes" : "no - requires explicit request"}\n`;
509
-
510
- if (env.mode === "unknown") {
511
- message += `\nNOTE: Environment unknown. Explicitly ask user before build.\n`;
512
- }
513
-
514
- return message;
452
+ return formatEnvForAgentResult(getWorkspaceEnv(workspacePath));
515
453
  }
516
454
 
517
455
  /**
518
456
  * Format workspace env as compact status line for UI
519
457
  */
520
458
  export function formatEnvStatusLine(env: WorkspaceEnvResult): string {
521
- const mode = env.mode.toUpperCase();
522
- const devStatus = env.devServerRunning ? "running" : (env.hasDevScript ? "available" : "none");
523
- const buildStatus = env.buildServerRunning ? "running" : (env.hasBuildScript ? "explicit request" : "none");
524
- const ramStr = env.ramAvailableGb !== null ? `${env.ramAvailableGb}/${env.ramTotalGb}` : "";
525
-
526
- let status = `Workspace: ${mode} | dev server: ${devStatus} | build: ${buildStatus}`;
527
- if (ramStr) {
528
- status += ` | RAM: ${ramStr}`;
529
- }
530
-
531
- return status;
459
+ return formatStatusLine(env);
532
460
  }
@@ -0,0 +1,144 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ describeServerState,
6
+ describeWorkspaceMode,
7
+ formatEnvForAgentResult,
8
+ formatEnvStatusLine,
9
+ } from "../src/workspace-env-format.ts";
10
+ import type { EnvEvidence, WorkspaceEnvResult } from "../src/workspace-env-format.ts";
11
+
12
+ function makeEnv(overrides: Partial<WorkspaceEnvResult> = {}): WorkspaceEnvResult {
13
+ return {
14
+ workspacePath: "/home/frappe/pi-harness-runtime",
15
+ mode: "unknown",
16
+ evidence: [],
17
+ hasDevScript: false,
18
+ hasBuildScript: false,
19
+ packageManager: "bun",
20
+ devServerRunning: false,
21
+ buildServerRunning: false,
22
+ ramTotalGb: 15,
23
+ ramAvailableGb: 7.7,
24
+ ramEffectiveLimitGb: null,
25
+ lowRamWarning: true,
26
+ buildAllowed: false,
27
+ detectedAt: 0,
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ describe("workspace mode wording", () => {
33
+ test("an unknown mode carries its reason", () => {
34
+ expect(describeWorkspaceMode(makeEnv())).toBe("UNKNOWN (no explicit NODE_ENV)");
35
+ });
36
+
37
+ test("an unrecognized NODE_ENV names the value that could not be read", () => {
38
+ const evidence: EnvEvidence[] = [
39
+ { source: "process.env", key: "NODE_ENV", value: "staging", timestamp: 0 },
40
+ ];
41
+
42
+ expect(describeWorkspaceMode(makeEnv({ evidence }))).toBe(
43
+ "UNKNOWN (NODE_ENV=staging is not a known mode)",
44
+ );
45
+ });
46
+
47
+ test("a known mode is reported plainly", () => {
48
+ expect(describeWorkspaceMode(makeEnv({ mode: "development" }))).toBe("DEVELOPMENT");
49
+ expect(describeWorkspaceMode(makeEnv({ mode: "production" }))).toBe("PRODUCTION");
50
+ expect(describeWorkspaceMode(makeEnv({ mode: "test" }))).toBe("TEST");
51
+ });
52
+
53
+ test("a dev script is a capability, not a state", () => {
54
+ expect(describeServerState(false, true)).toBe("not running (script available)");
55
+ expect(describeServerState(true, true)).toBe("running");
56
+ expect(describeServerState(false, false)).toBe("no script");
57
+ });
58
+ });
59
+
60
+ describe("formatEnvStatusLine", () => {
61
+ test("does not show a bare UNKNOWN next to an available dev script", () => {
62
+ const line = formatEnvStatusLine(makeEnv({ hasDevScript: true, hasBuildScript: true }));
63
+
64
+ expect(line).toContain("Workspace: UNKNOWN (no explicit NODE_ENV)");
65
+ expect(line).not.toContain("Workspace: UNKNOWN |");
66
+ expect(line).toContain("dev server: not running (script available)");
67
+ expect(line).toContain("build: not running (script available)");
68
+ expect(line).toContain("RAM: 7.7/15");
69
+ });
70
+
71
+ test("a running dev server is reported as running", () => {
72
+ const line = formatEnvStatusLine(makeEnv({ mode: "development", hasDevScript: true, devServerRunning: true }));
73
+
74
+ expect(line).toContain("Workspace: DEVELOPMENT");
75
+ expect(line).toContain("dev server: running");
76
+ });
77
+
78
+ test("a missing RAM reading omits the RAM segment", () => {
79
+ const line = formatEnvStatusLine(makeEnv({ ramTotalGb: null, ramAvailableGb: null }));
80
+
81
+ expect(line).not.toContain("RAM:");
82
+ });
83
+
84
+ test("a partial RAM reading never renders a null value", () => {
85
+ const available = formatEnvStatusLine(makeEnv({ ramTotalGb: null, ramAvailableGb: 7.7 }));
86
+ const total = formatEnvStatusLine(makeEnv({ ramTotalGb: 15, ramAvailableGb: null }));
87
+
88
+ expect(available).toContain("RAM: 7.7GB available");
89
+ expect(available).not.toContain("null");
90
+ expect(total).toContain("RAM: 15GB total");
91
+ expect(total).not.toContain("null");
92
+ });
93
+ });
94
+
95
+ describe("export surface of workspace-env.ts", () => {
96
+ // The module itself needs node:sqlite, which bun 1.3.x does not provide.
97
+ // Assert the re-export surface on the source text instead.
98
+ const source = readFileSync(
99
+ join(import.meta.dir, "..", "src", "workspace-env.ts"),
100
+ "utf8",
101
+ );
102
+
103
+ test("keeps re-exporting the formatting helpers", () => {
104
+ for (const name of [
105
+ "describeServerState",
106
+ "describeWorkspaceMode",
107
+ "formatEnvForAgentResult",
108
+ ]) {
109
+ expect(source).toContain(name);
110
+ }
111
+ });
112
+
113
+ test("keeps exporting the detector functions", () => {
114
+ for (const name of [
115
+ "detectWorkspaceEnv",
116
+ "loadWorkspaceEnv",
117
+ "getWorkspaceEnv",
118
+ "revalidateWorkspaceEnv",
119
+ "formatEnvForAgent",
120
+ "formatEnvStatusLine",
121
+ ]) {
122
+ expect(source).toContain(`export function ${name}`);
123
+ }
124
+ });
125
+ });
126
+
127
+ describe("formatEnvForAgentResult", () => {
128
+ test("explains that a dev script does not establish the mode", () => {
129
+ const text = formatEnvForAgentResult(makeEnv({ hasDevScript: true }));
130
+
131
+ expect(text).toContain("Mode: UNKNOWN (no explicit NODE_ENV)");
132
+ expect(text).toContain("Dev script: yes");
133
+ expect(text).toContain("Build allowed: no - requires explicit request");
134
+ expect(text).toContain("a dev/build script only means a server CAN be started");
135
+ });
136
+
137
+ test("a known mode drops the unknown-mode note", () => {
138
+ const text = formatEnvForAgentResult(makeEnv({ mode: "production", buildAllowed: true }));
139
+
140
+ expect(text).toContain("Mode: PRODUCTION");
141
+ expect(text).toContain("Build allowed: yes");
142
+ expect(text).not.toContain("Environment unknown");
143
+ });
144
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/cookie-sanitizer",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "pi-harness-runtime — cookie sanitizer (provider-quota cookie file normaliser)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/cost-optimizer",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Cost Optimizer for pi-harness-runtime — budget tracking, forecasting, and model selection optimization",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/dependency-analyzer",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/django-plugin",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Django framework analysis — models, management commands, settings",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/doc-generator",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Auto-Doc Generator — generate documentation from source code (RFC-0014)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/durable-review",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "SQLite-backed durable reviewer using @earendil-works/pi-durable",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/e2e-tester",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "WebMCP-powered E2E testing for pi-harness - natural language test scenarios",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/evaluation-engine",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Evaluation engine for pi-harness runtime (RFC-0057)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/evaluation-runner",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "description": "Framework-agnostic evaluation framework for AI outputs (RFC-0020)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/event-bus",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/event-sink",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "description": "Atomic event sourcing with SQLite transactions for pi-harness",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/event-store",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/experience-replay",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Experience replay for debugging and reproducibility (RFC-0059)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/feedback-collector",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "description": "Collect and aggregate human/automated feedback (RFC-0021)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/file-copy-helper",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Injects cp/sudo cp rule when user asks to mimic/copy files",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/framework-detector",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Framework detection system for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/framework-plugin-sdk",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Framework plugin SDK for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/frappe-plugin",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Frappe/ERPNext framework analysis — DocTypes, hooks, bench sites, site config",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/generic-web-plugin",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Generic web framework analysis — REST/GraphQL endpoints, OpenAPI, auth",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/health-monitor",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "description": "Health monitoring and auto-healing",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/intent-analyzer",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/jev-judge",
3
- "version": "1.1.387",
3
+ "version": "1.1.388",
4
4
  "description": "Jev Judge - Structured decision model for agent routing, E2E testing, and automated decisions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",