hungry-ghost-hive 0.49.0 → 0.50.0

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 (52) hide show
  1. package/dist/cli/commands/init.d.ts.map +1 -1
  2. package/dist/cli/commands/init.js +16 -6
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/cli/commands/manager/tech-lead-lifecycle.d.ts.map +1 -1
  5. package/dist/cli/commands/manager/tech-lead-lifecycle.js +9 -1
  6. package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
  7. package/dist/cli/commands/nuke.d.ts.map +1 -1
  8. package/dist/cli/commands/nuke.js +7 -7
  9. package/dist/cli/commands/nuke.js.map +1 -1
  10. package/dist/cli/commands/nuke.test.js +36 -24
  11. package/dist/cli/commands/nuke.test.js.map +1 -1
  12. package/dist/cli/commands/req.d.ts +1 -1
  13. package/dist/cli/commands/req.d.ts.map +1 -1
  14. package/dist/cli/commands/req.js +11 -3
  15. package/dist/cli/commands/req.js.map +1 -1
  16. package/dist/cli/dashboard/index.d.ts.map +1 -1
  17. package/dist/cli/dashboard/index.js +35 -8
  18. package/dist/cli/dashboard/index.js.map +1 -1
  19. package/dist/cli/dashboard/panels/activity.js +4 -1
  20. package/dist/cli/dashboard/panels/activity.js.map +1 -1
  21. package/dist/cli/dashboard/panels/stories.d.ts +3 -3
  22. package/dist/cli/dashboard/panels/stories.d.ts.map +1 -1
  23. package/dist/cli/dashboard/panels/stories.js +1 -2
  24. package/dist/cli/dashboard/panels/stories.js.map +1 -1
  25. package/dist/db/postgres-provider.d.ts +1 -1
  26. package/dist/db/postgres-provider.d.ts.map +1 -1
  27. package/dist/db/postgres-provider.js +118 -11
  28. package/dist/db/postgres-provider.js.map +1 -1
  29. package/dist/db/queries/logs.d.ts.map +1 -1
  30. package/dist/db/queries/logs.js +24 -7
  31. package/dist/db/queries/logs.js.map +1 -1
  32. package/dist/db/queries/requirements.js +3 -3
  33. package/dist/db/queries/requirements.js.map +1 -1
  34. package/dist/orchestrator/scheduler.js +1 -1
  35. package/dist/orchestrator/scheduler.js.map +1 -1
  36. package/dist/utils/with-hive-context.d.ts.map +1 -1
  37. package/dist/utils/with-hive-context.js +6 -2
  38. package/dist/utils/with-hive-context.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/cli/commands/init.ts +23 -6
  41. package/src/cli/commands/manager/tech-lead-lifecycle.ts +10 -1
  42. package/src/cli/commands/nuke.test.ts +39 -24
  43. package/src/cli/commands/nuke.ts +12 -15
  44. package/src/cli/commands/req.ts +13 -3
  45. package/src/cli/dashboard/index.ts +38 -8
  46. package/src/cli/dashboard/panels/activity.ts +5 -2
  47. package/src/cli/dashboard/panels/stories.ts +8 -6
  48. package/src/db/postgres-provider.ts +130 -11
  49. package/src/db/queries/logs.ts +27 -10
  50. package/src/db/queries/requirements.ts +3 -3
  51. package/src/orchestrator/scheduler.ts +1 -1
  52. package/src/utils/with-hive-context.ts +6 -2
@@ -3,10 +3,12 @@
3
3
  import blessed from 'blessed';
4
4
  import { appendFileSync, existsSync, renameSync, statSync } from 'fs';
5
5
  import { join } from 'path';
6
+ import { loadConfig } from '../../config/loader.js';
6
7
  import { getReadOnlyDatabase, type ReadOnlyDatabaseClient } from '../../db/client.js';
8
+ import { createPostgresProvider } from '../../db/postgres-provider.js';
7
9
  import type { DatabaseProvider } from '../../db/provider.js';
8
10
  import { getAllRequirements } from '../../db/queries/requirements.js';
9
- import { findHiveRoot, getHivePaths } from '../../utils/paths.js';
11
+ import { findHiveRoot, getHivePaths, getWorkspaceId } from '../../utils/paths.js';
10
12
  import { getVersion } from '../../utils/version.js';
11
13
  import { createActivityPanel, updateActivityPanel } from './panels/activity.js';
