@geraldmaron/construct 1.0.23 → 1.0.24

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/README.md +0 -2
  2. package/bin/construct +15 -215
  3. package/lib/embed/inbox.mjs +6 -3
  4. package/lib/embed/recommendation-store.mjs +7 -289
  5. package/lib/embed/reconcile.mjs +2 -2
  6. package/lib/hooks/config-protection.mjs +4 -4
  7. package/lib/hooks/session-reflect.mjs +5 -1
  8. package/lib/intake/git-queue.mjs +195 -0
  9. package/lib/intake/queue.mjs +9 -16
  10. package/lib/mcp/tools/storage.mjs +2 -3
  11. package/lib/mcp-catalog.json +3 -3
  12. package/lib/mcp-manager.mjs +59 -3
  13. package/lib/observation-store.mjs +38 -166
  14. package/lib/orchestration/runtime.mjs +3 -2
  15. package/lib/reconcile/index.mjs +0 -2
  16. package/lib/service-manager.mjs +38 -256
  17. package/lib/setup.mjs +26 -426
  18. package/lib/status.mjs +3 -6
  19. package/lib/storage/admin.mjs +48 -325
  20. package/lib/storage/backend.mjs +10 -57
  21. package/lib/storage/hybrid-query.mjs +15 -196
  22. package/lib/storage/sync.mjs +36 -177
  23. package/lib/storage/vector-client.mjs +256 -235
  24. package/lib/strategy-store.mjs +35 -286
  25. package/lib/worker/entrypoint.mjs +6 -14
  26. package/package.json +6 -5
  27. package/platforms/claude/settings.template.json +0 -7
  28. package/scripts/sync-specialists.mjs +46 -12
  29. package/specialists/prompts/cx-qa.md +1 -1
  30. package/specialists/registry.json +0 -8
  31. package/templates/docs/construct_guide.md +1 -1
  32. package/db/schema/001_init.sql +0 -40
  33. package/db/schema/002_pgvector.sql +0 -182
  34. package/db/schema/003_intake.sql +0 -47
  35. package/db/schema/003_observation_reconciliation.sql +0 -14
  36. package/db/schema/004_recommendations.sql +0 -46
  37. package/db/schema/005_strategy.sql +0 -21
  38. package/db/schema/006_graph.sql +0 -24
  39. package/db/schema/007_tags.sql +0 -30
  40. package/db/schema/008_skill_usage.sql +0 -24
  41. package/db/schema/009_scheduler.sql +0 -14
  42. package/db/schema/010_cx_scores.sql +0 -51
  43. package/lib/intake/postgres-queue.mjs +0 -240
  44. package/lib/reconcile/postgres-namespace.mjs +0 -102
  45. package/lib/services/local-postgres.mjs +0 -15
  46. package/lib/storage/backup.mjs +0 -347
  47. package/lib/storage/migrations.mjs +0 -187
  48. package/lib/storage/postgres-backup.mjs +0 -124
  49. package/lib/storage/sql-store.mjs +0 -45
  50. package/lib/storage/store-version.mjs +0 -115
  51. package/lib/storage/unified-storage.mjs +0 -550
  52. package/lib/storage/vector-store.mjs +0 -100
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * lib/service-manager.mjs — start, stop, and describe Construct runtime services.
3
3
  *
4
- * Manages the dashboard process, local Postgres (Docker), memory server (cm),
5
- * and OpenCode. startServices() is the single entry point for `construct up` —
4
+ * Manages the dashboard process, memory server (cm), and OpenCode.
5
+ * startServices() is the single entry point for `construct up` —
6
6
  * spawns whatever is available and returns a result per service.
7
7
  */
8
8
  import { spawn, spawnSync } from 'node:child_process';
@@ -14,37 +14,14 @@ import { fileURLToPath } from 'node:url';
14
14
 
15
15
  import { findAvailablePort } from './host-capabilities.mjs';
