@runtimescope/collector 0.9.1 → 0.9.3

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.
@@ -1,9 +1,6 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
1
+ import {
2
+ __require
3
+ } from "./chunk-UP2VWCW5.js";
7
4
 
8
5
  // src/ring-buffer.ts
9
6
  var RingBuffer = class {
@@ -53,6 +50,12 @@ var RingBuffer = class {
53
50
  };
54
51
 
55
52
  // src/store.ts
53
+ function matchesSessionFilter(eventSessionId, filterSessionId) {
54
+ if (filterSessionId.includes(",")) {
55
+ return filterSessionId.split(",").includes(eventSessionId);
56
+ }
57
+ return eventSessionId === filterSessionId;
58
+ }
56
59
  var EventStore = class {
57
60
  buffer;
58
61
  sessions = /* @__PURE__ */ new Map();
@@ -113,7 +116,7 @@ var EventStore = class {
113
116
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
114
117
  return this.buffer.query((e) => {
115
118
  if (e.eventType !== "network") return false;
116
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
119
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
117
120
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
118
121
  const ne = e;
119
122
  if (ne.timestamp < since) return false;
@@ -128,7 +131,7 @@ var EventStore = class {
128
131
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
129
132
  return this.buffer.query((e) => {
130
133
  if (e.eventType !== "console") return false;
131
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
134
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
132
135
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
133
136
  const ce = e;
134
137
  if (ce.timestamp < since) return false;
@@ -150,7 +153,7 @@ var EventStore = class {
150
153
  const typeSet = filter.eventTypes ? new Set(filter.eventTypes) : null;
151
154
  return this.buffer.toArray().filter((e) => {
152
155
  if (e.timestamp < since) return false;
153
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
156
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
154
157
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
155
158
  if (typeSet && !typeSet.has(e.eventType)) return false;
156
159
  return true;
@@ -171,16 +174,28 @@ var EventStore = class {
171
174
  getSessionIdsForProjectId(projectId) {
172
175
  return Array.from(this.sessions.values()).filter((s) => s.projectId === projectId).map((s) => s.sessionId);
173
176
  }
177
+ /** Re-tag all sessions with oldProjectId to use newProjectId. */
178
+ retagSessions(oldProjectId, newProjectId) {
179
+ for (const [, session] of this.sessions) {
180
+ if (session.projectId === oldProjectId) {
181
+ session.projectId = newProjectId;
182
+ }
183
+ }
184
+ }
174
185
  /** Check if an event belongs to the given projectId (via its session). */
175
186
  matchesProjectId(sessionId, projectId) {
176
187
  const session = this.sessions.get(sessionId);
177
- return session?.projectId === projectId;
188
+ if (!session?.projectId) return false;
189
+ if (projectId.includes(",")) {
190
+ return projectId.split(",").includes(session.projectId);
191
+ }
192
+ return session.projectId === projectId;
178
193
  }
179
194
  getStateEvents(filter = {}) {
180
195
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
181
196
  return this.buffer.query((e) => {
182
197
  if (e.eventType !== "state") return false;
183
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
198
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
184
199
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
185
200
  const se = e;
186
201
  if (se.timestamp < since) return false;
@@ -192,7 +207,7 @@ var EventStore = class {
192
207
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
193
208
  return this.buffer.query((e) => {
194
209
  if (e.eventType !== "render") return false;
195
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
210
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
196
211
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
197
212
  const re = e;
198
213
  if (re.timestamp < since) return false;
@@ -209,7 +224,7 @@ var EventStore = class {
209
224
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
210
225
  return this.buffer.query((e) => {
211
226
  if (e.eventType !== "performance") return false;
212
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
227
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
213
228
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
214
229
  const pe = e;
215
230
  if (pe.timestamp < since) return false;
@@ -221,7 +236,7 @@ var EventStore = class {
221
236
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
222
237
  return this.buffer.query((e) => {
223
238
  if (e.eventType !== "database") return false;
224
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
239
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
225
240
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
226
241
  const de = e;
227
242
  if (de.timestamp < since) return false;
@@ -243,7 +258,7 @@ var EventStore = class {
243
258
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
244
259
  return this.buffer.query((e) => {
245
260
  if (e.eventType !== "custom") return false;
246
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
261
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
247
262
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
248
263
  const ce = e;
249
264
  if (ce.timestamp < since) return false;
@@ -255,7 +270,7 @@ var EventStore = class {
255
270
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
256
271
  return this.buffer.query((e) => {
257
272
  if (e.eventType !== "ui") return false;
258
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
273
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
259
274
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
260
275
  const ue = e;
261
276
  if (ue.timestamp < since) return false;
@@ -270,7 +285,7 @@ var EventStore = class {
270
285
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
271
286
  const results = this.buffer.query((e) => {
272
287
  if (e.eventType !== eventType) return false;
273
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
288
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
274
289
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
275
290
  if (e.timestamp < since) return false;
276
291
  if (filter.url) {
@@ -285,7 +300,7 @@ var EventStore = class {
285
300
  const since = filter.sinceSeconds ? Date.now() - filter.sinceSeconds * 1e3 : 0;
286
301
  return this.buffer.query((e) => {
287
302
  if (e.eventType !== eventType) return false;
288
- if (filter.sessionId && e.sessionId !== filter.sessionId) return false;
303
+ if (filter.sessionId && !matchesSessionFilter(e.sessionId, filter.sessionId)) return false;
289
304
  if (filter.projectId && !this.matchesProjectId(e.sessionId, filter.projectId)) return false;
290
305
  if (e.timestamp < since) return false;
291
306
  if (filter.url) {
@@ -352,6 +367,18 @@ function getOrCreateProjectId(projectManager, appName) {
352
367
  projectManager.setProjectIdForApp(appName, projectId);
353
368
  return projectId;
354
369
  }
370
+ function resolveProjectId(projectManager, appName, pmStore) {
371
+ const fromIndex = projectManager.resolveAppProjectId(appName);
372
+ if (fromIndex) return fromIndex;
373
+ if (pmStore) {
374
+ const fromPm = pmStore.findProjectIdByApp(appName);
375
+ if (fromPm) {
376
+ projectManager.setProjectIdForApp(appName, fromPm);
377
+ return fromPm;
378
+ }
379
+ }
380
+ return getOrCreateProjectId(projectManager, appName);
381
+ }
355
382
 
356
383
  // src/sqlite-store.ts
357
384
  import { renameSync, existsSync } from "fs";
@@ -855,6 +882,7 @@ var CollectorServer = class {
855
882
  pruneTimer = null;
856
883
  heartbeatTimer = null;
857
884
  tlsConfig = null;
885
+ pmStore = null;
858
886
  constructor(options = {}) {
859
887
  this.store = new EventStore(options.bufferSize ?? 1e4);
860
888
  this.projectManager = options.projectManager ?? null;
@@ -890,6 +918,10 @@ var CollectorServer = class {
890
918
  getRateLimiter() {
891
919
  return this.rateLimiter;
892
920
  }
921
+ /** Set the PmStore for project ID resolution (called after construction when PmStore is available). */
922
+ setPmStore(pmStore) {
923
+ this.pmStore = pmStore;
924
+ }
893
925
  onConnect(cb) {
894
926
  this.connectCallbacks.push(cb);
895
927
  }
@@ -1074,7 +1106,7 @@ var CollectorServer = class {
1074
1106
  this.pendingHandshakes.delete(ws);
1075
1107
  }
1076
1108
  const projectName = payload.appName;
1077
- const projectId = payload.projectId ?? (this.projectManager ? getOrCreateProjectId(this.projectManager, projectName) : void 0);
1109
+ const projectId = payload.projectId ?? (this.projectManager ? resolveProjectId(this.projectManager, projectName, this.pmStore) : void 0);
1078
1110
  this.clients.set(ws, {
1079
1111
  sessionId: payload.sessionId,
1080
1112
  projectName,
@@ -1247,6 +1279,7 @@ var DEFAULT_GLOBAL_CONFIG = {
1247
1279
  };
1248
1280
  var ProjectManager = class {
1249
1281
  baseDir;
1282
+ appProjectIndex = /* @__PURE__ */ new Map();
1250
1283
  constructor(baseDir) {
1251
1284
  this.baseDir = baseDir ?? join(homedir(), ".runtimescope");
1252
1285
  }
@@ -1359,6 +1392,60 @@ var ProjectManager = class {
1359
1392
  }
1360
1393
  return null;
1361
1394
  }
1395
+ // --- Reverse index: appName → projectId ---
1396
+ /**
1397
+ * Build reverse index: appName -> projectId.
1398
+ * Scans all project configs, PM projects with runtimeApps + runtimeProjectId,
1399
+ * and project-level .runtimescope/config.json files from PM project paths.
1400
+ */
1401
+ rebuildAppIndex(pmStore) {
1402
+ this.appProjectIndex.clear();
1403
+ for (const name of this.listProjects()) {
1404
+ const config = this.getProjectConfig(name);
1405
+ if (config?.projectId) {
1406
+ this.appProjectIndex.set(name.toLowerCase(), config.projectId);
1407
+ }
1408
+ }
1409
+ if (pmStore) {
1410
+ for (const p of pmStore.listProjects()) {
1411
+ if (p.runtimeProjectId && p.runtimeApps) {
1412
+ for (const app of p.runtimeApps) {
1413
+ this.appProjectIndex.set(app.toLowerCase(), p.runtimeProjectId);
1414
+ }
1415
+ }
1416
+ }
1417
+ }
1418
+ if (pmStore) {
1419
+ for (const p of pmStore.listProjects()) {
1420
+ if (p.path) {
1421
+ try {
1422
+ const configPath = join(p.path, ".runtimescope", "config.json");
1423
+ if (existsSync2(configPath)) {
1424
+ const content = readFileSync2(configPath, "utf-8");
1425
+ const config = JSON.parse(content);
1426
+ if (config.projectId) {
1427
+ if (config.appName) {
1428
+ this.appProjectIndex.set(config.appName.toLowerCase(), config.projectId);
1429
+ }
1430
+ if (Array.isArray(config.sdks)) {
1431
+ for (const sdk of config.sdks) {
1432
+ if (sdk.appName) {
1433
+ this.appProjectIndex.set(sdk.appName.toLowerCase(), config.projectId);
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+ }
1439
+ } catch {
1440
+ }
1441
+ }
1442
+ }
1443
+ }
1444
+ }
1445
+ /** O(1) lookup from the cached index. */
1446
+ resolveAppProjectId(appName) {
1447
+ return this.appProjectIndex.get(appName.toLowerCase()) ?? null;
1448
+ }
1362
1449
  // --- Environment variable resolution ---
1363
1450
  resolveEnvVars(value) {
1364
1451
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
@@ -1412,6 +1499,129 @@ var ProjectManager = class {
1412
1499
  }
1413
1500
  };
1414
1501
 
1502
+ // src/project-config.ts
1503
+ import { readFileSync as readFileSync3, existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1504
+ import { join as join2 } from "path";
1505
+ var DEFAULT_CAPTURE = {
1506
+ network: true,
1507
+ console: true,
1508
+ xhr: true,
1509
+ body: false,
1510
+ performance: true,
1511
+ renders: true,
1512
+ navigation: true,
1513
+ clicks: false,
1514
+ http: false,
1515
+ errors: true,
1516
+ stackTraces: false
1517
+ };
1518
+ function readProjectConfig(projectDir) {
1519
+ const configPath = join2(projectDir, ".runtimescope", "config.json");
1520
+ if (!existsSync3(configPath)) return null;
1521
+ try {
1522
+ const content = readFileSync3(configPath, "utf-8");
1523
+ return JSON.parse(content);
1524
+ } catch {
1525
+ return null;
1526
+ }
1527
+ }
1528
+ function writeProjectConfig(projectDir, config) {
1529
+ const dir = join2(projectDir, ".runtimescope");
1530
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
1531
+ writeFileSync2(join2(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
1532
+ }
1533
+ function scaffoldProjectConfig(projectDir, opts) {
1534
+ const existing = readProjectConfig(projectDir);
1535
+ if (existing) {
1536
+ if (opts.sdkType) {
1537
+ const alreadyHas = existing.sdks.some((s) => s.type === opts.sdkType);
1538
+ if (!alreadyHas) {
1539
+ existing.sdks.push({
1540
+ type: opts.sdkType,
1541
+ framework: opts.framework,
1542
+ appName: opts.appName !== existing.appName ? opts.appName : void 0
1543
+ });
1544
+ writeProjectConfig(projectDir, existing);
1545
+ }
1546
+ }
1547
+ return existing;
1548
+ }
1549
+ const projectId = generateProjectId();
1550
+ const httpPort = process.env.RUNTIMESCOPE_HTTP_PORT ?? "9091";
1551
+ const dsn = `runtimescope://${projectId}@localhost:${httpPort}/${opts.appName}`;
1552
+ const config = {
1553
+ projectId,
1554
+ dsn,
1555
+ appName: opts.appName,
1556
+ description: opts.description,
1557
+ sdks: opts.sdkType ? [{ type: opts.sdkType, framework: opts.framework }] : [],
1558
+ capture: { ...DEFAULT_CAPTURE },
1559
+ category: opts.category
1560
+ };
1561
+ writeProjectConfig(projectDir, config);
1562
+ const gitignorePath = join2(projectDir, ".runtimescope", ".gitignore");
1563
+ if (!existsSync3(gitignorePath)) {
1564
+ writeFileSync2(gitignorePath, "# Keep config.json committed, ignore local state\n*.log\n*.db\n.env\n", "utf-8");
1565
+ }
1566
+ return config;
1567
+ }
1568
+ function resolveProjectAppNames(config) {
1569
+ const names = /* @__PURE__ */ new Set([config.appName]);
1570
+ for (const sdk of config.sdks) {
1571
+ if (sdk.appName) names.add(sdk.appName);
1572
+ }
1573
+ return Array.from(names);
1574
+ }
1575
+ function migrateProjectIds(projectManager, pmStore) {
1576
+ const result = { unified: 0, skipped: 0, details: [] };
1577
+ if (!pmStore) return result;
1578
+ for (const project of pmStore.listProjects()) {
1579
+ if (!project.runtimeApps || project.runtimeApps.length < 2) continue;
1580
+ if (!project.path) {
1581
+ result.skipped++;
1582
+ continue;
1583
+ }
1584
+ let canonicalId = null;
1585
+ try {
1586
+ const configPath = join2(project.path, ".runtimescope", "config.json");
1587
+ if (existsSync3(configPath)) {
1588
+ const config = JSON.parse(readFileSync3(configPath, "utf-8"));
1589
+ if (config.projectId) canonicalId = config.projectId;
1590
+ }
1591
+ } catch {
1592
+ }
1593
+ if (!canonicalId && project.runtimeProjectId) {
1594
+ canonicalId = project.runtimeProjectId;
1595
+ }
1596
+ if (!canonicalId) {
1597
+ for (const appName of project.runtimeApps) {
1598
+ const existing = projectManager.getProjectIdForApp(appName);
1599
+ if (existing) {
1600
+ canonicalId = existing;
1601
+ break;
1602
+ }
1603
+ }
1604
+ }
1605
+ if (!canonicalId) {
1606
+ result.skipped++;
1607
+ continue;
1608
+ }
1609
+ for (const appName of project.runtimeApps) {
1610
+ const current = projectManager.getProjectIdForApp(appName);
1611
+ if (current !== canonicalId) {
1612
+ projectManager.setProjectIdForApp(appName, canonicalId);
1613
+ result.details.push(`Unified ${appName}: ${current ?? "none"} \u2192 ${canonicalId}`);
1614
+ result.unified++;
1615
+ }
1616
+ }
1617
+ if (project.runtimeProjectId !== canonicalId) {
1618
+ pmStore.updateProject(project.id, { runtimeProjectId: canonicalId });
1619
+ result.details.push(`PM project ${project.name}: runtimeProjectId \u2192 ${canonicalId}`);
1620
+ }
1621
+ }
1622
+ return result;
1623
+ }
1624
+
1415
1625
  // src/auth.ts
1416
1626
  import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
1417
1627
  var AuthManager = class {
@@ -1576,7 +1786,7 @@ var Redactor = class {
1576
1786
  // src/platform.ts
1577
1787
  import { execFileSync, execSync } from "child_process";
1578
1788
  import { readlinkSync, readdirSync as readdirSync2 } from "fs";
1579
- import { join as join2 } from "path";
1789
+ import { join as join3 } from "path";
1580
1790
  var IS_WIN = process.platform === "win32";
1581
1791
  var IS_LINUX = process.platform === "linux";
1582
1792
  function runFile(cmd, args, timeoutMs = 5e3) {
@@ -1695,7 +1905,7 @@ function findPidsInDir_linux(dir) {
1695
1905
  const pid = parseInt(entry, 10);
1696
1906
  if (isNaN(pid) || pid <= 1) continue;
1697
1907
  try {
1698
- const cwd = readlinkSync(join2("/proc", entry, "cwd"));
1908
+ const cwd = readlinkSync(join3("/proc", entry, "cwd"));
1699
1909
  if (cwd.startsWith(dir)) pids.push(pid);
1700
1910
  } catch {
1701
1911
  }
@@ -1899,15 +2109,15 @@ var SessionManager = class {
1899
2109
  // src/http-server.ts
1900
2110
  import { createServer } from "http";
1901
2111
  import { createServer as createHttpsServer2 } from "https";
1902
- import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
2112
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
1903
2113
  import { resolve, dirname } from "path";
1904
2114
  import { fileURLToPath } from "url";
1905
2115
  import { WebSocketServer as WebSocketServer2 } from "ws";
1906
2116
 
1907
2117
  // src/pm/pm-routes.ts
1908
2118
  import { readdir, readFile, writeFile, unlink, mkdir } from "fs/promises";
1909
- import { existsSync as existsSync3 } from "fs";
1910
- import { join as join3 } from "path";
2119
+ import { existsSync as existsSync4 } from "fs";
2120
+ import { join as join4 } from "path";
1911
2121
  import { homedir as homedir2 } from "os";
1912
2122
  import { spawn, execFileSync as execFileSync2 } from "child_process";
1913
2123
  var LOG_RING_SIZE = 500;
@@ -2044,6 +2254,16 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2044
2254
  helpers.json(res, { error: err.message }, 400);
2045
2255
  }
2046
2256
  });
2257
+ route("DELETE", "/api/pm/projects/:id", (_req, res, params) => {
2258
+ const id = params.get("id");
2259
+ const project = pmStore.getProject(id);
2260
+ if (!project) {
2261
+ helpers.json(res, { error: "Project not found" }, 404);
2262
+ return;
2263
+ }
2264
+ pmStore.deleteProject(id);
2265
+ helpers.json(res, { ok: true, deleted: project.name });
2266
+ });
2047
2267
  route("GET", "/api/pm/tasks", (_req, res, params) => {
2048
2268
  const projectId = params.get("project_id") ?? void 0;
2049
2269
  const status = params.get("status") ?? void 0;
@@ -2219,13 +2439,13 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2219
2439
  helpers.json(res, { data: [], count: 0 });
2220
2440
  return;
2221
2441
  }
2222
- const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
2442
+ const memoryDir = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
2223
2443
  try {
2224
2444
  const files = await readdir(memoryDir);
2225
2445
  const mdFiles = files.filter((f) => f.endsWith(".md"));
2226
2446
  const result = await Promise.all(
2227
2447
  mdFiles.map(async (filename) => {
2228
- const content = await readFile(join3(memoryDir, filename), "utf-8");
2448
+ const content = await readFile(join4(memoryDir, filename), "utf-8");
2229
2449
  return { filename, content, sizeBytes: Buffer.byteLength(content) };
2230
2450
  })
2231
2451
  );
@@ -2242,7 +2462,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2242
2462
  helpers.json(res, { error: "Project not found" }, 404);
2243
2463
  return;
2244
2464
  }
2245
- const filePath = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
2465
+ const filePath = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
2246
2466
  try {
2247
2467
  const content = await readFile(filePath, "utf-8");
2248
2468
  helpers.json(res, { filename, content, sizeBytes: Buffer.byteLength(content) });
@@ -2265,9 +2485,9 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2265
2485
  }
2266
2486
  try {
2267
2487
  const { content } = JSON.parse(body);
2268
- const memoryDir = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
2488
+ const memoryDir = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory");
2269
2489
  await mkdir(memoryDir, { recursive: true });
2270
- await writeFile(join3(memoryDir, filename), content, "utf-8");
2490
+ await writeFile(join4(memoryDir, filename), content, "utf-8");
2271
2491
  helpers.json(res, { ok: true });
2272
2492
  } catch (err) {
2273
2493
  helpers.json(res, { error: err.message }, 500);
@@ -2281,7 +2501,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2281
2501
  helpers.json(res, { error: "Project not found" }, 404);
2282
2502
  return;
2283
2503
  }
2284
- const filePath = join3(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
2504
+ const filePath = join4(homedir2(), ".claude", "projects", project.claudeProjectKey, "memory", filename);
2285
2505
  try {
2286
2506
  await unlink(filePath);
2287
2507
  helpers.json(res, { ok: true });
@@ -2341,7 +2561,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2341
2561
  const { content } = JSON.parse(body);
2342
2562
  const paths = getRulesPaths(project.claudeProjectKey, project.path);
2343
2563
  const filePath = paths[scope];
2344
- const dir = join3(filePath, "..");
2564
+ const dir = join4(filePath, "..");
2345
2565
  await mkdir(dir, { recursive: true });
2346
2566
  await writeFile(filePath, content, "utf-8");
2347
2567
  helpers.json(res, { ok: true });
@@ -2361,7 +2581,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2361
2581
  return;
2362
2582
  }
2363
2583
  try {
2364
- const pkgPath = join3(project.path, "package.json");
2584
+ const pkgPath = join4(project.path, "package.json");
2365
2585
  const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
2366
2586
  const scripts = pkg.scripts ?? {};
2367
2587
  const recommended = ["dev", "start", "serve"].find((s) => s in scripts) ?? null;
@@ -2757,6 +2977,106 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
2757
2977
  });
2758
2978
  res.end(csv);
2759
2979
  });
2980
+ route("GET", "/api/pm/capex-report/:projectId", async (_req, res, params) => {
2981
+ const projectId = params.get("projectId");
2982
+ const startDate = params.get("start_date") ?? void 0;
2983
+ const endDate = params.get("end_date") ?? void 0;
2984
+ try {
2985
+ const buffer = await pmStore.exportCapexXlsx(projectId, { startDate, endDate });
2986
+ res.writeHead(200, {
2987
+ "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
2988
+ "Content-Disposition": `attachment; filename="capex-${projectId}.xlsx"`
2989
+ });
2990
+ res.end(buffer);
2991
+ } catch {
2992
+ const csv = pmStore.exportCapexCsv(projectId, { startDate, endDate });
2993
+ res.writeHead(200, {
2994
+ "Content-Type": "text/csv",
2995
+ "Content-Disposition": `attachment; filename="capex-${projectId}.csv"`
2996
+ });
2997
+ res.end(csv);
2998
+ }
2999
+ });
3000
+ route("GET", "/api/pm/capex-all", (_req, res, params) => {
3001
+ const category = params.get("category") ?? void 0;
3002
+ const projects = pmStore.listProjects();
3003
+ const filteredProjects = category ? projects.filter((p) => p.category === category) : projects;
3004
+ const allEntries = [];
3005
+ let totalCost = 0;
3006
+ let totalCapitalizable = 0;
3007
+ let totalExpensed = 0;
3008
+ let totalMinutes = 0;
3009
+ let totalConfirmed = 0;
3010
+ let totalUnconfirmed = 0;
3011
+ const byProject = [];
3012
+ for (const project of filteredProjects) {
3013
+ const entries = pmStore.listCapexEntries(project.id);
3014
+ let projCost = 0;
3015
+ let projCap = 0;
3016
+ let projExp = 0;
3017
+ let projMins = 0;
3018
+ let projConfirmed = 0;
3019
+ for (const e of entries) {
3020
+ allEntries.push({ ...e, projectName: project.name });
3021
+ projCost += e.adjustedCostMicrodollars;
3022
+ projMins += e.activeMinutes;
3023
+ if (e.classification === "capitalizable") projCap += e.adjustedCostMicrodollars;
3024
+ else projExp += e.adjustedCostMicrodollars;
3025
+ if (e.confirmed) projConfirmed++;
3026
+ }
3027
+ if (entries.length > 0) {
3028
+ byProject.push({
3029
+ projectId: project.id,
3030
+ projectName: project.name,
3031
+ category: project.category,
3032
+ totalCost: projCost,
3033
+ capitalizable: projCap,
3034
+ expensed: projExp,
3035
+ activeMinutes: projMins,
3036
+ activeHours: +(projMins / 60).toFixed(2),
3037
+ confirmed: projConfirmed,
3038
+ total: entries.length
3039
+ });
3040
+ }
3041
+ totalCost += projCost;
3042
+ totalCapitalizable += projCap;
3043
+ totalExpensed += projExp;
3044
+ totalMinutes += projMins;
3045
+ totalConfirmed += projConfirmed;
3046
+ totalUnconfirmed += entries.length - projConfirmed;
3047
+ }
3048
+ helpers.json(res, {
3049
+ data: {
3050
+ summary: {
3051
+ totalCost,
3052
+ capitalizable: totalCapitalizable,
3053
+ expensed: totalExpensed,
3054
+ activeMinutes: totalMinutes,
3055
+ activeHours: +(totalMinutes / 60).toFixed(2),
3056
+ confirmed: totalConfirmed,
3057
+ unconfirmed: totalUnconfirmed,
3058
+ projectCount: byProject.length
3059
+ },
3060
+ byProject,
3061
+ entries: allEntries.sort((a, b) => b.createdAt - a.createdAt)
3062
+ }
3063
+ });
3064
+ });
3065
+ route("GET", "/api/pm/capex-report-all", async (_req, res, params) => {
3066
+ const startDate = params.get("start_date") ?? void 0;
3067
+ const endDate = params.get("end_date") ?? void 0;
3068
+ const category = params.get("category") ?? void 0;
3069
+ try {
3070
+ const buffer = await pmStore.exportCapexXlsxAll({ startDate, endDate, category });
3071
+ res.writeHead(200, {
3072
+ "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
3073
+ "Content-Disposition": 'attachment; filename="capex-all-projects.xlsx"'
3074
+ });
3075
+ res.end(buffer);
3076
+ } catch (err) {
3077
+ helpers.json(res, { error: err.message }, 500);
3078
+ }
3079
+ });
2760
3080
  return {
2761
3081
  match(method, pathname) {
2762
3082
  const pathSegments = pathname.split("/");
@@ -2787,9 +3107,9 @@ function sanitizeFilename(name) {
2787
3107
  function getRulesPaths(claudeProjectKey, projectPath) {
2788
3108
  const home = homedir2();
2789
3109
  return {
2790
- global: join3(home, ".claude", "CLAUDE.md"),
2791
- project: claudeProjectKey ? join3(home, ".claude", "projects", claudeProjectKey, "CLAUDE.md") : join3(projectPath ?? "", ".claude", "CLAUDE.md"),
2792
- local: projectPath ? join3(projectPath, "CLAUDE.md") : join3(home, "CLAUDE.md")
3110
+ global: join4(home, ".claude", "CLAUDE.md"),
3111
+ project: claudeProjectKey ? join4(home, ".claude", "projects", claudeProjectKey, "CLAUDE.md") : join4(projectPath ?? "", ".claude", "CLAUDE.md"),
3112
+ local: projectPath ? join4(projectPath, "CLAUDE.md") : join4(home, "CLAUDE.md")
2793
3113
  };
2794
3114
  }
2795
3115
  function execGit(args, cwd) {
@@ -2834,7 +3154,7 @@ function parseGitStatus(porcelain) {
2834
3154
  }
2835
3155
  async function readRuleFile(filePath) {
2836
3156
  try {
2837
- if (existsSync3(filePath)) {
3157
+ if (existsSync4(filePath)) {
2838
3158
  const content = await readFile(filePath, "utf-8");
2839
3159
  return { path: filePath, content, exists: true };
2840
3160
  }
@@ -3079,7 +3399,7 @@ var HttpServer = class {
3079
3399
  return;
3080
3400
  }
3081
3401
  const payload = parsed;
3082
- const projectId = typeof payload.projectId === "string" ? payload.projectId : payload.appName && this.projectManager ? getOrCreateProjectId(this.projectManager, payload.appName) : void 0;
3402
+ const projectId = typeof payload.projectId === "string" ? payload.projectId : payload.appName && this.projectManager ? resolveProjectId(this.projectManager, payload.appName, this.pmStore) : void 0;
3083
3403
  if (!payload.sessionId || !Array.isArray(payload.events) || payload.events.length === 0) {
3084
3404
  this.json(res, {
3085
3405
  error: "Required: sessionId (string), events (non-empty array)",
@@ -3164,7 +3484,7 @@ var HttpServer = class {
3164
3484
  // npm installed
3165
3485
  ];
3166
3486
  for (const p of candidates) {
3167
- if (existsSync4(p)) {
3487
+ if (existsSync5(p)) {
3168
3488
  this.sdkBundlePath = p;
3169
3489
  return p;
3170
3490
  }
@@ -3312,7 +3632,7 @@ var HttpServer = class {
3312
3632
  if (req.method === "GET" && url.pathname === "/runtimescope.js") {
3313
3633
  const sdkPath = this.resolveSdkPath();
3314
3634
  if (sdkPath) {
3315
- const bundle = readFileSync3(sdkPath, "utf-8");
3635
+ const bundle = readFileSync4(sdkPath, "utf-8");
3316
3636
  res.writeHead(200, {
3317
3637
  "Content-Type": "application/javascript",
3318
3638
  "Cache-Control": "no-cache"
@@ -3326,14 +3646,12 @@ var HttpServer = class {
3326
3646
  }
3327
3647
  if (req.method === "GET" && url.pathname === "/snippet") {
3328
3648
  const appName = (url.searchParams.get("app") || "my-app").replace(/[^a-zA-Z0-9_-]/g, "");
3329
- const wsPort = process.env.RUNTIMESCOPE_PORT ?? "9090";
3649
+ const projectId = url.searchParams.get("project_id") || "proj_xxx";
3650
+ const dsn = `runtimescope://${projectId}@localhost:${this.activePort}/${appName}`;
3330
3651
  const snippet = `<!-- RuntimeScope SDK \u2014 paste before </body> -->
3331
3652
  <script src="http://localhost:${this.activePort}/runtimescope.js"></script>
3332
3653
  <script>
3333
- RuntimeScope.init({
3334
- appName: '${appName}',
3335
- endpoint: 'ws://localhost:${wsPort}',
3336
- });
3654
+ RuntimeScope.init({ dsn: '${dsn}' });
3337
3655
  </script>`;
3338
3656
  res.writeHead(200, {
3339
3657
  "Content-Type": "text/plain"
@@ -3563,6 +3881,13 @@ var PmStore = class {
3563
3881
  CREATE INDEX IF NOT EXISTS idx_pm_capex_project ON pm_capex_entries(project_id);
3564
3882
  CREATE INDEX IF NOT EXISTS idx_pm_capex_period ON pm_capex_entries(period);
3565
3883
  CREATE INDEX IF NOT EXISTS idx_pm_capex_confirmed ON pm_capex_entries(confirmed);
3884
+
3885
+ CREATE TABLE IF NOT EXISTS pm_deleted_projects (
3886
+ path TEXT PRIMARY KEY,
3887
+ name TEXT,
3888
+ deleted_at INTEGER NOT NULL
3889
+ );
3890
+ CREATE INDEX IF NOT EXISTS idx_deleted_path ON pm_deleted_projects(path);
3566
3891
  `);
3567
3892
  }
3568
3893
  runMigrations() {
@@ -3730,6 +4055,11 @@ var PmStore = class {
3730
4055
  this.updateProject(match.id, updates);
3731
4056
  return match.id;
3732
4057
  }
4058
+ /** Find a project's runtimeProjectId by checking if appName appears in any project's runtimeApps. */
4059
+ findProjectIdByApp(appName) {
4060
+ const row = this.db.prepare(`SELECT runtime_project_id FROM pm_projects WHERE runtime_apps LIKE ? AND runtime_project_id IS NOT NULL LIMIT 1`).get(`%"${appName}"%`);
4061
+ return row?.runtime_project_id ?? null;
4062
+ }
3733
4063
  listCategories() {
3734
4064
  const rows = this.db.prepare("SELECT DISTINCT category FROM pm_projects WHERE category IS NOT NULL ORDER BY category ASC").all();
3735
4065
  return rows.map((r) => r.category);
@@ -4345,6 +4675,129 @@ var PmStore = class {
4345
4675
  });
4346
4676
  return [headers.join(","), ...rows].join("\n");
4347
4677
  }
4678
+ async exportCapexXlsx(projectId, opts) {
4679
+ const ExcelJS = await import("./excel-3DUFQWCD.js");
4680
+ const workbook = new ExcelJS.Workbook();
4681
+ const project = this.getProject(projectId);
4682
+ const summary = this.getCapexSummary(projectId, opts);
4683
+ const entries = this.listCapexEntries(projectId, { month: opts?.startDate });
4684
+ const sessions = /* @__PURE__ */ new Map();
4685
+ for (const entry of entries) {
4686
+ if (!sessions.has(entry.sessionId)) {
4687
+ const s = this.getSession(entry.sessionId);
4688
+ if (s) sessions.set(entry.sessionId, s);
4689
+ }
4690
+ }
4691
+ const summarySheet = workbook.addWorksheet("Summary");
4692
+ const summaryData = [
4693
+ ["Project", project?.name ?? projectId],
4694
+ ["Phase", project?.phase ?? ""],
4695
+ ["Status", project?.projectStatus ?? ""],
4696
+ ["Total Active Hours", (summary.totalActiveMinutes / 60).toFixed(2)],
4697
+ ["Total Cost", `$${(summary.totalCostMicrodollars / 1e6).toFixed(2)}`],
4698
+ ["Capitalizable", `$${(summary.capitalizableCostMicrodollars / 1e6).toFixed(2)}`],
4699
+ ["Expensed", `$${(summary.expensedCostMicrodollars / 1e6).toFixed(2)}`],
4700
+ ["Sessions", String(summary.totalSessions)],
4701
+ ["Confirmed", `${summary.confirmedCount}/${summary.totalSessions}`]
4702
+ ];
4703
+ summaryData.forEach(([field, value]) => {
4704
+ const row = summarySheet.addRow([field, value]);
4705
+ row.getCell(1).font = { bold: true };
4706
+ });
4707
+ summarySheet.getColumn(1).width = 20;
4708
+ summarySheet.getColumn(2).width = 30;
4709
+ const detailSheet = workbook.addWorksheet("Daily Detail");
4710
+ detailSheet.addRow(["Date", "Session", "Model", "Active Hours", "Cost (USD)", "Classification", "Work Type", "Confirmed", "Notes"]);
4711
+ detailSheet.getRow(1).font = { bold: true };
4712
+ for (const e of entries) {
4713
+ const s = sessions.get(e.sessionId);
4714
+ const date = s?.startedAt ? new Date(s.startedAt).toISOString().split("T")[0] : e.period;
4715
+ detailSheet.addRow([
4716
+ date,
4717
+ s?.slug ?? e.sessionId.slice(0, 12),
4718
+ s?.model ?? "",
4719
+ Number((e.activeMinutes / 60).toFixed(2)),
4720
+ Number((e.costMicrodollars / 1e6).toFixed(4)),
4721
+ e.classification,
4722
+ e.workType ?? "",
4723
+ e.confirmed ? "Yes" : "No",
4724
+ e.notes ?? ""
4725
+ ]);
4726
+ }
4727
+ detailSheet.columns.forEach((col) => {
4728
+ col.width = 16;
4729
+ });
4730
+ detailSheet.getColumn(1).width = 12;
4731
+ detailSheet.getColumn(2).width = 24;
4732
+ const monthlySheet = workbook.addWorksheet("Monthly Totals");
4733
+ monthlySheet.addRow(["Period", "Active Hours", "Capitalizable ($)", "Expensed ($)", "Total ($)"]);
4734
+ monthlySheet.getRow(1).font = { bold: true };
4735
+ for (const m of summary.byMonth) {
4736
+ monthlySheet.addRow([
4737
+ m.period,
4738
+ Number((m.activeMinutes / 60).toFixed(2)),
4739
+ Number((m.capitalizable / 1e6).toFixed(2)),
4740
+ Number((m.expensed / 1e6).toFixed(2)),
4741
+ Number(((m.capitalizable + m.expensed) / 1e6).toFixed(2))
4742
+ ]);
4743
+ }
4744
+ monthlySheet.columns.forEach((col) => {
4745
+ col.width = 18;
4746
+ });
4747
+ return Buffer.from(await workbook.xlsx.writeBuffer());
4748
+ }
4749
+ async exportCapexXlsxAll(opts) {
4750
+ const ExcelJS = await import("./excel-3DUFQWCD.js");
4751
+ const workbook = new ExcelJS.Workbook();
4752
+ let projects = this.listProjects();
4753
+ if (opts?.category) {
4754
+ projects = projects.filter((p) => p.category === opts.category);
4755
+ }
4756
+ const overviewSheet = workbook.addWorksheet("Overview");
4757
+ overviewSheet.addRow(["Project", "Category", "Phase", "Total Hours", "Capitalizable ($)", "Expensed ($)", "Total ($)"]);
4758
+ overviewSheet.getRow(1).font = { bold: true };
4759
+ for (const p of projects) {
4760
+ const summary = this.getCapexSummary(p.id, opts);
4761
+ overviewSheet.addRow([
4762
+ p.name,
4763
+ p.category ?? "",
4764
+ p.phase,
4765
+ Number((summary.totalActiveMinutes / 60).toFixed(2)),
4766
+ Number((summary.capitalizableCostMicrodollars / 1e6).toFixed(2)),
4767
+ Number((summary.expensedCostMicrodollars / 1e6).toFixed(2)),
4768
+ Number((summary.totalCostMicrodollars / 1e6).toFixed(2))
4769
+ ]);
4770
+ }
4771
+ overviewSheet.columns.forEach((col) => {
4772
+ col.width = 18;
4773
+ });
4774
+ overviewSheet.getColumn(1).width = 28;
4775
+ const allSheet = workbook.addWorksheet("All Entries");
4776
+ allSheet.addRow(["Project", "Date", "Session", "Active Hours", "Cost (USD)", "Classification", "Work Type"]);
4777
+ allSheet.getRow(1).font = { bold: true };
4778
+ for (const p of projects) {
4779
+ const entries = this.listCapexEntries(p.id, { month: opts?.startDate });
4780
+ for (const e of entries) {
4781
+ const s = this.getSession(e.sessionId);
4782
+ const date = s?.startedAt ? new Date(s.startedAt).toISOString().split("T")[0] : e.period;
4783
+ allSheet.addRow([
4784
+ p.name,
4785
+ date,
4786
+ s?.slug ?? e.sessionId.slice(0, 12),
4787
+ Number((e.activeMinutes / 60).toFixed(2)),
4788
+ Number((e.costMicrodollars / 1e6).toFixed(4)),
4789
+ e.classification,
4790
+ e.workType ?? ""
4791
+ ]);
4792
+ }
4793
+ }
4794
+ allSheet.columns.forEach((col) => {
4795
+ col.width = 16;
4796
+ });
4797
+ allSheet.getColumn(1).width = 24;
4798
+ allSheet.getColumn(3).width = 24;
4799
+ return Buffer.from(await workbook.xlsx.writeBuffer());
4800
+ }
4348
4801
  mapCapexRow(row) {
4349
4802
  return {
4350
4803
  id: row.id,
@@ -4366,6 +4819,38 @@ var PmStore = class {
4366
4819
  };
4367
4820
  }
4368
4821
  // ============================================================
4822
+ // Deleted Projects Blocklist
4823
+ // ============================================================
4824
+ deleteProject(id) {
4825
+ const project = this.getProject(id);
4826
+ if (!project) return;
4827
+ if (project.path) {
4828
+ this.db.prepare(
4829
+ "INSERT OR REPLACE INTO pm_deleted_projects (path, name, deleted_at) VALUES (?, ?, ?)"
4830
+ ).run(project.path, project.name, Date.now());
4831
+ }
4832
+ if (project.claudeProjectKey) {
4833
+ this.db.prepare(
4834
+ "INSERT OR REPLACE INTO pm_deleted_projects (path, name, deleted_at) VALUES (?, ?, ?)"
4835
+ ).run(project.claudeProjectKey, project.name, Date.now());
4836
+ }
4837
+ this.db.prepare("DELETE FROM pm_capex_entries WHERE project_id = ?").run(id);
4838
+ this.db.prepare("DELETE FROM pm_notes WHERE project_id = ?").run(id);
4839
+ this.db.prepare("DELETE FROM pm_tasks WHERE project_id = ?").run(id);
4840
+ this.db.prepare("DELETE FROM pm_sessions WHERE project_id = ?").run(id);
4841
+ this.db.prepare("DELETE FROM pm_projects WHERE id = ?").run(id);
4842
+ }
4843
+ isDeletedPath(path) {
4844
+ const row = this.db.prepare("SELECT 1 FROM pm_deleted_projects WHERE path = ?").get(path);
4845
+ return !!row;
4846
+ }
4847
+ recoverProject(path) {
4848
+ this.db.prepare("DELETE FROM pm_deleted_projects WHERE path = ?").run(path);
4849
+ }
4850
+ listDeletedProjects() {
4851
+ return this.db.prepare("SELECT path, name, deleted_at as deletedAt FROM pm_deleted_projects ORDER BY deleted_at DESC").all();
4852
+ }
4853
+ // ============================================================
4369
4854
  // Cleanup
4370
4855
  // ============================================================
4371
4856
  close() {
@@ -4578,13 +5063,13 @@ async function parseSessionJsonl(jsonlPath, sessionId, projectId) {
4578
5063
 
4579
5064
  // src/pm/project-discovery.ts
4580
5065
  import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
4581
- import { join as join4, basename as basename2 } from "path";
4582
- import { existsSync as existsSync5 } from "fs";
5066
+ import { join as join5, basename as basename2 } from "path";
5067
+ import { existsSync as existsSync6 } from "fs";
4583
5068
  import { homedir as homedir3 } from "os";
4584
5069
  var LOG_PREFIX = "[RuntimeScope PM]";
4585
5070
  async function detectSdkInstalled(projectPath) {
4586
5071
  try {
4587
- const pkgPath = join4(projectPath, "package.json");
5072
+ const pkgPath = join5(projectPath, "package.json");
4588
5073
  const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
4589
5074
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4590
5075
  if ("@runtimescope/sdk" in allDeps || "@runtimescope/server-sdk" in allDeps) {
@@ -4594,13 +5079,13 @@ async function detectSdkInstalled(projectPath) {
4594
5079
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages ?? [];
4595
5080
  for (const ws of workspaces) {
4596
5081
  const wsBase = ws.replace(/\/?\*$/, "");
4597
- const wsDir = join4(projectPath, wsBase);
5082
+ const wsDir = join5(projectPath, wsBase);
4598
5083
  try {
4599
5084
  const entries = await readdir2(wsDir, { withFileTypes: true });
4600
5085
  for (const entry of entries) {
4601
5086
  if (!entry.isDirectory()) continue;
4602
5087
  try {
4603
- const wsPkg = JSON.parse(await readFile2(join4(wsDir, entry.name, "package.json"), "utf-8"));
5088
+ const wsPkg = JSON.parse(await readFile2(join5(wsDir, entry.name, "package.json"), "utf-8"));
4604
5089
  const wsDeps = { ...wsPkg.dependencies, ...wsPkg.devDependencies };
4605
5090
  if ("@runtimescope/sdk" in wsDeps || "@runtimescope/server-sdk" in wsDeps) {
4606
5091
  return true;
@@ -4615,7 +5100,7 @@ async function detectSdkInstalled(projectPath) {
4615
5100
  } catch {
4616
5101
  }
4617
5102
  try {
4618
- await stat2(join4(projectPath, "node_modules", "@runtimescope"));
5103
+ await stat2(join5(projectPath, "node_modules", "@runtimescope"));
4619
5104
  return true;
4620
5105
  } catch {
4621
5106
  return false;
@@ -4646,7 +5131,7 @@ function slugifyPath(fsPath) {
4646
5131
  }
4647
5132
  function decodeClaudeKey(key) {
4648
5133
  const naive = "/" + key.slice(1).replace(/-/g, "/");
4649
- if (existsSync5(naive)) return naive;
5134
+ if (existsSync6(naive)) return naive;
4650
5135
  const parts = key.slice(1).split("-");
4651
5136
  return resolvePathSegments(parts);
4652
5137
  }
@@ -4654,16 +5139,16 @@ function resolvePathSegments(parts) {
4654
5139
  if (parts.length === 0) return null;
4655
5140
  function tryResolve(prefix, remaining) {
4656
5141
  if (remaining.length === 0) {
4657
- return existsSync5(prefix) ? prefix : null;
5142
+ return existsSync6(prefix) ? prefix : null;
4658
5143
  }
4659
5144
  for (let count = remaining.length; count >= 1; count--) {
4660
5145
  const segment = remaining.slice(0, count).join("-");
4661
- const candidate = join4(prefix, segment);
5146
+ const candidate = join5(prefix, segment);
4662
5147
  if (count === remaining.length) {
4663
- if (existsSync5(candidate)) return candidate;
5148
+ if (existsSync6(candidate)) return candidate;
4664
5149
  } else {
4665
5150
  try {
4666
- if (existsSync5(candidate)) {
5151
+ if (existsSync6(candidate)) {
4667
5152
  const result = tryResolve(candidate, remaining.slice(count));
4668
5153
  if (result) return result;
4669
5154
  }
@@ -4679,13 +5164,14 @@ function toPeriod(timestampMs) {
4679
5164
  const d = new Date(timestampMs);
4680
5165
  const year = d.getFullYear();
4681
5166
  const month = String(d.getMonth() + 1).padStart(2, "0");
4682
- return `${year}-${month}`;
5167
+ const day = String(d.getDate()).padStart(2, "0");
5168
+ return `${year}-${month}-${day}`;
4683
5169
  }
4684
5170
  var ProjectDiscovery = class {
4685
5171
  constructor(pmStore, projectManager, claudeBaseDir) {
4686
5172
  this.pmStore = pmStore;
4687
5173
  this.projectManager = projectManager;
4688
- this.claudeBaseDir = claudeBaseDir ?? join4(homedir3(), ".claude");
5174
+ this.claudeBaseDir = claudeBaseDir ?? join5(homedir3(), ".claude");
4689
5175
  }
4690
5176
  claudeBaseDir;
4691
5177
  /**
@@ -4718,7 +5204,7 @@ var ProjectDiscovery = class {
4718
5204
  sessionsUpdated: 0,
4719
5205
  errors: []
4720
5206
  };
4721
- const projectsDir = join4(this.claudeBaseDir, "projects");
5207
+ const projectsDir = join5(this.claudeBaseDir, "projects");
4722
5208
  try {
4723
5209
  await stat2(projectsDir);
4724
5210
  } catch {
@@ -4785,6 +5271,7 @@ var ProjectDiscovery = class {
4785
5271
  await this.pmStore.upsertProject(updated);
4786
5272
  result.projectsUpdated = (result.projectsUpdated ?? 0) + 1;
4787
5273
  } else {
5274
+ if (this.pmStore.isDeletedPath(sourcePath)) continue;
4788
5275
  const fsPath = projectDir;
4789
5276
  const project = {
4790
5277
  id,
@@ -4830,10 +5317,10 @@ var ProjectDiscovery = class {
4830
5317
  if (!project.claudeProjectKey) {
4831
5318
  return 0;
4832
5319
  }
4833
- const projectDir = join4(this.claudeBaseDir, "projects", project.claudeProjectKey);
5320
+ const projectDir = join5(this.claudeBaseDir, "projects", project.claudeProjectKey);
4834
5321
  let sessionsIndexed = 0;
4835
5322
  try {
4836
- const indexPath = join4(projectDir, "sessions-index.json");
5323
+ const indexPath = join5(projectDir, "sessions-index.json");
4837
5324
  let indexEntries = null;
4838
5325
  try {
4839
5326
  const indexContent = await readFile2(indexPath, "utf-8");
@@ -4846,7 +5333,7 @@ var ProjectDiscovery = class {
4846
5333
  for (const jsonlFile of jsonlFiles) {
4847
5334
  try {
4848
5335
  const sessionId = jsonlFile.replace(".jsonl", "");
4849
- const jsonlPath = join4(projectDir, jsonlFile);
5336
+ const jsonlPath = join5(projectDir, jsonlFile);
4850
5337
  const fileStat = await stat2(jsonlPath);
4851
5338
  const fileSize = fileStat.size;
4852
5339
  const existingSession = await this.pmStore.getSession(sessionId);
@@ -4881,7 +5368,7 @@ var ProjectDiscovery = class {
4881
5368
  * Process a single Claude project directory key.
4882
5369
  */
4883
5370
  async processClaudeProject(key, result) {
4884
- const projectDir = join4(this.claudeBaseDir, "projects", key);
5371
+ const projectDir = join5(this.claudeBaseDir, "projects", key);
4885
5372
  let fsPath = decodeClaudeKey(key);
4886
5373
  if (!fsPath) {
4887
5374
  fsPath = await this.resolvePathFromIndex(projectDir);
@@ -4907,6 +5394,8 @@ var ProjectDiscovery = class {
4907
5394
  await this.pmStore.upsertProject(updated);
4908
5395
  result.projectsUpdated = (result.projectsUpdated ?? 0) + 1;
4909
5396
  } else {
5397
+ if (fsPath && this.pmStore.isDeletedPath(fsPath)) return;
5398
+ if (this.pmStore.isDeletedPath(key)) return;
4910
5399
  const project = {
4911
5400
  id,
4912
5401
  name,
@@ -4933,7 +5422,7 @@ var ProjectDiscovery = class {
4933
5422
  */
4934
5423
  async resolvePathFromIndex(projectDir) {
4935
5424
  try {
4936
- const indexPath = join4(projectDir, "sessions-index.json");
5425
+ const indexPath = join5(projectDir, "sessions-index.json");
4937
5426
  const content = await readFile2(indexPath, "utf-8");
4938
5427
  const index = JSON.parse(content);
4939
5428
  const entry = index.entries?.find((e) => e.projectPath);
@@ -4948,11 +5437,11 @@ var ProjectDiscovery = class {
4948
5437
  */
4949
5438
  async indexSessionsForClaudeProject(projectId, claudeKey) {
4950
5439
  const counts = { discovered: 0, updated: 0 };
4951
- const projectDir = join4(this.claudeBaseDir, "projects", claudeKey);
5440
+ const projectDir = join5(this.claudeBaseDir, "projects", claudeKey);
4952
5441
  try {
4953
5442
  let indexEntries = null;
4954
5443
  try {
4955
- const indexPath = join4(projectDir, "sessions-index.json");
5444
+ const indexPath = join5(projectDir, "sessions-index.json");
4956
5445
  const indexContent = await readFile2(indexPath, "utf-8");
4957
5446
  const index = JSON.parse(indexContent);
4958
5447
  indexEntries = index.entries ?? [];
@@ -4963,7 +5452,7 @@ var ProjectDiscovery = class {
4963
5452
  for (const jsonlFile of jsonlFiles) {
4964
5453
  try {
4965
5454
  const sessionId = jsonlFile.replace(".jsonl", "");
4966
- const jsonlPath = join4(projectDir, jsonlFile);
5455
+ const jsonlPath = join5(projectDir, jsonlFile);
4967
5456
  const fileStat = await stat2(jsonlPath);
4968
5457
  const fileSize = fileStat.size;
4969
5458
  const existingSession = await this.pmStore.getSession(sessionId);
@@ -5141,7 +5630,6 @@ var ProjectDiscovery = class {
5141
5630
  };
5142
5631
 
5143
5632
  export {
5144
- __require,
5145
5633
  RingBuffer,
5146
5634
  EventStore,
5147
5635
  generateProjectId,
@@ -5154,6 +5642,11 @@ export {
5154
5642
  resolveTlsConfig,
5155
5643
  CollectorServer,
5156
5644
  ProjectManager,
5645
+ readProjectConfig,
5646
+ writeProjectConfig,
5647
+ scaffoldProjectConfig,
5648
+ resolveProjectAppNames,
5649
+ migrateProjectIds,
5157
5650
  AuthManager,
5158
5651
  generateApiKey,
5159
5652
  BUILT_IN_RULES,
@@ -5172,4 +5665,4 @@ export {
5172
5665
  parseSessionJsonl,
5173
5666
  ProjectDiscovery
5174
5667
  };
5175
- //# sourceMappingURL=chunk-HZWALDZM.js.map
5668
+ //# sourceMappingURL=chunk-MM44DN7Y.js.map