12
14
  import { createAgentsPanel, updateAgentsPanel } from './panels/agents.js';
@@ -56,11 +58,39 @@ export async function startDashboard(options: DashboardOptions = {}): Promise<vo
56
58
 
57
59
  const paths = getHivePaths(root);
58
60
  const dbPath = join(paths.hiveDir, 'hive.db');
59
- const isDistributed = !existsSync(dbPath);
61
+ let isDistributed = false;
62
+ try {
63
+ const config = loadConfig(paths.hiveDir);
64
+ isDistributed = config.distributed === true;
65
+ } catch {
66
+ // fallback: not distributed
67
+ }
60
68
  debugLog(
61
69
  `Dashboard starting - root: ${root}, hiveDir: ${paths.hiveDir}, distributed: ${isDistributed}`
62
70
  );
63
- let db: ReadOnlyDatabaseClient = await getReadOnlyDatabase(paths.hiveDir);
71
+
72
+ async function createDbClient(): Promise<ReadOnlyDatabaseClient> {
73
+ if (isDistributed) {
74
+ const workspaceId = getWorkspaceId(paths);
75
+ if (!workspaceId) {
76
+ throw new Error(
77
+ 'Distributed mode but workspace.id is missing. Re-run "hive init --distributed".'
78
+ );
79
+ }
80
+ const envPath = join(root!, '.env');
81
+ const provider = await createPostgresProvider(workspaceId, envPath);
82
+ return {
83
+ db: null as never,
84
+ provider,
85
+ close: () => {
86
+ provider.close();
87
+ },
88
+ };
89
+ }
90
+ return getReadOnlyDatabase(paths.hiveDir);
91
+ }
92
+
93
+ let db: ReadOnlyDatabaseClient = await createDbClient();
64
94
  let lastDbMtime = isDistributed ? 0 : statSync(dbPath).mtimeMs;
65
95
  const refreshInterval = options.refreshInterval || 5000;
66
96
  const version = getVersion();
@@ -99,7 +129,7 @@ export async function startDashboard(options: DashboardOptions = {}): Promise<vo
99
129
 
100
130
  // Create panels
101
131
  const agentsPanel = createAgentsPanel(screen, db.provider, pauseRefresh, resumeRefresh);
102
- const storiesPanel = createStoriesPanel(screen, db.db);
132
+ const storiesPanel = createStoriesPanel(screen, db.provider);
103
133
  const pipelinePanel = createPipelinePanel(screen, db.provider);
104
134
  const activityPanel = createActivityPanel(screen, db.provider);
105
135
  const mergeQueuePanel = createMergeQueuePanel(screen, db.provider);
@@ -144,9 +174,9 @@ export async function startDashboard(options: DashboardOptions = {}): Promise<vo
144
174
 
145
175
  if (shouldReload) {
146
176
  debugLog(`Database changed - reloading from ${paths.hiveDir}`);
147
- const newDb = await getReadOnlyDatabase(paths.hiveDir);
177
+ const newDb = await createDbClient();
148
178
  try {
149
- db.db.close();
179
+ db.close();
150
180
  } catch (_error) {
151
181
  /* ignore close errors */
152
182
  }
@@ -161,7 +191,7 @@ export async function startDashboard(options: DashboardOptions = {}): Promise<vo
161
191
  );
162
192
 
163
193
  await updateAgentsPanel(agentsPanel, db.provider);
164
- await updateStoriesPanel(storiesPanel, db.db);
194
+ await updateStoriesPanel(storiesPanel, db.provider);
165
195
  await updatePipelinePanel(pipelinePanel, db.provider);
166
196
  await updateActivityPanel(activityPanel, db.provider);
167
197
  await updateMergeQueuePanel(mergeQueuePanel, db.provider);
