pi-harness-runtime 1.1.367 → 1.1.369

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 (116) hide show
  1. package/AGENTS.md +33 -0
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/agent-policy/dist/extension.d.ts.map +1 -1
  5. package/packages/agent-policy/dist/extension.js +15 -2
  6. package/packages/agent-policy/dist/extension.js.map +1 -1
  7. package/packages/agent-policy/package.json +1 -1
  8. package/packages/agent-policy/src/extension.ts +15 -2
  9. package/packages/architecture-generator/package.json +1 -1
  10. package/packages/auth/package.json +1 -1
  11. package/packages/autonomous-refactor/package.json +1 -1
  12. package/packages/autonomous-runtime/package.json +1 -1
  13. package/packages/cache-strategy/package.json +1 -1
  14. package/packages/capability-registry/package.json +1 -1
  15. package/packages/checkpoint/package.json +1 -1
  16. package/packages/cli-plugin-sdk/package.json +1 -1
  17. package/packages/clipboard/package.json +1 -1
  18. package/packages/clipboard-plugin/package.json +1 -1
  19. package/packages/code-generation/package.json +1 -1
  20. package/packages/code-review/package.json +1 -1
  21. package/packages/codex-adapter/package.json +1 -1
  22. package/packages/codex-watcher/package.json +1 -1
  23. package/packages/config-capture/package.json +1 -1
  24. package/packages/connection-runtime/package.json +1 -1
  25. package/packages/context-compiler/package.json +1 -1
  26. package/packages/context-discovery/package.json +1 -1
  27. package/packages/context-manager/package.json +1 -1
  28. package/packages/contracts/dist/workspace-env.d.ts +35 -14
  29. package/packages/contracts/dist/workspace-env.js +333 -132
  30. package/packages/contracts/package.json +1 -1
  31. package/packages/contracts/src/workspace-env.ts +411 -157
  32. package/packages/cookie-sanitizer/package.json +1 -1
  33. package/packages/cost-optimizer/package.json +1 -1
  34. package/packages/dependency-analyzer/package.json +1 -1
  35. package/packages/django-plugin/package.json +1 -1
  36. package/packages/doc-generator/package.json +1 -1
  37. package/packages/durable-review/package.json +1 -1
  38. package/packages/e2e-tester/package.json +1 -1
  39. package/packages/evaluation-engine/package.json +1 -1
  40. package/packages/evaluation-runner/package.json +1 -1
  41. package/packages/event-bus/package.json +1 -1
  42. package/packages/event-sink/package.json +1 -1
  43. package/packages/event-store/package.json +1 -1
  44. package/packages/experience-replay/package.json +1 -1
  45. package/packages/feedback-collector/package.json +1 -1
  46. package/packages/file-copy-helper/package.json +1 -1
  47. package/packages/framework-detector/package.json +1 -1
  48. package/packages/framework-plugin-sdk/package.json +1 -1
  49. package/packages/frappe-plugin/package.json +1 -1
  50. package/packages/generic-web-plugin/package.json +1 -1
  51. package/packages/health-monitor/package.json +1 -1
  52. package/packages/intent-analyzer/package.json +1 -1
  53. package/packages/jev-judge/package.json +1 -1
  54. package/packages/knowledge-graph/package.json +1 -1
  55. package/packages/knowledge-retrieval/package.json +1 -1
  56. package/packages/laravel-plugin/package.json +1 -1
  57. package/packages/learning-engine/package.json +1 -1
  58. package/packages/mcp-adapter/package.json +1 -1
  59. package/packages/memory-engine/package.json +1 -1
  60. package/packages/milestone-manager/package.json +1 -1
  61. package/packages/model-registry/package.json +1 -1
  62. package/packages/nextjs-plugin/package.json +1 -1
  63. package/packages/notification/package.json +1 -1
  64. package/packages/observability/package.json +1 -1
  65. package/packages/okf-indexer/package.json +1 -1
  66. package/packages/paperclip-integration/package.json +1 -1
  67. package/packages/performance-optimizer/package.json +1 -1
  68. package/packages/permission-mode/package.json +1 -1
  69. package/packages/privilege-broker/package.json +1 -1
  70. package/packages/project-analyzer/package.json +1 -1
  71. package/packages/project-bootstrap/package.json +1 -1
  72. package/packages/projection-engine/package.json +1 -1
  73. package/packages/prompt-compiler/package.json +1 -1
  74. package/packages/prompt-versioning/package.json +1 -1
  75. package/packages/provider-adapter-sdk/package.json +1 -1
  76. package/packages/provider-router/package.json +1 -1
  77. package/packages/provider-selector/package.json +1 -1
  78. package/packages/providers/package.json +1 -1
  79. package/packages/qdrant-skills/package.json +1 -1
  80. package/packages/quota-manager/package.json +1 -1
  81. package/packages/rate-limiter/package.json +1 -1
  82. package/packages/react-vite-plugin/package.json +1 -1
  83. package/packages/relay/package.json +1 -1
  84. package/packages/release-manager/package.json +1 -1
  85. package/packages/requirement-compiler/package.json +1 -1
  86. package/packages/runtime/package.json +1 -1
  87. package/packages/scheduler/package.json +1 -1
  88. package/packages/scheduler-adapter/package.json +1 -1
  89. package/packages/session/package.json +1 -1
  90. package/packages/session-api/package.json +1 -1
  91. package/packages/session-export/package.json +1 -1
  92. package/packages/shared-context/package.json +1 -1
  93. package/packages/skill-mcp-client/package.json +1 -1
  94. package/packages/skill-registry/package.json +1 -1
  95. package/packages/skills/package.json +1 -1
  96. package/packages/sprint-planner/package.json +1 -1
  97. package/packages/subscription-engine/package.json +1 -1
  98. package/packages/task-compiler/package.json +1 -1
  99. package/packages/telegram/package.json +1 -1
  100. package/packages/tencentdb-memory/package.json +1 -1
  101. package/packages/tencentdb-sync/package.json +1 -1
  102. package/packages/test-data-generator/package.json +1 -1
  103. package/packages/test-generator/package.json +1 -1
  104. package/packages/todo-bd-sync/package.json +1 -1
  105. package/packages/token-estimation/package.json +1 -1
  106. package/packages/token-optimizer/package.json +1 -1
  107. package/packages/trajectory/package.json +1 -1
  108. package/packages/tui/package.json +1 -1
  109. package/packages/types/package.json +1 -1
  110. package/packages/voice-api/package.json +1 -1
  111. package/packages/webmcp-tester/package.json +1 -1
  112. package/packages/wiki/package.json +1 -1
  113. package/packages/workflow-events/package.json +1 -1
  114. package/packages/workspace-scanner/package.json +1 -1
  115. package/packages/worktree/package.json +1 -1
  116. package/packages/write-review/package.json +1 -1