16
16
  import { getUserEnvPath, loadConstructEnv, parseEnvFile, writeEnvValues } from './env-config.mjs';
17
- import { detectDockerCompose } from './setup.mjs';
18
- import { stashConstructDb, restoreConstructDb } from './storage/postgres-backup.mjs';
19
17
  import { runPressureRelease } from './runtime-pressure.mjs';
20
- import {
21
- pruneStashDir,
22
- verifyTelemetryKeys,
23
- isRemoteTelemetry,
24
- } from './services/local-postgres.mjs';
25
18
  import { resolveTraceBackend, telemetryProviderLabel } from './telemetry/client.mjs';
26
- import { postgresPort, postgresContainerName, memoryPort as derivedMemoryPort } from './home-namespace.mjs';
27
-
28
- const CONSTRUCT_PG_COMPOSE_DIR = 'services/postgres';
29
- const CONSTRUCT_PG_CONTAINER = postgresContainerName();
30
- const CONSTRUCT_PG_PORT = postgresPort();
31
- const CONSTRUCT_PG_HEALTH_RETRIES = 12;
32
- const CONSTRUCT_PG_HEALTH_INTERVAL_MS = 2000;
19
+ import { memoryPort as derivedMemoryPort } from './home-namespace.mjs';
33
20
 
34
21
  const DASHBOARD_STATE_FILE = 'dashboard.json';
35
22
 
36
- // The caller's `rootDir` is the project (for compose/context/traces), but the
37
- // dashboard and doctor entrypoints ship with the install, not the project. They
38
- // must resolve against the install root — derived from this module's own
39
- // location — or `construct init` spawns `<project>/lib/server/index.mjs` and the
40
- // process dies with MODULE_NOT_FOUND while still advertising the URL.
41
-
42
23
  const INSTALL_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
43
24
 
44
- // pruneStashDir and verifyTelemetryKeys now live in lib/services/telemetry-backend.mjs.
45
- // Re-exported below at module bottom for tests that already import from here.
46
-
47
-
48
25
  function runtimeStateDir(homeDir = os.homedir()) {
49
26
  return path.join(homeDir, '.construct', 'runtime');
50
27
  }