@@ -191,7 +221,7 @@ export async function startDashboard(options: DashboardOptions = {}): Promise<vo
191
221
  screen.key(['q', 'C-c'], () => {
192
222
  if (currentTimeout) clearTimeout(currentTimeout);
193
223
  try {
194
- db.db.close();
224
+ db.close();
195
225
  } catch (_error) {
196
226
  /* ignore */
197
227
  }
@@ -60,8 +60,11 @@ export async function updateActivityPanel(
60
60
  box.setScrollPerc(100);
61
61
  }
62
62
 
63
- function formatTimestamp(timestamp: string): string {
64
- return timestamp.substring(11, 19);
63
+ function formatTimestamp(timestamp: string | Date): string {
64
+ if (timestamp instanceof Date) {
65
+ return timestamp.toISOString().substring(11, 19);
66
+ }
67
+ return String(timestamp).substring(11, 19);
65
68
  }
66
69
 
67
70
  function formatEventType(event: string): string {
@@ -1,10 +1,13 @@
1
1
  // Licensed under the Hungry Ghost Hive License. See LICENSE.
2
2
 
3
3
  import blessed, { type Widgets } from 'blessed';
4
- import type { Database } from 'sql.js';
5
- import { queryAll, type StoryRow } from '../../../db/client.js';
4
+ import type { StoryRow } from '../../../db/client.js';
5
+ import type { DatabaseProvider } from '../../../db/provider.js';
6
6
 
7
- export function createStoriesPanel(screen: Widgets.Screen, db: Database): Widgets.ListTableElement {
7
+ export function createStoriesPanel(
8
+ screen: Widgets.Screen,
9
+ db: DatabaseProvider
10
+ ): Widgets.ListTableElement {
8
11
  const table = blessed.listtable({
9
12
  parent: screen,
10
13
  top: '30%',
@@ -32,10 +35,9 @@ export function createStoriesPanel(screen: Widgets.Screen, db: Database): Widget
32
35
 
33
36
  export async function updateStoriesPanel(
34
37
  table: Widgets.ListTableElement,
35
- db: Database
38
+ db: DatabaseProvider
36
39
  ): Promise<void> {
37
- const stories = queryAll<StoryRow>(
38
- db,
40
+ const stories = await db.queryAll<StoryRow>(
39
41
  `
40
42
  SELECT * FROM stories
41
43
  ORDER BY
@@ -83,6 +83,66 @@ function detectTable(sql: string): string | null {
83
83
  return null;
84
84
  }
85
85
 
86
+ /**
87
+ * Detect the alias (or table name) for the primary table in a SQL statement.
88
+ * For `SELECT ... FROM stories s LEFT JOIN ...`, returns "s".
89
+ * For `SELECT ... FROM stories LEFT JOIN ...`, returns "stories".
90
+ * For UPDATE/DELETE, returns the table name or alias.
91
+ */
92
+ function detectTableQualifier(sql: string): string | null {
93
+ const normalized = sql.replace(/\s+/g, ' ').trim();
94
+
95
+ // SELECT ... FROM table [alias] [JOIN|WHERE|ORDER|GROUP|HAVING|LIMIT|,|)]
96
+ const selectMatch = normalized.match(/FROM\s+(\w+)(?:\s+(?:AS\s+)?(\w+))?/i);
97
+ if (selectMatch) {
98
+ const alias = selectMatch[2];
99
+ const table = selectMatch[1];
100
+ // Only treat as alias if the next word is not a SQL keyword
101
+ if (alias) {
102
+ const upper = alias.toUpperCase();
103
+ const keywords = new Set([
104
+ 'WHERE',
105
+ 'LEFT',
106
+ 'RIGHT',
107
+ 'INNER',
108
+ 'OUTER',
109
+ 'CROSS',
110
+ 'JOIN',
111
+ 'ON',
112
+ 'ORDER',
113
+ 'GROUP',
114
+ 'HAVING',
115
+ 'LIMIT',
116
+ 'OFFSET',
117
+ 'UNION',
118
+ 'EXCEPT',
119
+ 'INTERSECT',
120
+ 'FOR',
121
+ 'SET',
122
+ 'VALUES',
123
+ ]);
124
+ if (!keywords.has(upper)) {
125
+ return alias;
126
+ }
127
+ }
128
+ return table;
129
+ }
130
+
131
+ // UPDATE table [alias]
132
+ const updateMatch = normalized.match(/UPDATE\s+(\w+)(?:\s+(?:AS\s+)?(\w+))?/i);
133
+ if (updateMatch) {
134
+ return updateMatch[2] || updateMatch[1];
135
+ }
136
+
137
+ // DELETE FROM table [alias]
138
+ const deleteMatch = normalized.match(/DELETE\s+FROM\s+(\w+)(?:\s+(?:AS\s+)?(\w+))?/i);
139
+ if (deleteMatch) {
140
+ return deleteMatch[2] || deleteMatch[1];
141
+ }
142
+
143
+ return null;
144
+ }
145
+
86
146
  /**
87
147
  * Check if a SQL statement needs workspace_id injection.
88
148
  */
@@ -123,6 +183,8 @@ function injectInsertWorkspaceId(
123
183
  /**
124
184
  * Inject workspace_id into SELECT/UPDATE/DELETE WHERE clauses.
125
185
  * Adds `AND workspace_id = ?` to existing WHERE, or `WHERE workspace_id = ?` if none.
186
+ * When the query uses JOINs, qualifies workspace_id with the primary table alias
187
+ * to avoid ambiguous column references.
126
188
  */
127
189
  function injectWhereWorkspaceId(
128
190
  sql: string,
@@ -136,18 +198,46 @@ function injectWhereWorkspaceId(
136
198
  return { sql, params };
137
199
  }
138
200
 
201
+ // Determine if we need to qualify workspace_id (JOIN queries have ambiguous columns)
202
+ const hasJoin = /\bJOIN\b/i.test(normalized);
203
+ const qualifier = hasJoin ? detectTableQualifier(sql) : null;
204
+ const wsCol = qualifier ? `${qualifier}.workspace_id` : 'workspace_id';
205
+
139
206
  const upperSql = normalized.toUpperCase();
140
207
 
141
208
  // Find WHERE clause position
142
209
  const whereIndex = upperSql.indexOf(' WHERE ');
143
210
 
144
211
  if (whereIndex !== -1) {
145
- // Insert workspace_id condition right after WHERE
212
+ // Append workspace_id condition at the end of the WHERE clause.
213
+ // We must NOT prepend because params are positional — SET clause params
214
+ // come before WHERE clause params, and prepending would shift them.
146
215
  const before = normalized.substring(0, whereIndex + 7); // includes " WHERE "
147
216
  const after = normalized.substring(whereIndex + 7);
217
+
218
+ // Find any trailing clauses (ORDER BY, GROUP BY, etc.) after the WHERE
219
+ const upperAfter = after.toUpperCase();
220
+ const trailingPatterns = [
221
+ ' ORDER BY',
222
+ ' GROUP BY',
223
+ ' HAVING',
224
+ ' LIMIT',
225
+ ' OFFSET',
226
+ ' FOR UPDATE',
227
+ ];
228
+ let trailingPos = after.length;
229
+ for (const pattern of trailingPatterns) {
230
+ const idx = upperAfter.indexOf(pattern);
231
+ if (idx !== -1 && idx < trailingPos) {
232
+ trailingPos = idx;
233
+ }
234
+ }
235
+
236
+ const whereBody = after.substring(0, trailingPos);
237
+ const trailing = after.substring(trailingPos);
148
238
  return {
149
- sql: `${before}workspace_id = ? AND ${after}`,
150
- params: [workspaceId, ...params],
239
+ sql: `${before}${whereBody} AND ${wsCol} = ?${trailing}`,
240
+ params: [...params, workspaceId],
151
241
  };
152
242
  }
153
243
 
@@ -163,9 +253,16 @@ function injectWhereWorkspaceId(
163
253
 
164
254
  const before = normalized.substring(0, insertPos);
165
255
  const after = normalized.substring(insertPos);
256
+
257
+ // Count how many ? appear before the insertion point to determine where
258
+ // in the params array the workspace_id value should be spliced in.
259
+ const questionsBefore = (before.match(/\?/g) || []).length;
260
+ const newParams = [...params];
261
+ newParams.splice(questionsBefore, 0, workspaceId);
262
+
166
263
  return {
167
- sql: `${before} WHERE workspace_id = ?${after}`,
168
- params: [workspaceId, ...params],
264
+ sql: `${before} WHERE ${wsCol} = ?${after}`,
265
+ params: newParams,
169
266
  };
170
267
  }
171
268
 
@@ -191,6 +288,17 @@ function loadPgMigration(migrationName: string): string {
191
288
 
192
289
  const PG_MIGRATIONS = [{ name: '001-full-schema.sql' }];
193
290
 
291
+ /**
292
+ * Strip SQLite-specific syntax that Postgres does not understand.
293
+ */
294
+ function sanitizeForPostgres(sql: string): string {
295
+ // Remove COLLATE NOCASE (SQLite case-insensitive collation)
296
+ let result = sql.replace(/\s+COLLATE\s+NOCASE/gi, '');
297
+ // Convert INSERT OR IGNORE to INSERT ... ON CONFLICT DO NOTHING
298
+ result = result.replace(/INSERT\s+OR\s+IGNORE\s+INTO/gi, 'INSERT INTO');
299
+ return result;
300
+ }
301
+
194
302
  /**
195
303
  * Postgres implementation of DatabaseProvider using node-postgres (pg).
196
304
  * All queries are automatically scoped by workspace_id for multi-tenant isolation.
@@ -242,12 +350,16 @@ export class PostgresProvider implements WritableDatabaseProvider {
242
350
  }
243
351
 
244
352
  async queryAll<T>(sql: string, params: unknown[] = []): Promise<T[]> {
245
- let finalSql = sql;
353
+ let finalSql = sanitizeForPostgres(sql);
246
354
  let finalParams = params;
247
355
 
248
356
  if (needsWorkspaceScope(sql)) {
249
357
  const normalized = sql.replace(/\s+/g, ' ').trim().toUpperCase();
250
- if (!normalized.startsWith('INSERT')) {
358
+ if (normalized.startsWith('INSERT')) {
359
+ const result = injectInsertWorkspaceId(finalSql, this.workspaceId, finalParams);
360
+ finalSql = result.sql;
361
+ finalParams = result.params;
362
+ } else {
251
363
  const result = injectWhereWorkspaceId(finalSql, this.workspaceId, finalParams);
252
364
  finalSql = result.sql;
253
365
  finalParams = result.params;
@@ -265,7 +377,7 @@ export class PostgresProvider implements WritableDatabaseProvider {
265
377
  }
266
378
 
267
379
  async run(sql: string, params: unknown[] = []): Promise<void> {
268
- let finalSql = sql;
380
+ let finalSql = sanitizeForPostgres(sql);
269
381
  let finalParams = params;
270
382
 
271
383
  if (needsWorkspaceScope(sql)) {
@@ -340,11 +452,18 @@ export class PostgresProvider implements WritableDatabaseProvider {
340
452
  * Create a PostgresProvider from the HIVE_DATABASE_URL environment variable.
341
453
  * Loads .env file via dotenv if available.
342
454
  */
343
- export async function createPostgresProvider(workspaceId: string): Promise<PostgresProvider> {
344
- // Load .env file if dotenv is available
455
+ export async function createPostgresProvider(
456
+ workspaceId: string,
457
+ envPath?: string
458
+ ): Promise<PostgresProvider> {
459
+ // Load .env file if dotenv is available — use workspace root if provided
345
460
  try {
346
461
  const dotenv = await import('dotenv');
347
- dotenv.config();
462
+ if (envPath) {
463
+ dotenv.config({ path: envPath });
464
+ } else {
465
+ dotenv.config();
466
+ }
348
467
  } catch {
349
468
  // dotenv not available, rely on environment variables
350
469
  }
@@ -92,6 +92,17 @@ function inferAgentType(
92
92
  }
93
93
 
94
94
  async function getAgentColumnNames(provider: DatabaseProvider): Promise<Set<string>> {
95
+ try {
96
+ // Try Postgres information_schema first
97
+ const pgRows = await provider.queryAll<{ column_name: string }>(
98
+ "SELECT column_name FROM information_schema.columns WHERE table_name = 'agents'"
99
+ );
100
+ if (pgRows.length > 0) {
101
+ return new Set(pgRows.map(r => r.column_name));
102
+ }
103
+ } catch {
104
+ // Fall back to SQLite PRAGMA
105
+ }
95
106
  const rows = await provider.queryAll<{ name: string }>('PRAGMA table_info(agents)');
96
107
  const columnNames = new Set<string>();
97
108
  for (const row of rows) {
@@ -133,13 +144,18 @@ async function ensureLogAgentExists(provider: DatabaseProvider, agentId: string)
133
144
  }
134
145
 
135
146
  const placeholders = insertColumns.map(() => '?').join(', ');
136
- await provider.run(
137
- `
138
- INSERT OR IGNORE INTO agents (${insertColumns.join(', ')})
139
- VALUES (${placeholders})
140
- `,
141
- insertValues
142
- );
147
+ try {
148
+ await provider.run(
149
+ `
150
+ INSERT INTO agents (${insertColumns.join(', ')})
151
+ VALUES (${placeholders})
152
+ ON CONFLICT DO NOTHING
153
+ `,
154
+ insertValues
155
+ );
156
+ } catch {
157
+ // Ignore insert conflicts — agent row already exists
158
+ }
143
159
  }
144
160
 
145
161
  async function resolveLogAgentId(provider: DatabaseProvider, rawAgentId: string): Promise<string> {
@@ -188,10 +204,13 @@ export async function createLog(
188
204
  const resolvedAgentId = await resolveLogAgentId(provider, input.agentId);
189
205
  const resolvedStoryId = await resolveLogStoryId(provider, input.storyId);
190
206
 
191
- await provider.run(
207
+ // Use queryOne with RETURNING to get the inserted id in a single statement.
208
+ // RETURNING is supported by both SQLite (3.35+) and Postgres.
209
+ const result = await provider.queryOne<{ id: number }>(
192
210
  `
193
211
  INSERT INTO agent_logs (agent_id, story_id, event_type, status, message, metadata, timestamp)
194
212
  VALUES (?, ?, ?, ?, ?, ?, ?)
213
+ RETURNING id
195
214
  `,
196
215
  [
197
216
  resolvedAgentId,
@@ -204,8 +223,6 @@ export async function createLog(
204
223
  ]
205
224
  );
206
225
 
207
- // Get the last inserted row
208
- const result = await provider.queryOne<{ id: number }>('SELECT last_insert_rowid() as id');
209
226
  return (await getLogById(provider, result?.id || 0))!;
210
227
  }
211
228
 
@@ -77,7 +77,7 @@ export async function getRequirementById(
77
77
 
78
78
  export async function getAllRequirements(provider: DatabaseProvider): Promise<RequirementRow[]> {
79
79
  return await provider.queryAll<RequirementRow>(
80
- 'SELECT * FROM requirements ORDER BY created_at DESC, rowid DESC'
80
+ 'SELECT * FROM requirements ORDER BY created_at DESC, id DESC'
81
81
  );
82
82
  }
83
83
 
@@ -86,7 +86,7 @@ export async function getRequirementsByStatus(
86
86
  status: RequirementStatus
87
87
  ): Promise<RequirementRow[]> {
88
88
  return await provider.queryAll<RequirementRow>(
89
- 'SELECT * FROM requirements WHERE status = ? ORDER BY created_at DESC, rowid DESC',
89
+ 'SELECT * FROM requirements WHERE status = ? ORDER BY created_at DESC, id DESC',
90
90
  [status]
91
91
  );
92
92
  }
@@ -97,7 +97,7 @@ export async function getPendingRequirements(
97
97
  return await provider.queryAll<RequirementRow>(`
98
98
  SELECT * FROM requirements
99
99
  WHERE status IN ('pending', 'planning', 'in_progress')
100
- ORDER BY created_at, rowid
100
+ ORDER BY created_at, id
101
101
  `);
102
102
  }
103
103
 
@@ -1143,7 +1143,7 @@ export class Scheduler {
1143
1143
  */
1144
1144
  private async isGodmodeActive(): Promise<boolean> {
1145
1145
  const activeRequirements = await this.provider.queryAll<RequirementRow>(
1146
- `SELECT * FROM requirements WHERE status IN ('planning', 'planned', 'in_progress') AND godmode = 1`
1146
+ `SELECT * FROM requirements WHERE status IN ('planning', 'planned', 'in_progress') AND godmode = true`
1147
1147
  );
1148
1148
  return activeRequirements.length > 0;
1149
1149
  }
@@ -125,7 +125,9 @@ async function withDistributedHiveContext<T>(
125
125
  );
126
126
  }
127
127
 
128
- const provider = await createPostgresProvider(workspaceId);
128
+ const { join } = await import('path');
129
+ const envPath = join(root, '.env');
130
+ const provider = await createPostgresProvider(workspaceId, envPath);
129
131
  // Create a DatabaseClient-compatible wrapper around the Postgres provider
130
132
  const db: DatabaseClient = {
131
133
  db: null as never, // No sql.js database in distributed mode
@@ -178,7 +180,9 @@ async function withDistributedReadOnlyHiveContext<T>(
178
180
  );
179
181
  }
180
182
 
181
- const provider = await createPostgresProvider(workspaceId);
183
+ const { join } = await import('path');
184
+ const envPath = join(root, '.env');
185
+ const provider = await createPostgresProvider(workspaceId, envPath);
182
186
  const db: ReadOnlyDatabaseClient = {
183
187
  db: null as never, // No sql.js database in distributed mode
184
188
  provider,