@@ -2,25 +2,24 @@
2
2
  * Workspace Environment Detector
3
3
  *
4
4
  * Detects if the workspace is a development environment by checking:
5
- * 1. .env file existence and NODE_ENV value
6
- * 2. package.json "dev" script presence
7
- * 3. .env.development or .env.local files
5
+ * 1. process.env.NODE_ENV (for build processes)
6
+ * 2. .env file existence and NODE_ENV value
7
+ * 3. package.json "dev" script presence
8
8
  *
9
9
  * Stores detected environment in SQLite for agent reference.
10
+ * Uses synchronous directory creation and exception-safe operations.
10
11
  */
11
12
  import { DatabaseSync } from "node:sqlite";
12
- import { existsSync, readFileSync } from "fs";
13
- import { join, dirname } from "path";
14
- import { fileURLToPath } from "url";
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ import { existsSync, readFileSync, mkdirSync } from "fs";
14
+ import { join } from "path";
15
+ import { platform, totalmem, freemem } from "os";
16
16
  const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
17
17
  /**
18
- * Ensure store directory exists
18
+ * Ensure store directory exists synchronously
19
19
  */
20
20
  function ensureStoreDir() {
21
21
  if (!existsSync(STORE_DIR)) {
22
- // mkdirSync is not available in all contexts, use fs promises or exec
23
- import("fs").then((fs) => fs.mkdirSync(STORE_DIR, { recursive: true }));
22
+ mkdirSync(STORE_DIR, { recursive: true });
24
23
  }
25
24
  }