@@ -118,17 +95,8 @@ export function buildRuntimeRecoverySummary({ rootDir, homeDir = os.homedir(), r
118
95
  }
119
96
 
120
97
  export function isManagedConstructPostgresUrl(databaseUrl = '') {
121
- if (!databaseUrl) return false;
122
- try {
123
- const parsed = new URL(databaseUrl);
124
- const host = parsed.hostname;
125
- return (
126
- parsed.port === String(CONSTRUCT_PG_PORT) &&
127
- ['127.0.0.1', 'localhost', '::1'].includes(host)
128
- ) || host === CONSTRUCT_PG_CONTAINER;
129
- } catch {
130
- return databaseUrl.includes(`:${CONSTRUCT_PG_PORT}/`);
131
- }
98
+ // Construct no longer manages local Postgres containers.
99
+ return false;
132
100
  }
133
101
 
134
102
  function processExists(pid) {
@@ -296,11 +264,6 @@ export async function startDashboard({ rootDir, homeDir = os.homedir(), preferre
296
264
  });
297
265
  child.unref();
298
266
 
299
- // Confirm the process actually bound the port before persisting state or
300
- // advertising the URL. Without this the caller reports a dead PID/URL when the
301
- // server crashes on boot — the readiness gate turns a silent failure into an
302
- // honest, actionable one (the log tail names the cause).
303
-
304
267
  let ready = false;
305
268
  for (let i = 0; i < 30; i += 1) {
306
269
  if (await probeRuntimePort(port)) { ready = true; break; }
@@ -324,10 +287,6 @@ export async function startDashboard({ rootDir, homeDir = os.homedir(), preferre
324
287
  return { started: true, reused: false, ...state };
325
288
  }
326
289
 
327
- /**
328
- * Resolve runtime ports. Re-uses ports from config.env when the service is
329
- * already listening there (avoids port drift on repeated `construct up`).
330
- */
331
290
  export async function getRuntimePorts(homeDir = os.homedir(), {
332
291
  dashboardProbeFn = isDashboardRunning,
333
292
  memoryProbeFn = isMemoryRunning,
@@ -363,118 +322,12 @@ export async function describeRuntimeSupport() {
363
322
  }
364
323
  return {
365
324
  tmux: commandExists('tmux'),
366
- docker: commandExists('docker'),
367
325
  cm: commandExists('cm'),
368
326
  opencode: commandExists('opencode'),
369
327
  };
370
328
  }
371
329
 
372
- // ── Construct Postgres management ──────────────────────────────────────────
373
-
374
- function constructPgComposePath(rootDir) {
375
- return path.join(rootDir, CONSTRUCT_PG_COMPOSE_DIR, 'docker-compose.yml');
376
- }
377
-
378
- function isConstructPostgresRunning(spawnSyncFn = spawnSync) {
379
- const result = spawnSyncFn('docker', ['inspect', '--format', '{{.State.Running}}', CONSTRUCT_PG_CONTAINER], {
380
- encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
381
- });
382
- return result.stdout?.trim() === 'true';
383
- }
384
-
385
- function isConstructPostgresHealthy(spawnSyncFn = spawnSync) {
386
- const result = spawnSyncFn('docker', ['exec', CONSTRUCT_PG_CONTAINER, 'pg_isready', '-U', 'construct'], {
387
- encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
388
- });
389
- return result.status === 0;
390
- }
391
-
392
- async function waitForConstructPostgresHealthy({
393
- spawnSyncFn = spawnSync,
394
- maxRetries = CONSTRUCT_PG_HEALTH_RETRIES,
395
- intervalMs = CONSTRUCT_PG_HEALTH_INTERVAL_MS,
396
- } = {}) {
397
- for (let i = 0; i < maxRetries; i++) {
398
- if (isConstructPostgresHealthy(spawnSyncFn)) return true;
399
- await new Promise((r) => setTimeout(r, intervalMs));
400
- }
401
- return false;
402
- }
403
-
404
- function startConstructPostgres({ rootDir, homeDir = os.homedir(), spawnSyncFn = spawnSync, detectDockerComposeFn = detectDockerCompose } = {}) {
405
- const composeRunner = detectDockerComposeFn({ autoStart: true });
406
- if (!composeRunner) return { status: 'unavailable', note: 'Docker not available' };
407
-
408
- const composeFile = constructPgComposePath(rootDir);
409
- if (!fs.existsSync(composeFile)) return { status: 'unavailable', note: 'Postgres compose file not found — run construct init first' };
410
-
411
- const args = [...composeRunner.argsPrefix, '-p', CONSTRUCT_PG_CONTAINER, '-f', composeFile, 'up', '-d'];
412
- const r = spawnSyncFn(composeRunner.command, args, { stdio: 'pipe', encoding: 'utf8' });
413
- if (r.status === 0) return { status: 'started' };
414
- return { status: 'error', note: (r.stderr || '').trim().split('\n')[0] || 'compose up failed' };
415
- }
416
-
417
- function stopConstructPostgres({ rootDir, homeDir = os.homedir(), spawnSyncFn = spawnSync, detectDockerComposeFn = detectDockerCompose } = {}) {
418
- const composeRunner = detectDockerComposeFn();
419
- if (!composeRunner) return { status: 'skipped', note: 'Docker not available' };
420
-
421
- const composeFile = constructPgComposePath(rootDir);
422
- if (!fs.existsSync(composeFile)) return { status: 'skipped', note: 'no compose file' };
423
-
424
- const args = [...composeRunner.argsPrefix, '-p', CONSTRUCT_PG_CONTAINER, '-f', composeFile, 'down'];
425
- const r = spawnSyncFn(composeRunner.command, args, { stdio: 'pipe', encoding: 'utf8' });
426
- if (r.status === 0) return { status: 'stopped' };
427
- return { status: 'error', note: (r.stderr || '').trim().split('\n')[0] || 'compose down failed' };
428
- }
429
-
430
- function checkPgvectorEnabled(spawnSyncFn = spawnSync) {
431
- const result = spawnSyncFn('docker', [
432
- 'exec', CONSTRUCT_PG_CONTAINER, 'psql', '-U', 'construct', '-d', 'construct',
433
- '-tAc', "SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector')",
434
- ], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
435
- return result.stdout?.trim() === 't';
436
- }
437
-
438
- /**
439
- * On macOS, when the Docker CLI is installed but the daemon is down,
440
- * launch Docker Desktop and poll `docker info` up to `timeoutMs` for
441
- * the daemon to come up. Opt-out via `CONSTRUCT_DISABLE_DOCKER_AUTOSTART=1`
442
- * so users who keep Docker off aren't forced into it. No-ops on Linux
443
- * (systemd needs sudo) and Windows (different launcher).
444
- */
445
- async function tryStartDockerDaemon({
446
- env = process.env,
447
- spawnSyncFn = spawnSync,
448
- timeoutMs = 60_000,
449
- pollMs = 1500,
450
- } = {}) {
451
- if (env.CONSTRUCT_DISABLE_DOCKER_AUTOSTART === '1') return false;
452
- if (process.platform !== 'darwin') return false;
453
-
454
- // CLI must be present; without it, nothing to start.
455
- if (spawnSyncFn('command -v docker', { stdio: 'ignore', shell: true }).status !== 0) return false;
456
-
457
- // If the daemon is already up, no work needed.
458
- if (spawnSyncFn('docker', ['info'], { stdio: 'ignore' }).status === 0) return true;
459
-
460
- // Best-effort launch. `open -a Docker` returns immediately; the daemon
461
- // takes 5-30s to come fully up so we poll.
462
- spawnSyncFn('open', ['-a', 'Docker'], { stdio: 'ignore' });
463
-
464
- const deadline = Date.now() + timeoutMs;
465
- while (Date.now() < deadline) {
466
- await new Promise((r) => setTimeout(r, pollMs));
467
- if (spawnSyncFn('docker', ['info'], { stdio: 'ignore' }).status === 0) return true;
468
- }
469
- return false;
470
- }
471
-
472
- // The optional services a user can pick from with `construct dev --select` /
473
- // `--only=`. Pressure Guard (cleanup) and Doctor (the L0 supervisor daemon)
474
- // are infrastructure, always on — they are intentionally not selectable.
475
-
476
330
  export const SELECTABLE_SERVICES = Object.freeze([
477
- { key: 'postgres', label: 'Postgres', description: 'Managed pgvector container (or external DATABASE_URL).' },
478
331
  { key: 'dashboard', label: 'Dashboard', description: 'Local operations dashboard on http://127.0.0.1:4242.' },
479
332
  { key: 'telemetry', label: 'Telemetry', description: 'Trace export / local JSONL traces.' },
480
333
  { key: 'memory', label: 'Memory (cm)', description: 'Persistent memory service (cm).' },
@@ -488,36 +341,24 @@ export async function startServices({
488
341
  describeRuntimeSupportFn = describeRuntimeSupport,
489
342
  getRuntimePortsFn = getRuntimePorts,
490
343
  startDashboardFn = startDashboard,
491
- detectDockerComposeFn = detectDockerCompose,
492
344
  loadConstructEnvFn = loadConstructEnv,
493
345
  spawnDetachedFn = spawnDetached,
494
- verifyTelemetryKeysFn = verifyTelemetryKeys,
495
346
  memoryProbeFn = isMemoryRunning,
496
347
  openCodeProbeFn = isOpenCodeRunning,
497
348
  runPressureReleaseFn = runPressureRelease,
498
- tryStartDockerDaemonFn = tryStartDockerDaemon,
499
349
  } = {}) {
500
350
  const support = await describeRuntimeSupportFn();
501
351
  const ports = await getRuntimePortsFn(homeDir);
502
352
  const envPath = getUserEnvPath(homeDir);
503
353
 
504
- // A null selection means "start everything" (the default). A Set restricts
505
- // startup to the chosen optional services; always-on infrastructure ignores it.
506
-
507
354
  const wants = (key) => selected === null || selected.has(key);
508
355
 
509
- // On macOS, auto-launch Docker Desktop when the CLI is present but the
510
- // daemon is down. Polls up to 60s so downstream Postgres startup sees a working
511
- // compose runner instead of "Docker not available".
512
- await tryStartDockerDaemonFn({ env: { ...process.env, HOME: homeDir }, timeoutMs: 5000 });
513
-
514
356
  writeEnvValues(envPath, {
515
357
  DASHBOARD_PORT: String(ports.dashboard),
516
358
  MEMORY_PORT: String(ports.memory),
517
359
  BRIDGE_PORT: String(ports.bridge),
518
360
  });
519
361
 
520
- // Construct Postgres — start if DATABASE_URL points to managed container
521
362
  const liveEnv = loadConstructEnvFn({ rootDir, homeDir });
522
363
  const results = [];
523
364
  const pressureReport = runPressureReleaseFn({ env: { ...liveEnv, HOME: homeDir } });
@@ -529,40 +370,6 @@ export async function startServices({
529
370
  note: `terminated ${pressureReport.killed.length} stale process(es)`,
530
371
  });
531
372
  }
532
- const databaseUrl = liveEnv.DATABASE_URL || '';
533
- const usesManagedPostgres = isManagedConstructPostgresUrl(databaseUrl);
534
-
535
- if (wants('postgres') && usesManagedPostgres) {
536
- if (!isConstructPostgresRunning()) {
537
- const pgStart = startConstructPostgres({ rootDir, homeDir, spawnSyncFn: spawnSync, detectDockerComposeFn: detectDockerComposeFn });
538
- if (pgStart.status === 'started') {
539
- const healthy = await waitForConstructPostgresHealthy();
540
- if (healthy) {
541
- const pgvector = checkPgvectorEnabled();
542
- results.push({
543
- name: 'Postgres',
544
- url: `postgresql://127.0.0.1:${CONSTRUCT_PG_PORT}/construct`,
545
- status: pgvector ? 'started' : 'degraded',
546
- note: pgvector ? 'pgvector enabled' : 'pgvector not installed — semantic search unavailable',
547
- });
548
- } else {
549
- results.push({ name: 'Postgres', status: 'degraded', note: 'container started but health check timed out' });
550
- }
551
- } else {
552
- results.push({ name: 'Postgres', status: 'error', note: pgStart.note });
553
- }
554
- } else {
555
- const pgvector = checkPgvectorEnabled();
556
- results.push({
557
- name: 'Postgres',
558
- url: `postgresql://127.0.0.1:${CONSTRUCT_PG_PORT}/construct`,
559
- status: 'reused',
560
- note: pgvector ? 'pgvector enabled' : 'pgvector not installed',
561
- });
562
- }
563
- } else if (wants('postgres') && databaseUrl) {
564
- results.push({ name: 'Postgres', url: databaseUrl, status: 'configured', note: 'external database' });
565
- }
566
373
 
567
374
  // Dashboard
568
375
  if (wants('dashboard')) {
@@ -575,38 +382,38 @@ export async function startServices({
575
382
  const telemetryUrl = liveEnv.CONSTRUCT_TELEMETRY_URL ?? '';
576
383
  const traceBackend = resolveTraceBackend(liveEnv);
577
384
  if (wants('telemetry')) {
578
- if (traceBackend === 'local') {
579
- results.push({
580
- name: 'Telemetry',
581
- url: path.join(rootDir, '.cx', 'traces'),
582
- status: 'configured',
583
- note: 'local JSONL traces; remote export not configured',
584
- });
585
- } else if (traceBackend === 'none') {
586
- results.push({
587
- name: 'Telemetry',
588
- url: path.join(rootDir, '.cx', 'traces'),
589
- status: 'configured',
590
- note: 'remote export disabled; local JSONL traces preserved',
591
- });
592
- } else if (traceBackend === 'otel') {
593
- const endpoint = liveEnv.CONSTRUCT_OTEL_EXPORTER_OTLP_ENDPOINT || '';
594
- results.push({
595
- name: 'Telemetry',
596
- url: endpoint || undefined,
597
- status: endpoint ? 'configured' : 'unavailable',
598
- note: endpoint ? `OTLP export (${telemetryProviderLabel(liveEnv)})` : 'CONSTRUCT_OTEL_EXPORTER_OTLP_ENDPOINT not set',
599
- });
600
- } else if (telemetryUrl && (isRemoteTelemetry(telemetryUrl) || traceBackend === 'langfuse' || traceBackend === 'http')) {
601
- results.push({
602
- name: 'Telemetry',
603
- url: telemetryUrl,
604
- status: 'configured',
605
- note: `${telemetryProviderLabel(liveEnv)} export — ${telemetryUrl}`,
606
- });
607
- } else {
608
- results.push({ name: 'Telemetry', status: 'unavailable', note: 'remote export requested but endpoint is not configured' });
609
- }
385
+ if (traceBackend === 'local') {
386
+ results.push({
387
+ name: 'Telemetry',
388
+ url: path.join(rootDir, '.cx', 'traces'),
389
+ status: 'configured',
390
+ note: 'local JSONL traces; remote export not configured',
391
+ });
392
+ } else if (traceBackend === 'none') {
393
+ results.push({
394
+ name: 'Telemetry',
395
+ url: path.join(rootDir, '.cx', 'traces'),
396
+ status: 'configured',
397
+ note: 'remote export disabled; local JSONL traces preserved',
398
+ });
399
+ } else if (traceBackend === 'otel') {
400
+ const endpoint = liveEnv.CONSTRUCT_OTEL_EXPORTER_OTLP_ENDPOINT || '';
401
+ results.push({
402
+ name: 'Telemetry',
403
+ url: endpoint || undefined,
404
+ status: endpoint ? 'configured' : 'unavailable',
405
+ note: endpoint ? `OTLP export (${telemetryProviderLabel(liveEnv)})` : 'CONSTRUCT_OTEL_EXPORTER_OTLP_ENDPOINT not set',
406
+ });
407
+ } else if (telemetryUrl && (traceBackend === 'langfuse' || traceBackend === 'http')) {
408
+ results.push({
409
+ name: 'Telemetry',
410
+ url: telemetryUrl,
411
+ status: 'configured',
412
+ note: `${telemetryProviderLabel(liveEnv)} export — ${telemetryUrl}`,
413
+ });
414
+ } else {
415
+ results.push({ name: 'Telemetry', status: 'unavailable', note: 'remote export requested but endpoint is not configured' });
416
+ }
610
417
  }
611
418
 
612
419
  // Memory (cm)
@@ -650,9 +457,6 @@ export async function startServices({
650
457
  };
651
458
  }
652
459
 
653
- // Surface service probe failures into the role-framework event bus so cx-sre
654
- // can be invoked when a critical service is down. Non-blocking, best effort.
655
-
656
460
  async function emitServiceFailures(results) {
657
461
  if (process.env.CONSTRUCT_ROLES === 'off') return;
658
462
  const failed = results.filter((r) => r.status === 'error');
@@ -670,17 +474,9 @@ async function emitServiceFailures(results) {
670
474
  } catch { /* best effort */ }
671
475
  }
672
476
 
673
- // Exported for testing only
674
- export { verifyTelemetryKeys as _verifyTelemetryKeys, pruneStashDir as _pruneStashDir };
675
-
676
- /**
677
- * Kill every process listening on a given TCP port (best-effort, non-fatal).
678
- * Returns true if at least one PID was killed.
679
- */
680
477
  function killPortOwners(port, spawnSyncFn = spawnSync) {
681
478
  if (!port || !Number.isInteger(port) || port <= 0) return false;
682
479
  try {
683
- // lsof works on macOS and Linux; -t returns just PIDs
684
480
  const result = spawnSyncFn('lsof', ['-t', `-i:${port}`], { encoding: 'utf8' });
685
481
  const pids = (result.stdout || '').trim().split(/\s+/).filter(Boolean).map(Number).filter((n) => n > 0);
686
482
  if (pids.length === 0) return false;
@@ -695,21 +491,10 @@ function killPortOwners(port, spawnSyncFn = spawnSync) {
695
491
 
696
492
  export async function stopServices({
697
493
  homeDir = os.homedir(),
698
- rootDir,
699
494
  spawnSyncFn = spawnSync,
700
- detectDockerComposeFn = detectDockerCompose,
701
495
  } = {}) {
702
496
  const results = [];
703
497
 
704
- // Stash construct Postgres data before any container is stopped so data
705
- // survives Docker restarts and machine reboots.
706
- const constructDbStash = stashConstructDb({ homeDir, spawnSyncFn });
707
-
708
- // Construct Postgres
709
- const pgStop = stopConstructPostgres({ rootDir, homeDir, spawnSyncFn, detectDockerComposeFn });
710
- results.push({ name: 'Postgres', status: pgStop.status });
711
-
712
- // Doctor (L0 daemon)
713
498
  const doctor = stopDoctor(homeDir);
714
499
  if (doctor.stopped) {
715
500
  results.push({ name: 'Doctor', status: 'stopped', note: `pid ${doctor.pid}` });
@@ -717,7 +502,6 @@ export async function stopServices({
717
502
  results.push({ name: 'Doctor', status: 'cleaned', note: 'stale state file removed' });
718
503
  }
719
504
 
720
- // Dashboard
721
505
  const dashboard = stopDashboard(homeDir);
722
506
  if (dashboard.stopped) {
723
507
  results.push({ name: 'Dashboard', status: 'stopped', note: `pid ${dashboard.pid}, port ${dashboard.port}` });
@@ -727,18 +511,16 @@ export async function stopServices({
727
511
  results.push({ name: 'Dashboard', status: 'not-running' });
728
512
  }
729
513
 
730
- // Memory (cm) — find port from config.env or fall back to default
731
514
  const envPath = getUserEnvPath(homeDir);
732
515
  const envValues = parseEnvFile(envPath);
733
516
  const memoryPort = Number(envValues.MEMORY_PORT) || derivedMemoryPort();
734
517
  const cmKilled = killPortOwners(memoryPort, spawnSyncFn);
735
518
  results.push({ name: 'Memory (cm)', status: cmKilled ? 'stopped' : 'not-running' });
736
519
 
737
- // OpenCode — find port from config.env or fall back to default
738
520
  const bridgePort = Number(envValues.BRIDGE_PORT) || 5173;
739
521
  const openCodeKilled = killPortOwners(bridgePort, spawnSyncFn);
740
522
  results.push({ name: 'OpenCode', status: openCodeKilled ? 'stopped' : 'not-running' });
741
523
 
742
524
  const stopped = results.filter((r) => r.status === 'stopped' || r.status === 'cleaned').map((r) => r.name);
743
- return { stopped, results, constructDbStash };
525
+ return { stopped, results };
744
526
  }