26
25
  /**
@@ -34,15 +33,23 @@ function getDbPath() {
34
33
  * Initialize workspace environment database
35
34
  */
36
35
  function initDatabase() {
36
+ ensureStoreDir();
37
37
  const db = new DatabaseSync(getDbPath());
38
38
  db.exec(`
39
39
  CREATE TABLE IF NOT EXISTS workspace_env (
40
40
  workspace_path TEXT PRIMARY KEY,
41
- is_dev_env INTEGER NOT NULL DEFAULT 0,
41
+ mode TEXT NOT NULL DEFAULT 'unknown',
42
+ evidence TEXT NOT NULL DEFAULT '[]',
42
43
  has_dev_script INTEGER NOT NULL DEFAULT 0,
43
- node_env TEXT,
44
- has_env_file INTEGER NOT NULL DEFAULT 0,
45
- ram_gb REAL,
44
+ has_build_script INTEGER NOT NULL DEFAULT 0,
45
+ package_manager TEXT,
46
+ dev_server_running INTEGER NOT NULL DEFAULT 0,
47
+ build_server_running INTEGER NOT NULL DEFAULT 0,
48
+ ram_total_gb REAL,
49
+ ram_available_gb REAL,
50
+ ram_effective_limit_gb REAL,
51
+ low_ram_warning INTEGER NOT NULL DEFAULT 0,
52
+ build_allowed INTEGER NOT NULL DEFAULT -1,
46
53
  detected_at INTEGER NOT NULL,
47
54
  updated_at INTEGER NOT NULL
48
55
  )
@@ -50,107 +57,310 @@ function initDatabase() {
50
57
  return db;
51
58
  }
52
59
  /**
53
- * Detect RAM in GB
60
+ * Parse dotenv file without loading secrets
61
+ * Returns only NODE_ENV value, ignoring comments and other vars
54
62
  */
55
- function detectRamGb() {
63
+ function parseNodeEnvFromDotenv(content) {
64
+ const lines = content.split("\n");
65
+ for (const line of lines) {
66
+ // Skip empty lines and comments
67
+ const trimmed = line.trim();
68
+ if (!trimmed || trimmed.startsWith("#"))
69
+ continue;
70
+ // Match NODE_ENV= followed by value (handle quotes, spaces)
71
+ // Allow: NODE_ENV=development, NODE_ENV="production", NODE_ENV: development
72
+ const match = trimmed.match(/^NODE_ENV\s*[=:]\s*['"]?([^\s'";]+)['"]?/);
73
+ if (match) {
74
+ return match[1];
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ /**
80
+ * Detect RAM information
81
+ */
82
+ function detectRamInfo() {
56
83
  try {
57
- if (process.platform === "linux") {
58
- const meminfo = readFileSync("/proc/meminfo", "utf8");
59
- const match = meminfo.match(/MemTotal:\\s+(\\d+)\\s+kB/);
60
- if (match) {
61
- return Math.round((parseInt(match[1], 10) / 1024 / 1024) * 10) / 10;
62
- }
84
+ const totalBytes = totalmem();
85
+ const freeBytes = freemem();
86
+ const totalGb = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
87
+ const availableGb = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
88
+ const LOW_RAM_GB = 16;
89
+ return {
90
+ total: totalGb,
91
+ available: availableGb,
92
+ lowWarning: totalGb < LOW_RAM_GB || availableGb < 4,
93
+ };
94
+ }
95
+ catch {
96
+ return { total: null, available: null, lowWarning: false };
97
+ }
98
+ }
99
+ /**
100
+ * Check if dev/build server is running
101
+ */
102
+ function checkProcessRunning(pattern) {
103
+ try {
104
+ if (platform() === "linux") {
105
+ const { execSync } = require("child_process");
106
+ const out = execSync(`pgrep -f "${pattern}" 2>/dev/null || true`, {
107
+ encoding: "utf8",
108
+ timeout: 2000,
109
+ });
110
+ return out.trim().split("\n").filter(Boolean).length > 0;
63
111
  }
64
- else if (process.platform === "darwin") {
65
- const out = require("child_process").execSync("sysctl hw.memsize", { encoding: "utf8" });
66
- const match = out.match(/hw\\.memsize:\\s+(\\d+)/);
67
- if (match) {
68
- return Math.round((parseInt(match[1], 10) / 1024 / 1024 / 1024) * 10) / 10;
69
- }
112
+ else if (platform() === "darwin") {
113
+ const { execSync } = require("child_process");
114
+ const out = execSync(`pgrep -f "${pattern}" 2>/dev/null || true`, {
115
+ encoding: "utf8",
116
+ timeout: 2000,
117
+ });
118
+ return out.trim().split("\n").filter(Boolean).length > 0;
70
119
  }
71
120
  }
72
121
  catch {
73
- // Fallback: return null
122
+ // Process check failed - assume unknown
74
123
  }
124
+ return false;
125
+ }
126
+ /**
127
+ * Detect package manager from lock file
128
+ */
129
+ function detectPackageManager(workspacePath) {
130
+ if (existsSync(join(workspacePath, "pnpm-lock.yaml")))
131
+ return "pnpm";
132
+ if (existsSync(join(workspacePath, "yarn.lock")))
133
+ return "yarn";
134
+ if (existsSync(join(workspacePath, "package-lock.json")))
135
+ return "npm";
75
136
  return null;
76
137
  }
138
+ /**
139
+ * Determine workspace mode with proper precedence
140
+ * Never assume production - unknown requires investigation
141
+ */
142
+ function determineMode(processEnv, dotenvNodeEnv, evidence) {
143
+ // Explicit process.env takes highest priority (for build processes)
144
+ if (processEnv) {
145
+ const env = processEnv.toLowerCase();
146
+ if (env === "production") {
147
+ evidence.push({
148
+ source: "process.env",
149
+ key: "NODE_ENV",
150
+ value: env,
151
+ timestamp: Date.now(),
152
+ });
153
+ return "production";
154
+ }
155
+ if (env === "development" || env === "dev") {
156
+ evidence.push({
157
+ source: "process.env",
158
+ key: "NODE_ENV",
159
+ value: env,
160
+ timestamp: Date.now(),
161
+ });
162
+ return "development";
163
+ }
164
+ if (env === "test") {
165
+ evidence.push({
166
+ source: "process.env",
167
+ key: "NODE_ENV",
168
+ value: env,
169
+ timestamp: Date.now(),
170
+ });
171
+ return "test";
172
+ }
173
+ }
174
+ // Check dotenv file if present
175
+ if (dotenvNodeEnv) {
176
+ const env = dotenvNodeEnv.toLowerCase();
177
+ evidence.push({
178
+ source: ".env file",
179
+ key: "NODE_ENV",
180
+ value: env,
181
+ timestamp: Date.now(),
182
+ });
183
+ if (env === "production")
184
+ return "production";
185
+ if (env === "development" || env === "dev")
186
+ return "development";
187
+ if (env === "test")
188
+ return "test";
189
+ }
190
+ // Note: presence of dev script is a capability, not proof of mode
191
+ // Mode detection requires explicit NODE_ENV
192
+ return "unknown";
193
+ }
77
194
  /**
78
195
  * Detect and store workspace environment
79
196
  */
80
197
  export function detectWorkspaceEnv(workspacePath) {
81
198
  const now = Date.now();
82
- const ramGb = detectRamGb();
83
- // Check for .env files
84
- const hasEnvFile = existsSync(join(workspacePath, ".env")) ||
85
- existsSync(join(workspacePath, ".env.development")) ||
86
- existsSync(join(workspacePath, ".env.local")) ||
87
- existsSync(join(workspacePath, ".env.dev"));
88
- // Read NODE_ENV from .env file if present
89
- let nodeEnv = null;
90
- const envFile = join(workspacePath, ".env");
91
- if (existsSync(envFile)) {
92
- try {
93
- const envContent = readFileSync(envFile, "utf8");
94
- const match = envContent.match(/NODE_ENV=(.+)/);
95
- if (match) {
96
- nodeEnv = match[1].trim().replace(/['"]/g, "");
199
+ const evidence = [];
200
+ // Get process.env.NODE_ENV (for build process context)
201
+ const processNodeEnv = process.env.NODE_ENV;
202
+ // Detect RAM info
203
+ const ramInfo = detectRamInfo();
204
+ // Check for .env files and parse NODE_ENV
205
+ let dotenvNodeEnv = null;
206
+ const dotenvFiles = [
207
+ join(workspacePath, ".env"),
208
+ join(workspacePath, ".env.development"),
209
+ join(workspacePath, ".env.dev"),
210
+ ];
211
+ for (const envFile of dotenvFiles) {
212
+ if (existsSync(envFile)) {
213
+ try {
214
+ const content = readFileSync(envFile, "utf8");
215
+ dotenvNodeEnv = parseNodeEnvFromDotenv(content);
216
+ if (dotenvNodeEnv) {
217
+ evidence.push({
218
+ source: ".env file",
219
+ key: "NODE_ENV",
220
+ value: dotenvNodeEnv,
221
+ timestamp: now,
222
+ });
223
+ break;
224
+ }
225
+ }
226
+ catch {
227
+ // Ignore read errors
97
228
  }
98
- }
99
- catch {
100
- // Ignore read errors
101
229
  }
102
230
  }
103
- // Check for dev script in package.json
231
+ // Check for dev/build scripts in package.json
104
232
  let hasDevScript = false;
233
+ let hasBuildScript = false;
105
234
  const pkgJsonPath = join(workspacePath, "package.json");
106
235
  if (existsSync(pkgJsonPath)) {
107
236
  try {
108
237
  const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
109
- hasDevScript = !!(pkg.scripts && (pkg.scripts.dev || pkg.scripts.development));
238
+ hasDevScript = !!(pkg.scripts && (pkg.scripts.dev || pkg.scripts.development || pkg.scripts["dev:next"]));
239
+ hasBuildScript = !!(pkg.scripts && (pkg.scripts.build || pkg.scripts["build:next"]));
240
+ if (hasDevScript) {
241
+ evidence.push({
242
+ source: "package.json",
243
+ key: "scripts.dev",
244
+ value: "present",
245
+ timestamp: now,
246
+ });
247
+ }
110
248
  }
111
249
  catch {
112
250
  // Ignore parse errors
113
251
  }
114
252
  }
115
- // Determine if this is a dev environment
116
- // Dev env if: has .env file OR has dev script OR NODE_ENV=development
117
- const isDevEnv = hasEnvFile || hasDevScript || nodeEnv === "development" || nodeEnv === "dev";
118
- // Store in SQLite
119
- const db = initDatabase();
120
- db.prepare(`
121
- INSERT OR REPLACE INTO workspace_env
122
- (workspace_path, is_dev_env, has_dev_script, node_env, has_env_file, ram_gb, detected_at, updated_at)
123
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
124
- `).run(workspacePath, isDevEnv ? 1 : 0, hasDevScript ? 1 : 0, nodeEnv, hasEnvFile ? 1 : 0, ramGb, now, now);
125
- db.close();
126
- return {
253
+ // Detect package manager
254
+ const packageManager = detectPackageManager(workspacePath);
255
+ // Check for running dev/build processes
256
+ const devServerRunning = checkProcessRunning("(next dev|nuxt| vite |webpack|remix dev)");
257
+ const buildServerRunning = checkProcessRunning("(next build|nuxt build|webpack build)");
258
+ // Determine mode - NEVER assume production
259
+ const mode = determineMode(processNodeEnv, dotenvNodeEnv, evidence);
260
+ // Determine build permission
261
+ let buildAllowed = false;
262
+ if (mode === "production") {
263
+ buildAllowed = true; // Production build is allowed
264
+ }
265
+ else if (mode === "development") {
266
+ buildAllowed = !ramInfo.lowWarning; // Allow if not low RAM
267
+ }
268
+ else {
269
+ buildAllowed = false; // Unknown requires investigation
270
+ }
271
+ // Determine low RAM warning
272
+ const lowRamWarning = ramInfo.lowWarning;
273
+ // Build result
274
+ const result = {
127
275
  workspacePath,
128
- isDevEnv,
276
+ mode,
277
+ evidence,
129
278
  hasDevScript,
130
- nodeEnv,
131
- hasEnvFile,
132
- ramGb,
279
+ hasBuildScript,
280
+ packageManager,
281
+ devServerRunning,
282
+ buildServerRunning,
283
+ ramTotalGb: ramInfo.total,
284
+ ramAvailableGb: ramInfo.available,
285
+ ramEffectiveLimitGb: ramInfo.total, // Could be enhanced with cgroup detection
286
+ lowRamWarning,
287
+ buildAllowed,
133
288
  detectedAt: now,
134
289
  };
290
+ // Store in SQLite (synchronously, exception-safe)
291
+ let db = null;
292
+ try {
293
+ db = initDatabase();
294
+ db.prepare(`
295
+ INSERT OR REPLACE INTO workspace_env
296
+ (workspace_path, mode, evidence, has_dev_script, has_build_script, package_manager,
297
+ dev_server_running, build_server_running, ram_total_gb, ram_available_gb,
298
+ ram_effective_limit_gb, low_ram_warning, build_allowed, detected_at, updated_at)
299
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
300
+ `).run(workspacePath, mode, JSON.stringify(evidence), hasDevScript ? 1 : 0, hasBuildScript ? 1 : 0, packageManager, devServerRunning ? 1 : 0, buildServerRunning ? 1 : 0, ramInfo.total, ramInfo.available, ramInfo.total, lowRamWarning ? 1 : 0, buildAllowed ? 1 : 0, now, now);
301
+ }
302
+ catch (err) {
303
+ // SQLite failure - continue with in-memory result
304
+ console.error("[WorkspaceEnv] SQLite persistence failed:", err);
305
+ }
306
+ finally {
307
+ if (db) {
308
+ try {
309
+ db.close();
310
+ }
311
+ catch {
312
+ // Ignore close errors
313
+ }
314
+ }
315
+ }
316
+ return result;
135
317
  }
136
318
  /**
137
319
  * Load cached workspace environment
138
320
  */
139
321
  export function loadWorkspaceEnv(workspacePath) {
140
- const db = initDatabase();
141
- const row = db.prepare("SELECT * FROM workspace_env WHERE workspace_path = ?").get(workspacePath);
142
- db.close();
143
- if (!row)
322
+ let db = null;
323
+ try {
324
+ db = initDatabase();
325
+ const row = db.prepare("SELECT * FROM workspace_env WHERE workspace_path = ?").get(workspacePath);
326
+ if (!row)
327
+ return null;
328
+ // Check cache freshness (5 minutes for dev workspaces)
329
+ const ageMs = Date.now() - row.detected_at;
330
+ const MAX_CACHE_AGE_MS = 5 * 60 * 1000; // 5 minutes
331
+ if (ageMs > MAX_CACHE_AGE_MS) {
332
+ return null; // Cache stale
333
+ }
334
+ return {
335
+ workspacePath: row.workspace_path,
336
+ mode: row.mode,
337
+ evidence: JSON.parse(row.evidence || "[]"),
338
+ hasDevScript: row.has_dev_script === 1,
339
+ hasBuildScript: row.has_build_script === 1,
340
+ packageManager: row.package_manager,
341
+ devServerRunning: row.dev_server_running === 1,
342
+ buildServerRunning: row.build_server_running === 1,
343
+ ramTotalGb: row.ram_total_gb,
344
+ ramAvailableGb: row.ram_available_gb,
345
+ ramEffectiveLimitGb: row.ram_effective_limit_gb,
346
+ lowRamWarning: row.low_ram_warning === 1,
347
+ buildAllowed: row.build_allowed === 1,
348
+ detectedAt: row.detected_at,
349
+ };
350
+ }
351
+ catch {
144
352
  return null;
145
- return {
146
- workspacePath: row.workspace_path,
147
- isDevEnv: row.is_dev_env === 1,
148
- hasDevScript: row.has_dev_script === 1,
149
- nodeEnv: row.node_env,
150
- hasEnvFile: row.has_env_file === 1,
151
- ramGb: row.ram_gb,
152
- detectedAt: row.detected_at,
153
- };
353
+ }
354
+ finally {
355
+ if (db) {
356
+ try {
357
+ db.close();
358
+ }
359
+ catch {
360
+ // Ignore close errors
361
+ }
362
+ }
363
+ }
154
364
  }
155
365
  /**
156
366
  * Get or detect workspace environment
@@ -159,73 +369,64 @@ export function getWorkspaceEnv(workspacePath) {
159
369
  // Try to load cached result first
160
370
  const cached = loadWorkspaceEnv(workspacePath);
161
371
  if (cached) {
162
- // Check if cache is recent (within 1 hour)
163
- const ageMs = Date.now() - cached.detectedAt;
164
- if (ageMs < 60 * 60 * 1000) {
165
- return cached;
166
- }
372
+ return cached;
167
373
  }
168
374
  // Detect fresh
169
375
  return detectWorkspaceEnv(workspacePath);
170
376
  }
171
377
  /**
172
- * Check if agent should prefer dev mode (no build)
378
+ * Revalidate workspace environment before impactful commands
173
379
  */
174
- export function shouldUseDevMode(workspacePath) {
175
- const env = getWorkspaceEnv(workspacePath);
176
- // Low RAM threshold
177
- const LOW_RAM_GB = 16;
178
- if (!env.isDevEnv) {
179
- return {
180
- preferDev: false,
181
- reason: "Not detected as dev environment",
182
- ramGb: env.ramGb,
183
- };
184
- }
185
- if (env.ramGb !== null && env.ramGb < LOW_RAM_GB) {
186
- return {
187
- preferDev: true,
188
- reason: `Low RAM (${env.ramGb}GB < ${LOW_RAM_GB}GB) - avoid parallel build + dev`,
189
- ramGb: env.ramGb,
190
- };
191
- }
192
- if (env.hasDevScript) {
193
- return {
194
- preferDev: true,
195
- reason: `Dev script detected - hot reload available`,
196
- ramGb: env.ramGb,
197
- };
198
- }
199
- return {
200
- preferDev: false,
201
- reason: "Dev environment but no dev script found",
202
- ramGb: env.ramGb,
203
- };
380
+ export function revalidateWorkspaceEnv(workspacePath) {
381
+ // Force fresh detection for command decisions
382
+ return detectWorkspaceEnv(workspacePath);
204
383
  }
205
384
  /**
206
385
  * Format workspace env as agent-readable message
207
386
  */
208
387
  export function formatEnvForAgent(workspacePath) {
209
- const env = shouldUseDevMode(workspacePath);
210
- const details = getWorkspaceEnv(workspacePath);
388
+ const env = getWorkspaceEnv(workspacePath);
211
389
  let message = `[Workspace Environment]\n`;
212
- message += `- Path: ${workspacePath}\n`;
213
- message += `- Detected: ${details.isDevEnv ? "Development" : "Production/Unknown"}\n`;
214
- message += `- Has dev script: ${details.hasDevScript ? "Yes" : "No"}\n`;
215
- message += `- NODE_ENV: ${details.nodeEnv || "not set"}\n`;
216
- message += `- Has .env file: ${details.hasEnvFile ? "Yes" : "No"}\n`;
217
- message += `- RAM: ${details.ramGb ? `${details.ramGb}GB` : "unknown"}\n`;
218
- message += `\n`;
219
- if (env.preferDev) {
220
- message += `RECOMMENDATION: Use \`dev\` mode (no build needed)\n`;
221
- message += `Reason: ${env.reason}\n`;
222
- message += `→ Run: \`bun run dev\` (or \`npm run dev\`)\n`;
223
- message += `→ DON'T: Run \`build\` unless explicitly requested\n`;
390
+ message += `Mode: ${env.mode.toUpperCase()}\n`;
391
+ if (env.evidence.length > 0) {
392
+ const sources = env.evidence.map((e) => `${e.source}:${e.value}`).join(", ");
393
+ message += `Evidence: ${sources}\n`;
224
394
  }
225
- else {
226
- message += `RECOMMENDATION: Build may be needed\n`;
227
- message += `Reason: ${env.reason}\n`;
228
- message += `→ Use: \`bun run build\` before running\n`;
395
+ message += `Dev script: ${env.hasDevScript ? "yes" : "no"}\n`;
396
+ message += `Build script: ${env.hasBuildScript ? "yes" : "no"}\n`;
397
+ if (env.devServerRunning) {
398
+ message += `Dev server: running\n`;
399
+ }
400
+ if (env.buildServerRunning) {
401
+ message += `Build server: running\n`;
402
+ }
403
+ if (env.ramTotalGb !== null) {
404
+ const ramStr = env.ramAvailableGb !== null
405
+ ? `${env.ramAvailableGb}/${env.ramTotalGb}GB available`
406
+ : `${env.ramTotalGb}GB total`;
407
+ message += `RAM: ${ramStr}\n`;
408
+ if (env.lowRamWarning) {
409
+ message += `WARNING: Low RAM - avoid parallel build + dev\n`;
410
+ }
411
+ }
412
+ message += `\n`;
413
+ message += `Build allowed: ${env.buildAllowed ? "yes" : "no - requires explicit request"}\n`;
414
+ if (env.mode === "unknown") {
415
+ message += `\nNOTE: Environment unknown. Explicitly ask user before build.\n`;
229
416
  }
230
417
  return message;
231
418
  }
419
+ /**
420
+ * Format workspace env as compact status line for UI
421
+ */
422
+ export function formatEnvStatusLine(env) {
423
+ const mode = env.mode.toUpperCase();
424
+ const devStatus = env.devServerRunning ? "running" : (env.hasDevScript ? "available" : "none");
425
+ const buildStatus = env.buildServerRunning ? "running" : (env.hasBuildScript ? "explicit request" : "none");
426
+ const ramStr = env.ramAvailableGb !== null ? `${env.ramAvailableGb}/${env.ramTotalGb}` : "";
427
+ let status = `Workspace: ${mode} | dev server: ${devStatus} | build: ${buildStatus}`;
428
+ if (ramStr) {
429
+ status += ` | RAM: ${ramStr}`;
430
+ }
431
+ return status;
432
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/contracts",
3
- "version": "1.1.367",
3
+ "version": "1.1.369",
4
4
  "type": "module",
5
5
  "description": "RPC schema definitions for pi-harness runtime",
6
6
  "main": "./dist/index.js",