@schoolai/shipyard 3.9.1 → 3.10.0-rc.20260609.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 (61) hide show
  1. package/dist/{auth-UF3MLB77.js → auth-GGM253LQ.js} +3 -3
  2. package/dist/capability-detector-worker.js +9 -9
  3. package/dist/{chunk-LBTLMT5Z.js → chunk-2EQOL57Z.js} +2 -2
  4. package/dist/{chunk-CVMNGYPR.js → chunk-3WEEGJJN.js} +2 -2
  5. package/dist/{chunk-DGX2QR6G.js → chunk-4PBXNWJV.js} +26 -3
  6. package/dist/chunk-4PBXNWJV.js.map +1 -0
  7. package/dist/{chunk-K3QG7S6V.js → chunk-4THTCNVI.js} +4 -4
  8. package/dist/{chunk-K3QG7S6V.js.map → chunk-4THTCNVI.js.map} +1 -1
  9. package/dist/{chunk-7OD3UJUP.js → chunk-6LINHACK.js} +29 -9
  10. package/dist/chunk-6LINHACK.js.map +1 -0
  11. package/dist/{chunk-APVDHUPT.js → chunk-AEUTFH76.js} +28 -4
  12. package/dist/chunk-AEUTFH76.js.map +1 -0
  13. package/dist/{chunk-WYP4NTFE.js → chunk-GM6MH4CD.js} +2 -2
  14. package/dist/{chunk-G7W4GFUC.js → chunk-IJHF4OM4.js} +2 -2
  15. package/dist/{chunk-2I5XDMUD.js → chunk-KRX7OJER.js} +5 -5
  16. package/dist/{chunk-7AB4NH6T.js → chunk-Q2HUVPOL.js} +38 -10
  17. package/dist/chunk-Q2HUVPOL.js.map +1 -0
  18. package/dist/{chunk-3SXIJEPM.js → chunk-QJP7JCIS.js} +31 -13
  19. package/dist/chunk-QJP7JCIS.js.map +1 -0
  20. package/dist/{chunk-MCDZOOAI.js → chunk-RW2OTTUA.js} +50 -15
  21. package/dist/chunk-RW2OTTUA.js.map +1 -0
  22. package/dist/{chunk-AVF7LE7Q.js → chunk-WMOR5Q6C.js} +181 -23
  23. package/dist/chunk-WMOR5Q6C.js.map +1 -0
  24. package/dist/{chunk-MLEGFDFW.js → chunk-Z37T5W6S.js} +13 -2
  25. package/dist/chunk-Z37T5W6S.js.map +1 -0
  26. package/dist/cursor-runner.js +4 -4
  27. package/dist/electron-utility.js +5 -5
  28. package/dist/{git-repo-723BKZIH.js → git-repo-QNGPCJLI.js} +6 -4
  29. package/dist/index.js +8 -8
  30. package/dist/{logger-KICM6IPJ.js → logger-2F3CBS3V.js} +7 -5
  31. package/dist/{login-OKFUEGZW.js → login-NZKH63H7.js} +7 -7
  32. package/dist/{logout-5RGCOHCI.js → logout-HY3MPOY5.js} +5 -5
  33. package/dist/{mcp-servers-CAUI2K5W.js → mcp-servers-ICHOWXZB.js} +4 -4
  34. package/dist/{roi-Q3BQLIO7.js → roi-YM5OOWHG.js} +3 -3
  35. package/dist/{serve-76X367VD.js → serve-FJT6POBH.js} +64212 -62725
  36. package/dist/{serve-76X367VD.js.map → serve-FJT6POBH.js.map} +1 -1
  37. package/dist/{skills-YEZJMAPT.js → skills-W2Y6TWHA.js} +2 -2
  38. package/dist/{start-QOGAKRUP.js → start-MD62XHS6.js} +11 -10
  39. package/dist/{start-QOGAKRUP.js.map → start-MD62XHS6.js.map} +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunk-3SXIJEPM.js.map +0 -1
  42. package/dist/chunk-7AB4NH6T.js.map +0 -1
  43. package/dist/chunk-7OD3UJUP.js.map +0 -1
  44. package/dist/chunk-APVDHUPT.js.map +0 -1
  45. package/dist/chunk-AVF7LE7Q.js.map +0 -1
  46. package/dist/chunk-DGX2QR6G.js.map +0 -1
  47. package/dist/chunk-MCDZOOAI.js.map +0 -1
  48. package/dist/chunk-MLEGFDFW.js.map +0 -1
  49. /package/dist/{auth-UF3MLB77.js.map → auth-GGM253LQ.js.map} +0 -0
  50. /package/dist/{chunk-LBTLMT5Z.js.map → chunk-2EQOL57Z.js.map} +0 -0
  51. /package/dist/{chunk-CVMNGYPR.js.map → chunk-3WEEGJJN.js.map} +0 -0
  52. /package/dist/{chunk-WYP4NTFE.js.map → chunk-GM6MH4CD.js.map} +0 -0
  53. /package/dist/{chunk-G7W4GFUC.js.map → chunk-IJHF4OM4.js.map} +0 -0
  54. /package/dist/{chunk-2I5XDMUD.js.map → chunk-KRX7OJER.js.map} +0 -0
  55. /package/dist/{git-repo-723BKZIH.js.map → git-repo-QNGPCJLI.js.map} +0 -0
  56. /package/dist/{logger-KICM6IPJ.js.map → logger-2F3CBS3V.js.map} +0 -0
  57. /package/dist/{login-OKFUEGZW.js.map → login-NZKH63H7.js.map} +0 -0
  58. /package/dist/{logout-5RGCOHCI.js.map → logout-HY3MPOY5.js.map} +0 -0
  59. /package/dist/{mcp-servers-CAUI2K5W.js.map → mcp-servers-ICHOWXZB.js.map} +0 -0
  60. /package/dist/{roi-Q3BQLIO7.js.map → roi-YM5OOWHG.js.map} +0 -0
  61. /package/dist/{skills-YEZJMAPT.js.map → skills-W2Y6TWHA.js.map} +0 -0
@@ -7,10 +7,10 @@ import {
7
7
  } from "./chunk-RR6V6SNM.js";
8
8
  import {
9
9
  logger
10
- } from "./chunk-3SXIJEPM.js";
10
+ } from "./chunk-QJP7JCIS.js";
11
11
  import {
12
12
  getShipyardHome
13
- } from "./chunk-MLEGFDFW.js";
13
+ } from "./chunk-Z37T5W6S.js";
14
14
  import {
15
15
  external_exports
16
16
  } from "./chunk-CNR7O5YH.js";
@@ -252,7 +252,7 @@ async function refreshRepoPathsInBackground(log) {
252
252
  const startGen = cacheGeneration;
253
253
  const start = Date.now();
254
254
  try {
255
- const { findGitRepos: findGitRepos2, discoverWorktrees: discoverWorktrees2 } = await import("./git-repo-723BKZIH.js");
255
+ const { findGitRepos: findGitRepos2, discoverWorktrees: discoverWorktrees2 } = await import("./git-repo-QNGPCJLI.js");
256
256
  const { homedir: homedir2 } = await import("os");
257
257
  const repos = await findGitRepos2(homedir2());
258
258
  const worktrees = await discoverWorktrees2(repos);
@@ -407,16 +407,30 @@ async function withProbeTimeout(op, timeoutMs) {
407
407
  );
408
408
  });
409
409
  }
410
+ var NEGATIVE_CACHE_TTL_MS = 5e3;
410
411
  var gitRepoCache = /* @__PURE__ */ new Map();
411
- async function isGitRepo(cwd) {
412
+ function readGitRepoCache(cwd, now = Date.now()) {
412
413
  const cached = gitRepoCache.get(cwd);
414
+ if (cached === void 0) return void 0;
415
+ if (cached.isRepo) return true;
416
+ if (cached.expiresAt !== void 0 && now >= cached.expiresAt) {
417
+ gitRepoCache.delete(cwd);
418
+ return void 0;
419
+ }
420
+ return false;
421
+ }
422
+ async function isGitRepo(cwd) {
423
+ const cached = readGitRepoCache(cwd);
413
424
  if (cached !== void 0) return cached;
414
425
  try {
415
426
  await runWithTimeout("git", ["rev-parse", "--git-dir"], cwd, TIMEOUT_MS);
416
- gitRepoCache.set(cwd, true);
427
+ gitRepoCache.set(cwd, { isRepo: true });
417
428
  return true;
418
429
  } catch {
419
- gitRepoCache.set(cwd, false);
430
+ gitRepoCache.set(cwd, {
431
+ isRepo: false,
432
+ expiresAt: Date.now() + NEGATIVE_CACHE_TTL_MS
433
+ });
420
434
  return false;
421
435
  }
422
436
  }
@@ -611,7 +625,7 @@ async function detectEnvironments(lastKnown) {
611
625
  repoPaths = await findGitRepos(homedir2());
612
626
  } catch (err) {
613
627
  if (lastKnown && lastKnown.length > 0) {
614
- const { logger: logger2 } = await import("./logger-KICM6IPJ.js");
628
+ const { logger: logger2 } = await import("./logger-2F3CBS3V.js");
615
629
  logger2.warn(
616
630
  { err, lastKnownCount: lastKnown.length },
617
631
  "detectEnvironments findGitRepos threw \u2014 preserving lastKnown"
@@ -621,7 +635,7 @@ async function detectEnvironments(lastKnown) {
621
635
  return [];
622
636
  }
623
637
  if (repoPaths.length === 0 && lastKnown && lastKnown.length > 0) {
624
- const { logger: logger2 } = await import("./logger-KICM6IPJ.js");
638
+ const { logger: logger2 } = await import("./logger-2F3CBS3V.js");
625
639
  logger2.warn(
626
640
  { lastKnownCount: lastKnown.length },
627
641
  "detectEnvironments walk empty but lastKnown non-empty \u2014 preserving lastKnown"
@@ -714,6 +728,11 @@ var _testing = {
714
728
  withProbeTimeout,
715
729
  GIT_PROBE_TIMEOUT_MS,
716
730
  SLOW_PATH_LOG_THRESHOLD_MS,
731
+ NEGATIVE_CACHE_TTL_MS,
732
+ readGitRepoCache,
733
+ resetGitRepoCache: () => {
734
+ gitRepoCache.clear();
735
+ },
717
736
  resetGhAvailableCache: () => {
718
737
  ghAvailableCache = false;
719
738
  }
@@ -731,6 +750,7 @@ export {
731
750
  addWorktreeToCache,
732
751
  removeWorktreeFromCache,
733
752
  hydrateAndLogRepoPathsCache,
753
+ NEGATIVE_CACHE_TTL_MS,
734
754
  gitRepoCache,
735
755
  isGitRepo,
736
756
  isGhAvailable,
@@ -744,4 +764,4 @@ export {
744
764
  discoverWorktrees,
745
765
  _testing
746
766
  };
747
- //# sourceMappingURL=chunk-7OD3UJUP.js.map
767
+ //# sourceMappingURL=chunk-6LINHACK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/capabilities/git-repo.ts","../src/shared/capabilities/environments-cache.ts","../src/shared/capabilities/repo-paths-cache.ts","../src/shared/capabilities/shell.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { GitRepoInfo } from '@shipyard/session';\nimport { logger } from '../logger.js';\nimport {\n type CacheEntry,\n type GitCacheFingerprint,\n getGitCacheFingerprint,\n isCacheValid,\n loadCache,\n resolveHeadPath,\n saveCache,\n} from './environments-cache.js';\nimport { gitExecSafe } from './git-pool.js';\nimport { getCachedRepoPaths, setCachedRepoPaths } from './repo-paths-cache.js';\nimport { run, runWithTimeout, TIMEOUT_MS } from './shell.js';\n\n/**\n * Per-path hard timeout for the environments walk. Lowered from\n * `TIMEOUT_MS` (5s) so a single hung path (NFS stall, symlink loop,\n * dead network drive — the May 2026 detect_environments wedge scenario)\n * can't pin the whole detector for tens of seconds. The walk continues\n * past timed-out paths; the per-path slow log surfaces the culprit on\n * the next investigation.\n */\nconst GIT_PROBE_TIMEOUT_MS = 3_000;\n\n/** Single-path duration past which we emit a slow-log event for diagnosis. */\nconst SLOW_PATH_LOG_THRESHOLD_MS = 2_000;\n\n/**\n * Race `op` against a `timeoutMs` deadline. On timeout, resolves to `null`\n * — the walker treats this the same as a git-call failure (skip the path\n * but continue the walk). Used so a single hung NFS path can't pin the\n * whole environments walk.\n */\nasync function withProbeTimeout<T>(op: Promise<T>, timeoutMs: number): Promise<T | null> {\n return new Promise<T | null>((resolve) => {\n const timer = setTimeout(() => resolve(null), timeoutMs);\n timer.unref?.();\n op.then(\n (v) => {\n clearTimeout(timer);\n resolve(v);\n },\n () => {\n clearTimeout(timer);\n resolve(null);\n }\n );\n });\n}\n\n/**\n * Negative cache TTL for `isGitRepo`. A directory that is not yet a git\n * repo may become one (`git init`) during the daemon lifetime; positives\n * are permanent (a work-tree never stops being one) but negatives must\n * expire so we re-probe periodically.\n */\nexport const NEGATIVE_CACHE_TTL_MS = 5_000;\n\ntype GitRepoCacheEntry = { isRepo: boolean; expiresAt?: number };\n\n/** Cache of directory paths to whether they are inside a git repository. */\nexport const gitRepoCache = new Map<string, GitRepoCacheEntry>();\n\nfunction readGitRepoCache(cwd: string, now = Date.now()): boolean | undefined {\n const cached = gitRepoCache.get(cwd);\n if (cached === undefined) return undefined;\n if (cached.isRepo) return true;\n if (cached.expiresAt !== undefined && now >= cached.expiresAt) {\n gitRepoCache.delete(cwd);\n return undefined;\n }\n return false;\n}\n\n/**\n * Check whether the given directory is inside a git work-tree.\n * Results are cached per directory so repeated calls (e.g. during\n * debounced diff captures) do not re-spawn git processes.\n */\nexport async function isGitRepo(cwd: string): Promise<boolean> {\n const cached = readGitRepoCache(cwd);\n if (cached !== undefined) return cached;\n try {\n await runWithTimeout('git', ['rev-parse', '--git-dir'], cwd, TIMEOUT_MS);\n gitRepoCache.set(cwd, { isRepo: true });\n return true;\n } catch {\n gitRepoCache.set(cwd, {\n isRepo: false,\n expiresAt: Date.now() + NEGATIVE_CACHE_TTL_MS,\n });\n return false;\n }\n}\n\nlet ghAvailableCache = false;\n\nexport async function isGhAvailable(): Promise<boolean> {\n if (ghAvailableCache) return true;\n try {\n await run('which', ['gh']);\n ghAvailableCache = true;\n return true;\n } catch {\n return false;\n }\n}\n\nconst topLevelCache = new Map<string, string>();\n\nexport async function getGitTopLevel(cwd: string): Promise<string> {\n const cached = topLevelCache.get(cwd);\n if (cached !== undefined) return cached;\n const topLevel = (\n await runWithTimeout('git', ['rev-parse', '--show-toplevel'], cwd, TIMEOUT_MS)\n ).trim();\n topLevelCache.set(cwd, topLevel);\n return topLevel;\n}\n\nexport function parseOwnerRepo(remoteUrl: string): { owner: string; repo: string } | null {\n const match = remoteUrl.match(/github\\.com[:/]([^/]+)\\/([^/.]+)/);\n if (!match?.[1] || !match[2]) return null;\n return { owner: match[1], repo: match[2] };\n}\n\n/**\n * Look up the repo's default branch via the `origin/HEAD` symbolic ref.\n * Returns the branch name (e.g. `main`) or null when the symbolic ref\n * isn't set (rare — happens for repos cloned without `git remote\n * set-head` or local-only repos).\n *\n * The result is cached for 5 seconds via the per-cwd OneShotPool in\n * git-pool.ts so repeated calls from serve-factory and stack-detection\n * per request coalesce to a single subprocess.\n */\nexport async function getRepoDefaultBranch(cwd: string): Promise<string | null> {\n const head = await gitExecSafe(cwd, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);\n if (!head) return null;\n return head.replace(/^origin\\//, '') || null;\n}\n\nexport async function isAncestor(\n ancestor: string,\n descendant: string,\n cwd: string\n): Promise<boolean> {\n try {\n await runWithTimeout('git', ['merge-base', '--is-ancestor', ancestor, descendant], cwd, 5_000);\n return true;\n } catch {\n return false;\n }\n}\n\nconst EXCLUDE_DIRS = new Set([\n 'node_modules',\n 'Library',\n 'Applications',\n 'Pictures',\n 'Music',\n 'Movies',\n 'go',\n '.Trash',\n]);\n\nconst MAX_DEPTH = 4;\nconst WORKTREE_DISCOVERY_CONCURRENCY = 8;\nconst REPO_METADATA_CONCURRENCY = 16;\n\nasync function mapLimit<T, R>(\n items: readonly T[],\n limit: number,\n mapper: (item: T, index: number) => Promise<R>\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n const indexedItems = items.map((item, index) => ({ item, index }));\n let nextIndex = 0;\n const workerCount = Math.min(Math.max(1, limit), items.length);\n\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n for (;;) {\n const next = indexedItems[nextIndex];\n nextIndex += 1;\n if (!next) return;\n results[next.index] = await mapper(next.item, next.index);\n }\n })\n );\n\n return results;\n}\n\nexport async function findGitRepos(dir: string, depth = 0): Promise<string[]> {\n if (depth > MAX_DEPTH) return [];\n\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name === '.git') {\n return [dir];\n }\n }\n\n if (depth >= MAX_DEPTH) return [];\n\n const promises: Promise<string[]>[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.')) continue;\n if (EXCLUDE_DIRS.has(entry.name)) continue;\n promises.push(findGitRepos(join(dir, entry.name), depth + 1));\n }\n\n const results = await Promise.all(promises);\n return results.flat();\n } catch {\n return [];\n }\n}\n\n/**\n * Read the current branch name by parsing `.git/HEAD` directly.\n *\n * Format of `.git/HEAD`:\n * - Checked-out branch: `ref: refs/heads/<branch>\\n`\n * - Detached HEAD: `<sha>\\n`\n *\n * First attempts to read `.git/HEAD` directly (fast path for normal repos).\n * Falls back to `resolveHeadPath` for linked worktrees where `.git` is a\n * file pointing to a gitdir rather than a directory.\n */\nasync function readBranchFromHead(repoPath: string): Promise<string> {\n const directHeadPath = join(repoPath, '.git', 'HEAD');\n try {\n const direct = await readFile(directHeadPath, 'utf8');\n const trimmed = direct.trim();\n if (trimmed.startsWith('ref: refs/heads/')) {\n return trimmed.slice('ref: refs/heads/'.length);\n }\n if (!trimmed.startsWith('gitdir:')) {\n return 'HEAD';\n }\n } catch {}\n\n try {\n const headPath = await resolveHeadPath(repoPath);\n if (!headPath) return 'HEAD';\n const contents = await readFile(headPath, 'utf8');\n const trimmed2 = contents.trim();\n if (trimmed2.startsWith('ref: refs/heads/')) {\n return trimmed2.slice('ref: refs/heads/'.length);\n }\n return 'HEAD';\n } catch {\n return 'HEAD';\n }\n}\n\nexport async function getRepoMetadata(\n repoPath: string,\n cached?: CacheEntry\n): Promise<GitRepoInfo | null> {\n try {\n if (cached) {\n return {\n path: repoPath,\n name: basename(repoPath),\n branch: cached.branch,\n ...(cached.remote !== null && { remote: cached.remote }),\n };\n }\n\n /**\n * Both reads are bounded by `GIT_PROBE_TIMEOUT_MS`. `gitExecSafe`\n * already has the 5s `OneShotPool` TTL but no hard kill — a hung\n * `git remote get-url` would pin the walker indefinitely without\n * this race. `readBranchFromHead` is plain `fs.readFile` and is\n * usually fast, but the timeout is cheap insurance against an NFS\n * stall on the HEAD file itself.\n */\n const [branchResult, remoteResult] = await Promise.all([\n withProbeTimeout(readBranchFromHead(repoPath), GIT_PROBE_TIMEOUT_MS),\n withProbeTimeout(\n gitExecSafe(repoPath, ['remote', 'get-url', 'origin']),\n GIT_PROBE_TIMEOUT_MS\n ),\n ]);\n const branch = branchResult ?? 'HEAD';\n const remote = remoteResult ? remoteResult || undefined : undefined;\n\n return {\n path: repoPath,\n name: basename(repoPath),\n branch,\n ...(remote && { remote }),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Process a single repo path during the environments walk: resolve cache hit,\n * run the timed metadata probe, update `nextCache`, and emit slow/timeout logs.\n * Extracted from the `mapLimit` callback in `detectEnvironments` to keep that\n * function under the cognitive-complexity ceiling.\n */\nasync function processRepoPaths(\n path: string,\n idx: number,\n fingerprints: GitCacheFingerprint[],\n prevCache: Map<string, CacheEntry>,\n nextCache: Map<string, CacheEntry>\n): Promise<GitRepoInfo | null> {\n const currentFingerprint = fingerprints[idx] ?? { headMtimeMs: null, configMtimeMs: null };\n const prev = prevCache.get(path);\n const cacheHit =\n prev !== undefined &&\n currentFingerprint.headMtimeMs !== null &&\n isCacheValid(prev, currentFingerprint);\n\n const start = Date.now();\n const info = await withProbeTimeout(\n getRepoMetadata(path, cacheHit ? prev : undefined),\n GIT_PROBE_TIMEOUT_MS\n );\n const durationMs = Date.now() - start;\n\n if (info === null && durationMs >= GIT_PROBE_TIMEOUT_MS * 0.9) {\n logger.warn(\n { event: 'environments_walk_path_timed_out', path, durationMs },\n 'environments walk skipped path past per-path timeout'\n );\n } else if (durationMs >= SLOW_PATH_LOG_THRESHOLD_MS) {\n logger.warn(\n { event: 'environments_walk_path_slow', path, durationMs },\n 'environments walk path resolved slowly'\n );\n }\n\n if (info !== null && currentFingerprint.headMtimeMs !== null) {\n nextCache.set(path, {\n path,\n branch: info.branch,\n remote: info.remote ?? null,\n headMtimeMs: currentFingerprint.headMtimeMs,\n configMtimeMs: currentFingerprint.configMtimeMs,\n cachedAt: cacheHit && prev ? prev.cachedAt : Date.now(),\n });\n }\n return info;\n}\n\n/**\n * Detect git environments under $HOME.\n *\n * `lastKnown` preserves the previously-detected environment list when the\n * underlying `findGitRepos` walk returns `[]` for a non-authoritative reason\n * (root readdir failure: EMFILE, EACCES, EBUSY). Without this, a transient\n * filesystem hiccup in the 30s capability-refresh tick collapses environments\n * to `[]`, which `applyFreshCapabilities` writes wholesale, and the browser\n * briefly renders \"No environment\" until the next successful tick. Mirror of\n * the `lastKnownAgents` pattern in `agents.ts`.\n *\n * Discrimination: a successful walk that genuinely finds zero repos is\n * indistinguishable from a swallowed-error walk in the current shape (the\n * inner `try/catch` in `findGitRepos` returns `[]` on readdir failure). The\n * `lastKnown.length > 0` guard means we only preserve when there's something\n * worth preserving; a clean machine that genuinely has zero repos still gets\n * `[]`.\n */\nexport async function detectEnvironments(lastKnown?: GitRepoInfo[]): Promise<GitRepoInfo[]> {\n const { homedir } = await import('node:os');\n\n /**\n * The repo-paths cache (`repo-paths-cache.ts`) holds the result of the\n * `findGitRepos(homedir())` walk and the N parallel `git worktree list`\n * spawns — together ~12-15s on a busy machine. Per-repo branch info is\n * still resolved fresh below via `getRepoMetadata` (cached separately by\n * git HEAD + config mtimes in `environments-cache.ts`). The list cache lives\n * for the daemon lifetime; `invalidateRepoPaths()` is called by\n * worktree-service (add/remove), capability-watcher (FSEvents rescan),\n * and explicit force-refresh paths.\n */\n const cachedPaths = getCachedRepoPaths();\n let repoPaths: string[];\n let worktreePaths: string[];\n\n if (cachedPaths) {\n repoPaths = cachedPaths.repos;\n worktreePaths = cachedPaths.worktrees;\n } else {\n try {\n repoPaths = await findGitRepos(homedir());\n } catch (err) {\n if (lastKnown && lastKnown.length > 0) {\n const { logger } = await import('../logger.js');\n logger.warn(\n { err, lastKnownCount: lastKnown.length },\n 'detectEnvironments findGitRepos threw — preserving lastKnown'\n );\n return lastKnown;\n }\n return [];\n }\n\n if (repoPaths.length === 0 && lastKnown && lastKnown.length > 0) {\n /**\n * Walk returned empty but we know there used to be repos — almost\n * certainly a transient root-readdir error swallowed inside\n * `findGitRepos`. Preserve rather than wipe.\n */\n const { logger } = await import('../logger.js');\n logger.warn(\n { lastKnownCount: lastKnown.length },\n 'detectEnvironments walk empty but lastKnown non-empty — preserving lastKnown'\n );\n return lastKnown;\n }\n\n worktreePaths = await discoverWorktrees(repoPaths);\n setCachedRepoPaths(repoPaths, worktreePaths);\n }\n\n const allPaths = [...new Set([...repoPaths, ...worktreePaths])];\n\n const prevCache = await loadCache();\n const fingerprints = await mapLimit(allPaths, REPO_METADATA_CONCURRENCY, getGitCacheFingerprint);\n const nextCache = new Map<string, CacheEntry>();\n\n /**\n * Per-path timing + hard timeout. Walk continues past timed-out paths so\n * one bad mount (NFS, dead symlink, removed volume) can't pin the entire\n * environments slice. Slow paths log a structured event for diagnosis.\n */\n const resolved = await mapLimit(allPaths, REPO_METADATA_CONCURRENCY, (path, idx) =>\n processRepoPaths(path, idx, fingerprints, prevCache, nextCache)\n );\n\n await saveCache(nextCache);\n\n return resolved.filter((info): info is GitRepoInfo => info !== null);\n}\n\n/**\n * For each repo, run `git worktree list --porcelain` to discover\n * linked worktrees (which may live in dot-directories like .claude/worktrees/).\n *\n * Dedupes across repos: linked worktrees that are themselves git repos get\n * walked AGAIN by `findGitRepos`, and running `git worktree list` from each\n * one reports the same set of paths back. Without dedupe the cache grew to\n * 7,420 entries on a machine with only 149 unique worktrees (2026-05-14\n * boot wedge investigation). `dedupePerRepoWorktreeOutputs` collapses to\n * unique paths while preserving the first-seen ordering for determinism.\n */\nexport async function discoverWorktrees(repoPaths: string[]): Promise<string[]> {\n const results = await mapLimit(\n repoPaths,\n WORKTREE_DISCOVERY_CONCURRENCY,\n async (repoPath): Promise<PromiseSettledResult<string[]>> => {\n try {\n return { status: 'fulfilled', value: await listLinkedWorktrees(repoPath) };\n } catch (reason) {\n return { status: 'rejected', reason };\n }\n }\n );\n return dedupePerRepoWorktreeOutputs(results);\n}\n\nfunction dedupePerRepoWorktreeOutputs(results: PromiseSettledResult<string[]>[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const r of results) {\n if (r.status !== 'fulfilled') continue;\n for (const wt of r.value) {\n if (seen.has(wt)) continue;\n seen.add(wt);\n out.push(wt);\n }\n }\n return out;\n}\n\n/**\n * Enumerate linked worktrees for a git repository by reading the\n * `.git/worktrees/` directory directly rather than spawning a git subprocess.\n *\n * Git's on-disk format (gitrepository-layout(5), stable since git 2.5):\n * .git/worktrees/<name>/gitdir — contains the path to the worktree's\n * `.git` file, e.g. `/path/to/wt/.git`\n *\n * Stripping `/.git` from that content yields the worktree path. Missing\n * `.git/worktrees/` directory means no linked worktrees — return [].\n * Orphaned entries (gitdir file missing/unreadable) are silently skipped.\n */\nasync function listLinkedWorktrees(repoPath: string): Promise<string[]> {\n const gitWorktreesDir = join(repoPath, '.git', 'worktrees');\n let entries: string[];\n try {\n entries = await readdir(gitWorktreesDir);\n } catch {\n return [];\n }\n\n const paths = await Promise.all(\n entries.map(async (name) => {\n try {\n const gitdirFile = join(gitWorktreesDir, name, 'gitdir');\n const content = await readFile(gitdirFile, 'utf8');\n const wtGitPath = content.trim();\n if (wtGitPath.endsWith('/.git')) {\n return wtGitPath.slice(0, -'/.git'.length);\n }\n return null;\n } catch {\n return null;\n }\n })\n );\n\n return paths.filter((p): p is string => p !== null && p !== repoPath);\n}\n\n/** @internal kept for tests that exercise parse logic on porcelain output */\nfunction parseWorktreeListOutput(output: string, repoPath: string): string[] {\n const paths: string[] = [];\n for (const line of output.split('\\n')) {\n if (line.startsWith('worktree ')) {\n const wt = line.slice('worktree '.length).trim();\n if (wt && wt !== repoPath) paths.push(wt);\n }\n }\n return paths;\n}\n\nexport const _testing = {\n parseWorktreeListOutput,\n dedupePerRepoWorktreeOutputs,\n listLinkedWorktrees,\n readBranchFromHead,\n mapLimit,\n withProbeTimeout,\n GIT_PROBE_TIMEOUT_MS,\n SLOW_PATH_LOG_THRESHOLD_MS,\n NEGATIVE_CACHE_TTL_MS,\n readGitRepoCache,\n resetGitRepoCache: () => {\n gitRepoCache.clear();\n },\n resetGhAvailableCache: () => {\n ghAvailableCache = false;\n },\n};\n","import { mkdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { z } from 'zod';\nimport { getShipyardHome } from '../env.js';\nimport { isEnoent } from '../fs-utils.js';\nimport { logger } from '../logger.js';\n\nconst CacheEntrySchema = z.object({\n path: z.string(),\n branch: z.string(),\n remote: z.string().nullable(),\n headMtimeMs: z.number(),\n /**\n * Added after the original cache shape. Optional so old disk caches parse,\n * but `isCacheValid` treats missing as invalid and refreshes once.\n */\n configMtimeMs: z.number().nullable().optional(),\n cachedAt: z.number(),\n});\n\nconst CacheFileSchema = z.object({\n entries: z.record(z.string(), CacheEntrySchema),\n});\n\nexport type CacheEntry = z.infer<typeof CacheEntrySchema>;\ntype CacheFile = z.infer<typeof CacheFileSchema>;\nexport interface GitCacheFingerprint {\n headMtimeMs: number | null;\n configMtimeMs: number | null;\n}\n\nfunction cacheFilePath(): string {\n return join(getShipyardHome(), 'data', 'environments-cache.json');\n}\n\nlet cacheMigrationDone = false;\n\nexport async function loadCache(): Promise<Map<string, CacheEntry>> {\n const filePath = cacheFilePath();\n try {\n const raw = await readFile(filePath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n const result = CacheFileSchema.safeParse(parsed);\n if (!result.success) {\n logger.debug({ path: filePath }, 'environments-cache: malformed, treating as empty');\n cacheMigrationDone = true;\n return new Map();\n }\n const cache = new Map(Object.entries(result.data.entries));\n if (!cacheMigrationDone) {\n cacheMigrationDone = true;\n await migrateConfigMtimes(cache);\n }\n return cache;\n } catch (err) {\n if (isEnoent(err)) {\n cacheMigrationDone = true;\n return new Map();\n }\n logger.debug({ err }, 'environments-cache: read failed, treating as empty');\n return new Map();\n }\n}\n\n/**\n * One-shot migration: entries written before #3619 lack `configMtimeMs`.\n * `isCacheValid` would force a re-spawn for every such entry, creating a\n * git subprocess storm on first boot after upgrade. Backfill the field by\n * reading `.git/config` mtimes directly, then persist so subsequent boots\n * hit the cache normally.\n */\nasync function migrateConfigMtimes(cache: Map<string, CacheEntry>): Promise<void> {\n const stale = [...cache.values()].filter((e) => e.configMtimeMs === undefined);\n if (stale.length === 0) return;\n await Promise.all(\n stale.map(async (entry) => {\n const fp = await getGitCacheFingerprint(entry.path);\n cache.set(entry.path, { ...entry, configMtimeMs: fp.configMtimeMs });\n })\n );\n logger.info({ count: stale.length }, 'environments-cache: cache_migration_seeded');\n await saveCache(cache);\n}\n\n/**\n * Resolve git metadata paths for a repository or linked worktree.\n *\n * - If `<path>/.git` is a directory, metadata lives inside that directory.\n * - If `<path>/.git` is a file, it contains `gitdir: <path>`. HEAD lives in\n * that worktree gitdir, while remotes live in the common git dir's config.\n * - Returns null on any error (missing, unreadable, malformed).\n */\nasync function readGitdirFile(dotGitPath: string): Promise<string | null> {\n const contents = await readFile(dotGitPath, 'utf8');\n const match = contents.match(/^gitdir:\\s*(.+)\\s*$/m);\n if (!match?.[1]) return null;\n const gitdirRaw = match[1].trim();\n return isAbsolute(gitdirRaw) ? gitdirRaw : resolve(dirname(dotGitPath), gitdirRaw);\n}\n\nasync function resolveCommonGitDir(gitdir: string): Promise<string> {\n try {\n const raw = await readFile(join(gitdir, 'commondir'), 'utf8');\n const commonDirRaw = raw.trim();\n if (commonDirRaw === '') return gitdir;\n return isAbsolute(commonDirRaw) ? commonDirRaw : resolve(gitdir, commonDirRaw);\n } catch {\n return gitdir;\n }\n}\n\nexport async function resolveHeadPath(worktreePath: string): Promise<string | null> {\n const paths = await resolveGitMetadataPaths(worktreePath);\n return paths?.headPath ?? null;\n}\n\nexport async function resolveGitMetadataPaths(\n worktreePath: string\n): Promise<{ headPath: string; configPath: string | null } | null> {\n const dotGitPath = join(worktreePath, '.git');\n try {\n const dotGitStat = await stat(dotGitPath);\n if (dotGitStat.isDirectory()) {\n return { headPath: join(dotGitPath, 'HEAD'), configPath: join(dotGitPath, 'config') };\n }\n if (dotGitStat.isFile()) {\n const gitdir = await readGitdirFile(dotGitPath);\n if (!gitdir) return null;\n const commonGitDir = await resolveCommonGitDir(gitdir);\n return { headPath: join(gitdir, 'HEAD'), configPath: join(commonGitDir, 'config') };\n }\n return null;\n } catch {\n return null;\n }\n}\n\nasync function getPathMtime(path: string | null): Promise<number | null> {\n if (!path) return null;\n try {\n const pathStat = await stat(path);\n return pathStat.mtimeMs;\n } catch {\n return null;\n }\n}\n\nexport async function getGitCacheFingerprint(worktreePath: string): Promise<GitCacheFingerprint> {\n const paths = await resolveGitMetadataPaths(worktreePath);\n if (!paths) return { headMtimeMs: null, configMtimeMs: null };\n const [headMtimeMs, configMtimeMs] = await Promise.all([\n getPathMtime(paths.headPath),\n getPathMtime(paths.configPath),\n ]);\n return { headMtimeMs, configMtimeMs };\n}\n\nexport function isCacheValid(entry: CacheEntry, current: GitCacheFingerprint): boolean {\n /**\n * `.git/HEAD` invalidates branch, and `.git/config` invalidates remote.\n * Entries without `configMtimeMs` are soft-migrated at `loadCache` time\n * (see `migrateConfigMtimes`), so by the time this is called the field\n * is always present. Treat `undefined` the same as `null` for safety.\n */\n return (\n entry.headMtimeMs === current.headMtimeMs &&\n (entry.configMtimeMs ?? null) === current.configMtimeMs\n );\n}\n\nexport async function saveCache(entries: Map<string, CacheEntry>): Promise<void> {\n const filePath = cacheFilePath();\n const tmpPath = `${filePath}.tmp`;\n try {\n await mkdir(dirname(filePath), { recursive: true });\n /**\n * Merge-on-save: re-read disk immediately before writing so a concurrent\n * saveCache call's entries are preserved rather than erased (new entries\n * win for paths present in both — they are fresher).\n */\n const current = await loadCache();\n for (const [path, entry] of entries) {\n current.set(path, entry);\n }\n const payload: CacheFile = { entries: Object.fromEntries(current) };\n await writeFile(tmpPath, JSON.stringify(payload), 'utf8');\n await rename(tmpPath, filePath);\n } catch (err) {\n logger.debug({ err, filePath }, 'environments-cache: save failed');\n void unlink(tmpPath).catch(() => {});\n }\n}\n","/**\n * In-memory cache of the list of git repo paths under $HOME and the linked\n * worktree paths discovered via `git worktree list --porcelain` for each.\n *\n * Distinct from `environments-cache.ts` (which caches per-repo branch/remote\n * info on disk, keyed by git HEAD + config mtimes). This one caches the LIST of repos and\n * worktrees — i.e. the `findGitRepos(homedir())` walk and the N parallel\n * `git worktree list` spawns. Those are the ~12-50s cost on a busy machine.\n *\n * Lifetime: persisted to disk at `~/.shipyard/data/repo-paths-cache.json`\n * and rehydrated on every daemon boot. The previous implementation rejected\n * the disk cache outright when its age exceeded 1h — which is almost every\n * reboot in practice — and forced the 30s `findGitRepos($HOME)` walk to fire\n * on each cold boot. We now serve any structurally-valid cache regardless of\n * age and kick off a background re-walk when the cache is older than 5\n * minutes. Boot time drops from 30-50s to milliseconds; freshness is\n * restored ~10-30s after boot when the background walk completes.\n *\n * Invalidation:\n * - `invalidateRepoPaths()` — called by:\n * * capability-watcher.ts (FSEvents rescan + escalation paths)\n * * explicit force-refresh control message\n * - `addWorktreeToCache()` / `removeWorktreeFromCache()` — incremental\n * update used by worktree create/remove. Avoids the cold-walk recompute\n * storm that otherwise stalls the event loop 12-15s and kills the\n * daemon's WebRTC + control channels mid-creation. If there is no\n * active cache (boot before first walk), the call is a no-op — the\n * next cold walk picks up the new state.\n *\n * The walk + worktree spawns ARE NOT triggered by the capability-watcher's\n * normal events: scoped re-detect (`refreshMcpServers` etc.) skips\n * `detectEnvironments` entirely and reuses the existing `daemon.capabilities\n * ?.environments`. The cache is only consulted by full `detectCapabilities`\n * runs (manual Refresh button, boot, cwd-changed).\n */\n\nimport { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { z } from 'zod';\nimport { getShipyardHome } from '../env.js';\nimport { isEnoent } from '../fs-utils.js';\nimport type { createChildLogger } from '../logger.js';\nimport { logger } from '../logger.js';\n\nexport {\n getCachedRepoPaths,\n hydrateAndLogRepoPathsCache,\n hydrateRepoPathsCacheFromDisk,\n setCachedRepoPaths,\n invalidateRepoPaths,\n addWorktreeToCache,\n removeWorktreeFromCache,\n};\n\nexport type { HydrateResult };\n\ninterface CachedEntry {\n repos: string[];\n worktrees: string[];\n populatedAt: number;\n}\n\ninterface HydrateResult {\n hit: boolean;\n stale: boolean;\n}\n\nconst RepoPathsDiskSchema = z.object({\n repos: z.array(z.string()),\n worktrees: z.array(z.string()),\n populatedAt: z.number(),\n});\n\nconst STALE_AFTER_MS_DEFAULT = 5 * 60 * 1000;\n\nlet cache: CachedEntry | null = null;\n/**\n * Monotonic counter incremented on every mutating call. Async disk operations\n * capture the generation at the moment they were scheduled and no-op when\n * the generation has changed by the time they get to the syscall. Without\n * this, `invalidateRepoPaths()` (which fires `void unlinkDisk()`) racing\n * with a subsequent `setCachedRepoPaths()` (which fires `void persistDisk()`)\n * could leave the disk in either state — and if `unlinkDisk` wins after\n * `persistDisk`'s rename succeeds, the next daemon restart pays the full\n * cold-walk cost despite a valid cache having just been written.\n */\nlet cacheGeneration = 0;\nlet backgroundRefreshInFlight = false;\n\nfunction cacheFilePath(): string {\n return join(getShipyardHome(), 'data', 'repo-paths-cache.json');\n}\n\nfunction getCachedRepoPaths(): { repos: string[]; worktrees: string[] } | null {\n if (!cache) return null;\n return { repos: cache.repos, worktrees: cache.worktrees };\n}\n\nfunction setCachedRepoPaths(repos: string[], worktrees: string[], now: number = Date.now()): void {\n cache = { repos, worktrees, populatedAt: now };\n cacheGeneration += 1;\n void persistDisk(cache, cacheGeneration);\n}\n\nfunction invalidateRepoPaths(): void {\n cache = null;\n cacheGeneration += 1;\n void unlinkDisk(cacheGeneration);\n}\n\n/**\n * Best-effort hydrate the in-memory cache from disk on daemon boot. Always\n * accepts a structurally-valid on-disk entry regardless of age — the boot\n * path needs the data immediately and any staleness is repaired by a\n * background re-walk scheduled by `hydrateAndLogRepoPathsCache`. Returns\n * `{ hit, stale }` so the caller knows whether to schedule that refresh.\n *\n * The `staleAfterMs` threshold defines when the cache is \"old enough to\n * re-walk in the background\" — not when it's \"too old to serve\". Default\n * 5 minutes; data older than that triggers a background refresh but is\n * still returned to the boot path so `detect_environments` sees a populated\n * cache and skips the 30s `findGitRepos` walk + `git worktree list` storm.\n *\n * Wired from `serve.ts` BEFORE `createSignalingHandle()` so the first\n * `detectEnvironments` call inside the background `runCapabilityDetection`\n * (#3360 — detection moved off the boot path) sees the populated in-memory\n * cache. Without this, every daemon restart pays the full cold-walk cost —\n * observed at 30-50s on busy machines and the dominant component of 42.5s\n * worst-case boot.\n */\nasync function hydrateRepoPathsCacheFromDisk(opts?: {\n staleAfterMs?: number;\n}): Promise<HydrateResult> {\n if (cache !== null) return { hit: false, stale: false };\n const filePath = cacheFilePath();\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf8');\n } catch (err: unknown) {\n if (isEnoent(err)) return { hit: false, stale: false };\n logger.debug({ err, filePath }, 'repo-paths-cache: read failed');\n return { hit: false, stale: false };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return { hit: false, stale: false };\n }\n const result = RepoPathsDiskSchema.safeParse(parsed);\n if (!result.success) return { hit: false, stale: false };\n\n /**\n * Race-safe: another caller may have populated the slot between our\n * `cache !== null` check and the disk read. Don't clobber the live entry.\n */\n if (cache !== null) return { hit: false, stale: false };\n\n const staleAfterMs = opts?.staleAfterMs ?? STALE_AFTER_MS_DEFAULT;\n const ageMs = Math.max(0, Date.now() - result.data.populatedAt);\n const stale = ageMs > staleAfterMs;\n\n cache = result.data;\n logger.info(\n { repos: result.data.repos.length, worktrees: result.data.worktrees.length, ageMs, stale },\n 'repo-paths-cache: hydrated from disk'\n );\n return { hit: true, stale };\n}\n\nasync function persistDisk(entry: CachedEntry, generation: number): Promise<void> {\n const filePath = cacheFilePath();\n const tmpPath = `${filePath}.tmp`;\n try {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(tmpPath, JSON.stringify(entry), 'utf8');\n /**\n * Drop the write if a later mutation (set/invalidate/add/remove) raced\n * us. The successor's persist/unlink will reach the rename target with\n * the right state. Without this, an in-flight rename can clobber the\n * successor's just-written file or, worse, race a fresh persist after\n * an invalidate.\n */\n if (generation !== cacheGeneration) {\n await unlink(tmpPath).catch(() => {});\n return;\n }\n await rename(tmpPath, filePath);\n } catch (err: unknown) {\n logger.debug({ err, filePath }, 'repo-paths-cache: save failed');\n void unlink(tmpPath).catch(() => {});\n }\n}\n\nasync function unlinkDisk(generation: number): Promise<void> {\n /**\n * Drop the unlink if a later `setCachedRepoPaths` already raced us — its\n * `persistDisk` will write the fresh file and the file the unlink would\n * have removed is the predecessor's. Without this generation check, an\n * `invalidate → set` sequence can end up with NO disk file even though\n * `set` persisted one.\n */\n if (generation !== cacheGeneration) return;\n try {\n await unlink(cacheFilePath());\n } catch {\n /** No-op — file may not exist yet. */\n }\n}\n\n/**\n * Append a freshly-created worktree to the existing cached result. Source\n * repo path is also added (deduped) because a brand-new repo can host a\n * brand-new worktree, and the next `detectEnvironments` call needs to see\n * both. No-op when there is no cache yet — the next cold walk will discover\n * the new entries naturally.\n */\nfunction addWorktreeToCache(worktreePath: string, sourceRepoPath: string): void {\n if (!cache) return;\n const repos = cache.repos.includes(sourceRepoPath)\n ? cache.repos\n : [...cache.repos, sourceRepoPath];\n const worktrees = cache.worktrees.includes(worktreePath)\n ? cache.worktrees\n : [...cache.worktrees, worktreePath];\n cache = { repos, worktrees, populatedAt: cache.populatedAt };\n cacheGeneration += 1;\n void persistDisk(cache, cacheGeneration);\n}\n\n/**\n * Splice a worktree path out of the cached worktree list. The source repo\n * stays — removing a single linked worktree never deletes its source. No-op\n * when there is no cache yet.\n */\nfunction removeWorktreeFromCache(worktreePath: string): void {\n if (!cache) return;\n const next = cache.worktrees.filter((p) => p !== worktreePath);\n if (next.length === cache.worktrees.length) return;\n cache = { repos: cache.repos, worktrees: next, populatedAt: cache.populatedAt };\n cacheGeneration += 1;\n void persistDisk(cache, cacheGeneration);\n}\n\n/**\n * Re-walk `$HOME` and rewrite the cache. Fired by `hydrateAndLogRepoPathsCache`\n * on boot when the hydrated cache is stale, restoring freshness ~10-30s after\n * the user is already interactive. Single-flight: a second call while one is\n * in progress is a no-op. Generation-guarded: if any synchronous mutation\n * (`setCachedRepoPaths`, `addWorktreeToCache`, `removeWorktreeFromCache`,\n * `invalidateRepoPaths`) lands while the walk is in flight, the refresh\n * result is dropped so it does not clobber a more-recent incremental update.\n *\n * Lazy-imports `./git-repo.js` to break the import cycle (`git-repo` already\n * imports this module).\n */\nasync function refreshRepoPathsInBackground(\n log: ReturnType<typeof createChildLogger>\n): Promise<void> {\n if (backgroundRefreshInFlight) return;\n backgroundRefreshInFlight = true;\n const startGen = cacheGeneration;\n const start = Date.now();\n try {\n const { findGitRepos, discoverWorktrees } = await import('./git-repo.js');\n const { homedir } = await import('node:os');\n const repos = await findGitRepos(homedir());\n const worktrees = await discoverWorktrees(repos);\n if (cacheGeneration !== startGen) {\n log.debug(\n { event: 'repo_paths_cache_refresh_dropped' },\n 'repo-paths-cache: background refresh dropped (cache mutated mid-walk)'\n );\n return;\n }\n /**\n * Defense in depth: a transient root-readdir failure inside\n * `findGitRepos` returns `[]`, indistinguishable from \"no repos exist\".\n * If the prior cache had entries, preserve it rather than wipe — the\n * next refresh (capability-watcher tick) will retry. Mirrors the same\n * guard inside `detectEnvironments`.\n */\n if (repos.length === 0 && cache && cache.repos.length > 0) {\n log.warn(\n {\n event: 'repo_paths_cache_refresh_empty_preserved',\n prevRepos: cache.repos.length,\n },\n 'repo-paths-cache: background refresh returned empty; preserving prior cache'\n );\n return;\n }\n setCachedRepoPaths(repos, worktrees);\n log.info(\n {\n event: 'repo_paths_cache_refresh_complete',\n repos: repos.length,\n worktrees: worktrees.length,\n durationMs: Date.now() - start,\n },\n 'repo-paths-cache: background refresh complete'\n );\n } catch (err) {\n log.warn(\n { err, event: 'repo_paths_cache_refresh_failed', durationMs: Date.now() - start },\n 'repo-paths-cache: background refresh failed'\n );\n } finally {\n backgroundRefreshInFlight = false;\n }\n}\n\n/**\n * Hydrate from disk and log the hit/miss outcome. Boot callers wrap this in\n * `bootstrapPhase('repo_paths_cache_hydrate', ...)` — the wrapper always\n * records `outcome: 'success'` because hydrate returns a result object (no\n * throw on ENOENT). The explicit log here is what surfaces whether the\n * upcoming `detect_environments` will pay the cold walk and whether a\n * background refresh was scheduled.\n */\nasync function hydrateAndLogRepoPathsCache(\n log: ReturnType<typeof createChildLogger>\n): Promise<boolean> {\n const result = await hydrateRepoPathsCacheFromDisk();\n log.info(\n {\n event: 'repo_paths_cache_hydrate_result',\n hit: result.hit,\n stale: result.stale,\n },\n result.hit\n ? result.stale\n ? 'repo-paths cache hydrated from disk (stale; background refresh scheduled)'\n : 'repo-paths cache hydrated from disk (fresh)'\n : 'repo-paths cache cold (walk will fire inside detect_environments)'\n );\n if (result.hit && result.stale) {\n void refreshRepoPathsInBackground(log);\n }\n return result.hit;\n}\n\nexport const _testing = {\n reset: invalidateRepoPaths,\n inspect: () => cache,\n refreshRepoPathsInBackground,\n cacheFilePath,\n isBackgroundRefreshInFlight: () => backgroundRefreshInFlight,\n};\n","import { execFile } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const TIMEOUT_MS = 5_000;\n/**\n * Cap on `claude auth status --json` during capability detection. Healthy\n * CLI responds in well under 1s; 15s was a worst-case-CLI-cold-start\n * accommodation that singlehandedly added up to 15s of boot stall when the\n * subprocess hung. 5s is generous slack while keeping the bootstrap\n * `detectCapabilities → signaling register` path bounded. See issue #3267.\n */\nexport const AUTH_STATUS_TIMEOUT_MS = 5_000;\n\n/**\n * Build the version-manager and user-local binary dirs for macOS.\n * These paths are absent when the daemon is launched from Finder/Spotlight\n * because macOS GUI apps inherit a stripped PATH that skips the user's\n * shell profile (where nvm/volta/mise/asdf entries normally live).\n *\n * Listed from most commonly needed (nvm, volta) to least common (cargo).\n * All are idempotently filtered by augmentPath — already-present entries\n * are never duplicated.\n */\nfunction buildDarwinVersionManagerPaths(home: string): string[] {\n return [\n join(home, '.nvm', 'current', 'bin'),\n join(home, '.volta', 'bin'),\n join(home, '.local', 'bin'),\n join(home, '.cargo', 'bin'),\n '/opt/homebrew/opt/node/bin',\n ];\n}\n\nexport const STANDARD_PATHS_BY_PLATFORM: Partial<Record<NodeJS.Platform, readonly string[]>> = {\n darwin: [\n '/usr/bin',\n '/usr/local/bin',\n '/opt/homebrew/bin',\n ...buildDarwinVersionManagerPaths(homedir()),\n ],\n linux: ['/usr/bin', '/usr/local/bin'],\n};\n\n/**\n * Daemons launched from Finder/Spotlight on macOS get a stripped PATH that may\n * omit /usr/bin where git lives. Prepend standard locations idempotently so\n * `execFile('git', ...)` resolves the binary. User-customized PATH entries\n * stay preferred — we only prepend dirs that aren't already present.\n */\nexport function augmentPath(currentPath: string, platform: NodeJS.Platform): string {\n const standardPaths = STANDARD_PATHS_BY_PLATFORM[platform];\n if (!standardPaths) return currentPath;\n\n const existing = currentPath.length > 0 ? currentPath.split(':') : [];\n const existingSet = new Set(existing);\n const toPrepend = standardPaths.filter((p) => !existingSet.has(p));\n if (toPrepend.length === 0) return currentPath;\n return existing.length > 0 ? `${toPrepend.join(':')}:${currentPath}` : toPrepend.join(':');\n}\n\nconst augmentedPath = augmentPath(process.env.PATH ?? '', process.platform);\nif (augmentedPath.length > 0) {\n process.env.PATH = augmentedPath;\n}\n\nexport function run(\n command: string,\n args: string[],\n cwd?: string,\n timeoutMs: number = TIMEOUT_MS\n): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(command, args, { timeout: timeoutMs, cwd }, (error, stdout) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(stdout.trim());\n });\n });\n}\n\nexport function runWithTimeout(\n command: string,\n args: string[],\n cwd: string,\n timeoutMs: number\n): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n command,\n args,\n { timeout: timeoutMs, cwd, maxBuffer: 10 * 1024 * 1024 },\n (error, stdout) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(stdout.trim());\n }\n );\n });\n}\n\nexport const DIFF_TIMEOUT_MS = 15_000;\nexport const MAX_DIFF_SIZE = 500_000;\n\nexport function isMaxBufferError(err: unknown): boolean {\n return err instanceof Error && err.message.includes('maxBuffer');\n}\n\nexport const BUFFER_OVERFLOW_MSG = '... diff too large (exceeded buffer limit) ...\\n';\n\nexport function truncateDiff(result: string): string {\n if (result.length <= MAX_DIFF_SIZE) return result;\n const boundary = result.lastIndexOf('\\ndiff --git ', MAX_DIFF_SIZE);\n const cutoff = boundary > 0 ? boundary : MAX_DIFF_SIZE;\n return `${result.slice(0, cutoff)}\\n\\n... diff truncated (exceeds 500KB) ...\\n`;\n}\n"],"mappings":"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","names":["readFile","join","cache","mkdir","readFile","rename","unlink","writeFile","dirname","join","cacheFilePath","join","cacheFilePath","readFile","mkdir","dirname","writeFile","unlink","rename","findGitRepos","discoverWorktrees","homedir","join","resolve","resolve","join","readFile","homedir","logger"]}
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getDaemonVersion
4
+ } from "./chunk-4THTCNVI.js";
2
5
  import {
3
6
  WorkerCommandCodec,
4
7
  WorkerReplyCodec
5
8
  } from "./chunk-NACJENDW.js";
6
9
  import {
7
10
  buildNodeSpawnEnv
8
- } from "./chunk-WYP4NTFE.js";
11
+ } from "./chunk-GM6MH4CD.js";
9
12
  import {
10
13
  createServiceSupervisor
11
14
  } from "./chunk-ZFKJAYAN.js";
@@ -15,7 +18,7 @@ import {
15
18
  import {
16
19
  resolveNodeExecPath,
17
20
  validateEnv
18
- } from "./chunk-MLEGFDFW.js";
21
+ } from "./chunk-Z37T5W6S.js";
19
22
 
20
23
  // src/services/metrics/metrics-collector.ts
21
24
  var NOOP_METRICS = {
@@ -38,6 +41,13 @@ var MetricsCollector = class {
38
41
  #authToken;
39
42
  #maxBatchSize;
40
43
  #log;
44
+ /**
45
+ * Resolved once at construction — the version is static for a process, and
46
+ * getDaemonVersion() can throw in a misconfigured packaged build, so we never
47
+ * want to call it per-event. Falls back to 'unknown' rather than failing
48
+ * telemetry capture if resolution throws.
49
+ */
50
+ #daemonVersion;
41
51
  #buffer = [];
42
52
  #timer = null;
43
53
  #disposed = false;
@@ -48,6 +58,7 @@ var MetricsCollector = class {
48
58
  this.#authToken = authToken;
49
59
  this.#maxBatchSize = opts?.maxBatchSize ?? 50;
50
60
  this.#log = opts?.log ?? NOOP_LOG;
61
+ this.#daemonVersion = resolveDaemonVersionSafely();
51
62
  const intervalMs = opts?.flushIntervalMs ?? 3e4;
52
63
  this.#timer = setInterval(() => {
53
64
  this.flush();
@@ -60,7 +71,13 @@ var MetricsCollector = class {
60
71
  this.#buffer.push({
61
72
  eventType,
62
73
  taskId,
63
- payload: properties ?? {},
74
+ /**
75
+ * Every event carries daemonVersion so fleet telemetry can be sliced by
76
+ * version — without it, triage can't distinguish 'fix not deployed' from
77
+ * 'fix ineffective'. Spread last so a stray caller field can't override
78
+ * the authoritative value.
79
+ */
80
+ payload: { ...properties ?? {}, daemonVersion: this.#daemonVersion },
64
81
  clientTimestamp: Date.now()
65
82
  });
66
83
  if (this.#buffer.length >= this.#maxBatchSize) {
@@ -121,6 +138,13 @@ var MetricsCollector = class {
121
138
  this.flush();
122
139
  }
123
140
  };
141
+ function resolveDaemonVersionSafely() {
142
+ try {
143
+ return getDaemonVersion();
144
+ } catch {
145
+ return "unknown";
146
+ }
147
+ }
124
148
  function createMetricsCollector(workerUrl, authToken, telemetryEnabled, log) {
125
149
  if (!telemetryEnabled || !workerUrl || !authToken) return NOOP_METRICS;
126
150
  return new MetricsCollector(workerUrl, authToken, { log });
@@ -1070,4 +1094,4 @@ export {
1070
1094
  shutdownFileWatcherGuard,
1071
1095
  guardedSubscribe
1072
1096
  };
1073
- //# sourceMappingURL=chunk-APVDHUPT.js.map
1097
+ //# sourceMappingURL=chunk-AEUTFH76.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/metrics/metrics-collector.ts","../src/shared/file-watcher-guard.ts","../src/services/watcher-worker/worker-supervisor.ts"],"sourcesContent":["import type { MetricsIngestRequest } from '@shipyard/session';\nimport { getDaemonVersion } from '../../shared/daemon-version.js';\n\n/**\n * Known event types emitted by the daemon. Documented in one place so the\n * grep target is unambiguous when wiring a new emitter or chasing a metric.\n *\n * - `daemon_catastrophic_restart` — boot detected a prior abnormal exit via\n * the heartbeat file (native crash, SIGABRT, SIGKILL). Payload:\n * `{ priorPid, priorHeartbeatAt, ageMs }` always, plus breadcrumb-derived\n * diagnostics when the prior run left a readable breadcrumb whose pid matches:\n * `lastPhase` (the boot/runtime phase it was last in), `lastError` (a\n * captured early-boot exception message, if any), `breadcrumbAgeMs` (age of\n * the last breadcrumb write). Without these the event was unrootcauseable —\n * they turn \"it died\" into \"it died in phase X with error Y\".\n * - `daemon_early_boot_crashed` — an exception escaped the daemon early-boot\n * path before the daemon stabilized (caught by the guard in `serve()` before\n * the entry-point `.catch()` exits the process). Payload: `{ phase, error,\n * stack? }`. The same error is also written to the breadcrumb so the NEXT\n * boot's `daemon_catastrophic_restart` carries it as `lastError`.\n * - `file_watcher_circuit_open` — file-watcher-guard tripped its breaker for\n * a path after repeated subscribe failures.\n * - `file_watcher_evicted` — guard evicted a `lazy` watcher to make room.\n * - `file_watcher_essential_evicted` — guard evicted an `essential` watcher\n * (should not happen in practice; warning telemetry).\n * - `file_watcher_subscribe_failed` — single subscribe attempt failed.\n * - `file_watcher_active_count` — periodic gauge of active watchers.\n * - `file_watcher_budget_exceeded` — guard rejected a subscribe because the\n * FSEvents budget would be overrun.\n * - `watcher_worker_started` — worker-supervisor forked the initial watcher\n * subprocess.\n * - `watcher_worker_died` — watcher subprocess exited abnormally\n * (SIGABRT/SIGSEGV/SIGBUS or non-zero code).\n * - `watcher_worker_respawned` — supervisor successfully re-forked the\n * subprocess and replayed its subscription map.\n * - `watcher_worker_circuit_open` — too many subprocess deaths in the\n * circuit window; supervisor opens cooldown before next probe.\n * - `watcher_worker_circuit_closed` — half-open probe succeeded (or a\n * normal subscribe_success closed an open cooldown); cooldown is reset.\n * - `daemon_supervisor_respawned` — outer CLI supervisor re-forked the\n * daemon child after an abnormal exit.\n * - `daemon_supervisor_circuit_open` — outer CLI supervisor gave up after\n * crash-loop and is exiting non-zero.\n * - `bootstrap_phase` — per-step timing+outcome emitted by the auto-instrumentation\n * primitive in `shared/bootstrap-phase.ts`. Payload: `{ phase, durationMs, outcome,\n * error? }`. Fires once per wrapped startup step (#3290).\n * - `startup_complete` — aggregate emitted at the end of daemon boot with\n * `{ totalMs, phaseCount, phases[] }`. Pairs with the same-named structured\n * log line for easy post-mortem.\n * - `run_stall_timeout` — Cursor runner silence watchdog fired.\n * - `request_task_state_replay_failed` — peer-requested task-state replay failed.\n * - `loro_recovery_failed` — CRDT recovery failed after a recoverable Loro panic.\n * - `mcp_server_connect_failed` — coordinator moved an MCP server into failed\n * because connect/probe/timeout failed.\n * - `mcp_server_needs_auth` — coordinator moved an MCP server into needs-auth\n * after an auth-class failure.\n * - `mcp_reconnect_failed` — subprocess-side MCP reconnect failed with a\n * transient/unclassified error.\n * - `mcp_reconnect_terminal` — subprocess-side reconnect failed with a\n * terminal reason that needs user action.\n * - `mcp_reconnect_terminal_unsupported` — reconnect hit a calm terminal\n * unsupported-server classification.\n * - `mcp_reauth_failed` — an MCP OAuth re-authentication flow failed, so the\n * user's recovery path for a needs-auth server is itself broken.\n * - `mcp_adopted_set_failed` — coordinator failed to push the resolved MCP set\n * into a newly adopted subprocess.\n * - `mcp_push_failed` — coordinator failed to repush the resolved MCP set into\n * a running subprocess.\n * - `harness_mcp_registration_failed` — an agent init did not report the\n * Shipyard harness MCP server as connected, so Shipyard tools may be absent.\n * - `codex_auth_divergence_detected` — Codex subprocess auth signal disagreed with disk.\n * - `cursor_auth_divergence_detected` — Cursor subprocess auth signal disagreed with Cursor.me().\n * - `event_loop_stall` — discrete event-loop stall above watchdog threshold.\n * - `machine_sleep_artifact` — watchdog lag too large to be actionable as a JS stall.\n * - `squirrel_install_stuck_recovered` — Electron main cleared a wedged Squirrel.Mac\n * staged install on launch so electron-updater can re-download.\n *\n * The wire format treats `eventType` as an opaque string — this list exists\n * to give type-aware editors something to autocomplete and to give code\n * review a single grep target. New event types are added here.\n */\nexport type DaemonMetricsEvent =\n | 'daemon_catastrophic_restart'\n | 'daemon_early_boot_crashed'\n | 'file_watcher_circuit_open'\n | 'file_watcher_evicted'\n | 'file_watcher_essential_evicted'\n | 'file_watcher_subscribe_failed'\n | 'file_watcher_active_count'\n | 'file_watcher_budget_exceeded'\n | 'watcher_worker_started'\n | 'watcher_worker_died'\n | 'watcher_worker_respawned'\n | 'watcher_worker_circuit_open'\n | 'watcher_worker_circuit_closed'\n | 'daemon_supervisor_respawned'\n | 'daemon_supervisor_circuit_open'\n | 'bootstrap_phase'\n | 'startup_complete'\n | 'run_stall_timeout'\n | 'request_task_state_replay_failed'\n | 'loro_recovery_failed'\n | 'mcp_server_connect_failed'\n | 'mcp_server_needs_auth'\n | 'mcp_reconnect_failed'\n | 'mcp_reconnect_terminal'\n | 'mcp_reconnect_terminal_unsupported'\n | 'mcp_reauth_failed'\n | 'mcp_adopted_set_failed'\n | 'mcp_push_failed'\n | 'harness_mcp_registration_failed'\n | 'codex_auth_divergence_detected'\n | 'cursor_auth_divergence_detected'\n | 'event_loop_stall'\n | 'machine_sleep_artifact'\n | 'squirrel_install_stuck_recovered';\n\nexport interface MetricsCapture {\n capture(eventType: DaemonMetricsEvent | string, properties?: Record<string, unknown>): void;\n dispose(): void;\n}\n\nexport const NOOP_METRICS: MetricsCapture = {\n capture() {},\n dispose() {},\n};\n\n/**\n * Sink for telemetry-delivery failures. Wired to the daemon logger at WARN so a\n * broken pipeline is visible at the default LOG_LEVEL. Without this, `flush()`\n * swallowed every non-2xx/error (`fetch().catch(() => {})`) — which is why a\n * fleet-wide ingest outage went unnoticed for 9 days.\n */\nexport type MetricsLogger = (entry: { event: string; [key: string]: unknown }) => void;\n\nconst NOOP_LOG: MetricsLogger = () => {};\n\n/** Min spacing between failure logs so a persistent outage doesn't itself become log spam. */\nconst FAILURE_LOG_THROTTLE_MS = 600_000;\n\ninterface MetricsCollectorOpts {\n flushIntervalMs?: number;\n maxBatchSize?: number;\n log?: MetricsLogger;\n}\n\ninterface BufferedEvent {\n eventType: string;\n taskId?: string;\n payload: Record<string, unknown>;\n clientTimestamp: number;\n}\n\nexport class MetricsCollector {\n readonly #workerUrl: string;\n /**\n * String OR a getter resolved at flush time. The daemon holds a static\n * boot-time token; a getter lets a long-running daemon pick up a token\n * refreshed in config.json (re-login) without restarting, so its telemetry\n * stops 401-ing on the stale token.\n */\n readonly #authToken: string | (() => string);\n readonly #maxBatchSize: number;\n readonly #log: MetricsLogger;\n /**\n * Resolved once at construction — the version is static for a process, and\n * getDaemonVersion() can throw in a misconfigured packaged build, so we never\n * want to call it per-event. Falls back to 'unknown' rather than failing\n * telemetry capture if resolution throws.\n */\n readonly #daemonVersion: string;\n #buffer: BufferedEvent[] = [];\n #timer: ReturnType<typeof setInterval> | null = null;\n #disposed = false;\n #lastFailureLogMs = 0;\n #suppressedFailures = 0;\n\n constructor(workerUrl: string, authToken: string | (() => string), opts?: MetricsCollectorOpts) {\n this.#workerUrl = workerUrl.replace(/\\/$/, '');\n this.#authToken = authToken;\n this.#maxBatchSize = opts?.maxBatchSize ?? 50;\n this.#log = opts?.log ?? NOOP_LOG;\n this.#daemonVersion = resolveDaemonVersionSafely();\n\n const intervalMs = opts?.flushIntervalMs ?? 30_000;\n this.#timer = setInterval(() => {\n this.flush();\n }, intervalMs);\n this.#timer.unref();\n }\n\n capture(eventType: string, properties?: Record<string, unknown>): void {\n if (this.#disposed) return;\n\n const taskId = typeof properties?.taskId === 'string' ? properties.taskId : undefined;\n\n this.#buffer.push({\n eventType,\n taskId,\n /**\n * Every event carries daemonVersion so fleet telemetry can be sliced by\n * version — without it, triage can't distinguish 'fix not deployed' from\n * 'fix ineffective'. Spread last so a stray caller field can't override\n * the authoritative value.\n */\n payload: { ...(properties ?? {}), daemonVersion: this.#daemonVersion },\n clientTimestamp: Date.now(),\n });\n\n if (this.#buffer.length >= this.#maxBatchSize) {\n this.flush();\n }\n }\n\n flush(): void {\n if (this.#buffer.length === 0) return;\n\n const events = this.#buffer;\n this.#buffer = [];\n\n const body: MetricsIngestRequest = { events };\n\n const token = typeof this.#authToken === 'function' ? this.#authToken() : this.#authToken;\n fetch(`${this.#workerUrl}/ingest`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(body),\n })\n .then((res) => {\n if (res.ok) {\n this.#suppressedFailures = 0;\n return;\n }\n this.#reportFailure({ status: res.status, droppedEvents: events.length });\n })\n .catch((err: unknown) => {\n this.#reportFailure({\n error: err instanceof Error ? err.message : String(err),\n droppedEvents: events.length,\n });\n });\n }\n\n /**\n * Surface a delivery failure at most once per throttle window, carrying the\n * count of failures suppressed since the last log so a persistent outage is\n * visible without flooding the log.\n */\n #reportFailure(detail: Record<string, unknown>): void {\n const now = Date.now();\n if (now - this.#lastFailureLogMs < FAILURE_LOG_THROTTLE_MS) {\n this.#suppressedFailures += 1;\n return;\n }\n this.#log({\n event: 'telemetry_ingest_failed',\n suppressedSinceLastLog: this.#suppressedFailures,\n ...detail,\n });\n this.#lastFailureLogMs = now;\n this.#suppressedFailures = 0;\n }\n\n dispose(): void {\n this.#disposed = true;\n if (this.#timer) {\n clearInterval(this.#timer);\n this.#timer = null;\n }\n this.flush();\n }\n}\n\nfunction resolveDaemonVersionSafely(): string {\n try {\n return getDaemonVersion();\n } catch {\n return 'unknown';\n }\n}\n\nexport function createMetricsCollector(\n workerUrl: string | undefined,\n authToken: string | (() => string) | undefined,\n telemetryEnabled: boolean,\n log: MetricsLogger\n): MetricsCapture {\n if (!telemetryEnabled || !workerUrl || !authToken) return NOOP_METRICS;\n return new MetricsCollector(workerUrl, authToken, { log });\n}\n","import { fork as childProcessFork } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type {\n AsyncSubscription,\n Event as ParcelEvent,\n Options as ParcelOptions,\n} from '@parcel/watcher';\nimport {\n createWatcherWorkerSupervisor,\n type SupervisorMetrics,\n type WatcherWorkerEventLike,\n type WatcherWorkerSupervisor,\n} from '../services/watcher-worker/worker-supervisor.js';\nimport { assertNever } from './assert-never.js';\nimport { validateEnv } from './env.js';\nimport { buildNodeSpawnEnv, resolveNodeExecPath } from './spawn-as-node.js';\n\n/**\n * Re-entry safety (AGENTS.md Invariant #11): synthetic 'evicted' dispatch is\n * deferred via queueMicrotask so we never re-enter @parcel/watcher's own\n * dispatch from inside this module.\n */\n\nexport type GuardTier = 'essential' | 'lazy';\nexport type GuardReason = 'initial' | 'escalation' | 'rescan';\n\n/**\n * Wider event shape than @parcel/watcher's. The guard injects synthetic\n * 'evicted' events into a watcher's callback when LRU pressure forces eviction;\n * consumers can react by re-establishing a subscription or doing a readdir\n * catch-up on the next read.\n */\nexport type GuardedEvent = ParcelEvent | { type: 'evicted'; path: string };\n\nexport type GuardedSubscribeCallback = (err: Error | null, events: GuardedEvent[]) => unknown;\n\nexport type GuardedSubscribeOptions = ParcelOptions;\n\nexport type FileWatcherGuardLogger = (entry: { event: string; [key: string]: unknown }) => void;\n\nexport interface FileWatcherGuardDeps {\n /**\n * Underlying watcher implementation. In production this routes through the\n * watcher worker subprocess via `WatcherWorkerSupervisor.subscribe`; the\n * supervisor's synthetic 'evicted' event (fired across worker respawns) is\n * forwarded to the consumer alongside the guard's own LRU-eviction synthetic\n * events. Tests inject a direct fake.\n */\n subscribe: (\n path: string,\n fn: (err: Error | null, events: GuardedEvent[]) => unknown,\n opts?: ParcelOptions\n ) => Promise<AsyncSubscription>;\n log: FileWatcherGuardLogger;\n /** Injected for deterministic testing. */\n now: () => number;\n /** Injected so tests can drive without real timers. */\n setTimeout: (fn: () => void, ms: number) => unknown;\n /** Injected so tests can cancel scheduled timers (e.g. supervisor shutdown). */\n clearTimeout: (timer: unknown) => void;\n}\n\nconst DEFAULT_MAX_ACTIVE_WATCHERS = 450;\nexport const MAX_ACTIVE_WATCHERS =\n validateEnv().SHIPYARD_FILE_WATCHER_MAX ?? DEFAULT_MAX_ACTIVE_WATCHERS;\n\nexport const STARTING_BACKOFF_MS_INITIAL = 250;\nexport const STARTING_BACKOFF_MS_ESCALATION = 1_000;\nexport const MAX_BACKOFF_MS = 30_000;\nexport const BACKOFF_RESET_AFTER_MS = 5 * 60_000;\n\nexport const CIRCUIT_FAILURES = 5;\nexport const CIRCUIT_WINDOW_MS = 60_000;\nexport const CIRCUIT_OPEN_MS = 5 * 60_000;\nexport const CIRCUIT_OPEN_MAX_MS = 30 * 60_000;\n\n/** Delay before the guard attempts to re-subscribe an evicted essential watcher. */\nexport const ESSENTIAL_RESUBSCRIBE_DELAY_MS = 1_000;\n\nexport interface AttemptState {\n /** Monotonic count of consecutive failures since last clean window. */\n failureCount: number;\n /** Current backoff value (next subscribe attempt waits this long). */\n backoffMs: number;\n /** Last failure timestamp, used to compute clean-window decay. */\n lastFailureAt: number;\n /** Set when circuit is open. */\n circuitOpenedAt: number | null;\n /** Cooldown duration for the currently-open circuit (doubles on re-open). */\n circuitCooldownMs: number;\n /** Most recent failure timestamps used to detect rate (5 within window). */\n recentFailures: number[];\n /** When circuit went half-open (one probe permitted). */\n halfOpenAt: number | null;\n}\n\nexport function makeInitialAttemptState(): AttemptState {\n return {\n failureCount: 0,\n backoffMs: 0,\n lastFailureAt: 0,\n circuitOpenedAt: null,\n circuitCooldownMs: CIRCUIT_OPEN_MS,\n recentFailures: [],\n halfOpenAt: null,\n };\n}\n\nfunction isCleanAttemptState(state: AttemptState): boolean {\n return (\n state.circuitOpenedAt === null &&\n state.failureCount === 0 &&\n state.backoffMs === 0 &&\n state.recentFailures.length === 0 &&\n state.halfOpenAt === null\n );\n}\n\nexport type DecisionEvent =\n | { kind: 'request_subscribe'; reason: GuardReason; activeCount: number }\n | { kind: 'subscribe_success' }\n | { kind: 'subscribe_failure' };\n\nexport type DecisionAction =\n | { kind: 'subscribe' }\n | { kind: 'evict_and_subscribe' }\n | { kind: 'wait'; ms: number }\n | { kind: 'reject_stub'; reason: 'circuit_open' }\n | { kind: 'noop' };\n\nexport interface DecisionResult {\n state: AttemptState;\n action: DecisionAction;\n /** Side-effect signals the shell should emit (logs). */\n signals: Array<\n { kind: 'circuit_opened' } | { kind: 'circuit_closed' } | { kind: 'circuit_probe' }\n >;\n}\n\n/**\n * Pure state-machine step. No I/O, deterministic — drives all retry / circuit /\n * backoff behavior. The shell interprets DecisionAction.\n */\nexport function decideAction(\n state: AttemptState,\n event: DecisionEvent,\n now: number\n): DecisionResult {\n switch (event.kind) {\n case 'request_subscribe':\n return decideRequest(state, event, now);\n case 'subscribe_success':\n return decideSuccess(state, now);\n case 'subscribe_failure':\n return decideFailure(state, now);\n default:\n return assertNever(event);\n }\n}\n\nfunction decideRequest(\n state: AttemptState,\n event: { kind: 'request_subscribe'; reason: GuardReason; activeCount: number },\n now: number\n): DecisionResult {\n /** Decay backoff after a long clean window. */\n const decayed = decayBackoff(state, now);\n\n if (decayed.circuitOpenedAt !== null) {\n const cooldownEnd = decayed.circuitOpenedAt + decayed.circuitCooldownMs;\n if (now < cooldownEnd) {\n return {\n state: decayed,\n action: { kind: 'reject_stub', reason: 'circuit_open' },\n signals: [],\n };\n }\n /** Half-open: allow ONE probe through; mark it. */\n return {\n state: { ...decayed, halfOpenAt: now },\n action:\n event.activeCount >= MAX_ACTIVE_WATCHERS\n ? { kind: 'evict_and_subscribe' }\n : { kind: 'subscribe' },\n signals: [{ kind: 'circuit_probe' }],\n };\n }\n\n /**\n * If we have an outstanding backoff window and we're inside it, tell the\n * shell to wait. The shell schedules a retry via setTimeout.\n */\n if (decayed.backoffMs > 0 && now < decayed.lastFailureAt + decayed.backoffMs) {\n const remaining = decayed.lastFailureAt + decayed.backoffMs - now;\n return { state: decayed, action: { kind: 'wait', ms: remaining }, signals: [] };\n }\n\n /**\n * No prior failure but reason='escalation' — start with a higher floor since\n * escalation is by definition retry pressure.\n */\n let nextState = decayed;\n if (decayed.backoffMs === 0 && event.reason === 'escalation') {\n nextState = { ...decayed, backoffMs: STARTING_BACKOFF_MS_ESCALATION };\n }\n\n if (event.activeCount >= MAX_ACTIVE_WATCHERS) {\n return { state: nextState, action: { kind: 'evict_and_subscribe' }, signals: [] };\n }\n return { state: nextState, action: { kind: 'subscribe' }, signals: [] };\n}\n\nfunction decideSuccess(state: AttemptState, _now: number): DecisionResult {\n const wasHalfOpen = state.halfOpenAt !== null;\n const next: AttemptState = {\n failureCount: 0,\n backoffMs: 0,\n lastFailureAt: 0,\n circuitOpenedAt: null,\n circuitCooldownMs: CIRCUIT_OPEN_MS,\n recentFailures: [],\n halfOpenAt: null,\n };\n return {\n state: next,\n action: { kind: 'noop' },\n signals: wasHalfOpen ? [{ kind: 'circuit_closed' }] : [],\n };\n}\n\nfunction decideFailure(state: AttemptState, now: number): DecisionResult {\n const recent = [...state.recentFailures.filter((t) => now - t < CIRCUIT_WINDOW_MS), now];\n const baseBackoff =\n state.backoffMs === 0\n ? STARTING_BACKOFF_MS_INITIAL\n : Math.min(state.backoffMs * 2, MAX_BACKOFF_MS);\n\n /** Already half-open and probe failed: re-open with doubled cooldown. */\n if (state.halfOpenAt !== null) {\n const nextCooldown = Math.min(state.circuitCooldownMs * 2, CIRCUIT_OPEN_MAX_MS);\n return {\n state: {\n ...state,\n failureCount: state.failureCount + 1,\n backoffMs: baseBackoff,\n lastFailureAt: now,\n recentFailures: recent,\n circuitOpenedAt: now,\n circuitCooldownMs: nextCooldown,\n halfOpenAt: null,\n },\n action: { kind: 'noop' },\n signals: [{ kind: 'circuit_opened' }],\n };\n }\n\n /** Newly opening the circuit. */\n if (recent.length >= CIRCUIT_FAILURES && state.circuitOpenedAt === null) {\n return {\n state: {\n ...state,\n failureCount: state.failureCount + 1,\n backoffMs: baseBackoff,\n lastFailureAt: now,\n recentFailures: recent,\n circuitOpenedAt: now,\n circuitCooldownMs: CIRCUIT_OPEN_MS,\n halfOpenAt: null,\n },\n action: { kind: 'noop' },\n signals: [{ kind: 'circuit_opened' }],\n };\n }\n\n return {\n state: {\n ...state,\n failureCount: state.failureCount + 1,\n backoffMs: baseBackoff,\n lastFailureAt: now,\n recentFailures: recent,\n },\n action: { kind: 'noop' },\n signals: [],\n };\n}\n\nfunction decayBackoff(state: AttemptState, now: number): AttemptState {\n if (state.lastFailureAt === 0 || state.backoffMs === 0) return state;\n const sinceLast = now - state.lastFailureAt;\n if (sinceLast >= BACKOFF_RESET_AFTER_MS) {\n return { ...state, backoffMs: 0, failureCount: 0, recentFailures: [] };\n }\n /** Halve every 60s of clean window — mirrors the documented decay. */\n const halvings = Math.floor(sinceLast / 60_000);\n if (halvings === 0) return state;\n const next = Math.max(STARTING_BACKOFF_MS_INITIAL, state.backoffMs >> halvings);\n return { ...state, backoffMs: next };\n}\n\ninterface ActiveEntry {\n path: string;\n tier: GuardTier;\n lastUsedAt: number;\n unsubscribe: () => Promise<void>;\n /** Original consumer callback so we can fire synthetic 'evicted' events. */\n consumerCallback: GuardedSubscribeCallback;\n}\n\n/**\n * A reservation reserves a slot in the budget while a `subscribe` call is\n * in flight. Without this, N concurrent subscribes when active.size == MAX-1\n * all see \"subscribe\" with no eviction and overrun the budget on resolve.\n */\ninterface Reservation {\n path: string;\n tier: GuardTier;\n reservedAt: number;\n}\n\ninterface GuardModuleState {\n active: Set<ActiveEntry>;\n reservations: Set<Reservation>;\n perPath: Map<string, AttemptState>;\n /** Cache of circuit-open log emission so we log only once per opening. */\n loggedOpenAt: Map<string, number>;\n /** Live deps; overridden in tests via _resetGuardForTesting / configureFileWatcherGuard. */\n deps: FileWatcherGuardDeps;\n /**\n * Lazily-instantiated worker supervisor — Commit 2 of the watcher-worker\n * subprocess rollout. Created on first `guardedSubscribe` call so test\n * envs that override `deps.subscribe` never instantiate it.\n */\n supervisor: WatcherWorkerSupervisor | null;\n supervisorMetrics: SupervisorMetrics | null;\n}\n\nlet moduleState: GuardModuleState = makeModuleState();\n\nfunction makeModuleState(): GuardModuleState {\n return {\n active: new Set<ActiveEntry>(),\n reservations: new Set<Reservation>(),\n perPath: new Map<string, AttemptState>(),\n loggedOpenAt: new Map<string, number>(),\n deps: defaultDeps(),\n supervisor: null,\n supervisorMetrics: null,\n };\n}\n\n/**\n * Vitest sets `process.env.VITEST === 'true'`; under that we fall back to\n * the direct `@parcel/watcher` import so existing consumer tests that mock\n * `@parcel/watcher` keep working without each test having to mock the\n * worker-supervisor module. Production code paths set neither, so the\n * supervisor routing is always live in `shipyard start`.\n *\n * WHY THIS FALLBACK STILL EXISTS (Commit 4 audit):\n * - `apps/daemon/src/services/file-watcher.test.ts` mocks `@parcel/watcher`\n * directly via `vi.mock('@parcel/watcher', ...)` and exercises the guard\n * indirectly through `FileWatcherPool`. Migrating it (and any future\n * consumer test that adopts the same pattern) to mock the worker supervisor\n * is a separate refactor — its scope is \"rewrite the FileWatcherPool test\n * harness\" which is independent of the watcher-worker subprocess rollout.\n * - `tests/integration-coverage.test.ts` keeps `file-watcher-guard.ts` in\n * the Invariant #17 allowlist for as long as this fallback exists. Once\n * `file-watcher.test.ts` switches to mocking the supervisor module, this\n * constant + `legacyDirectSubscribe` can both be removed and the allowlist\n * tightened to only `worker.ts` and its tests.\n */\nconst IN_VITEST = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';\n\nfunction defaultDeps(): FileWatcherGuardDeps {\n return {\n /**\n * Production path: route every subscribe through the watcher worker\n * supervisor (which runs `@parcel/watcher` in a subprocess). The\n * supervisor delivers `WatcherWorkerEventLike[]` whose 'evicted' variant\n * is fired during worker respawns; we widen it to `GuardedEvent[]`\n * (which already accommodates 'evicted' via the LRU eviction path).\n */\n subscribe: (path, fn, opts) => {\n if (IN_VITEST) return legacyDirectSubscribe(path, fn, opts);\n return supervisorSubscribe(path, fn, opts);\n },\n log: () => {},\n now: () => Date.now(),\n setTimeout: (fn, ms) => setTimeout(fn, ms),\n clearTimeout: (timer) => {\n /**\n * `timer` is typed `unknown` in `FileWatcherGuardDeps` so test impls\n * can use numeric handles. The Node runtime accepts both numeric IDs\n * and `Timeout` objects via the same call.\n */\n if (timer === null || timer === undefined) return;\n clearTimeout(timer as never);\n },\n };\n}\n\nconst supervisorSubscribe: FileWatcherGuardDeps['subscribe'] = async (path, fn, opts) => {\n const supervisor = ensureSupervisor();\n const handle = await supervisor.subscribe(\n path,\n (err, events) => {\n fn(err, events.map(toGuardedEvent));\n },\n toSupervisorOpts(opts)\n );\n return { unsubscribe: () => handle.unsubscribe() };\n};\n\nfunction toSupervisorOpts(opts?: ParcelOptions): {\n ignore?: string[];\n backend?: 'default' | 'brute-force' | 'watchman';\n} {\n const out: { ignore?: string[]; backend?: 'default' | 'brute-force' | 'watchman' } = {};\n if (!opts) return out;\n if (opts.ignore) out.ignore = opts.ignore;\n if (opts.backend === 'brute-force' || opts.backend === 'watchman') {\n out.backend = opts.backend;\n }\n return out;\n}\n\nfunction toGuardedEvent(e: WatcherWorkerEventLike): GuardedEvent {\n switch (e.type) {\n case 'evicted':\n return { type: 'evicted', path: e.path };\n case 'create':\n case 'update':\n case 'delete':\n return { type: e.type, path: e.path };\n default:\n return assertNever(e.type);\n }\n}\n\nfunction ensureSupervisor(): WatcherWorkerSupervisor {\n if (moduleState.supervisor) return moduleState.supervisor;\n /**\n * `workerPath` resolves to the bundled worker entry shipped alongside the\n * daemon. tsup is configured (in `apps/daemon/tsup.config.ts`) with two\n * entries that emit at the top level of `dist/`: `src/index.ts` →\n * `dist/index.js` and `src/services/watcher-worker/worker.ts` →\n * `dist/worker.js`. tsup uses the basename of each entry for the output\n * filename and does NOT preserve the source directory structure. At\n * runtime `import.meta.url` is the daemon bundle (the only place that\n * imports this module in production) so `./worker.js` resolves to its\n * sibling. Meta-test \"Daemon Fork Targets Exist\" enforces this.\n *\n * In dev mode (`tsx src/index.ts`), `import.meta.url` is the source file\n * (`src/shared/file-watcher-guard.ts`), not `dist/index.js`. `./worker.js`\n * relative to `src/shared/` would point to a non-existent file. Detect the\n * dev path by checking whether the module URL contains `/src/` and resolve\n * to the actual worker source (`src/services/watcher-worker/worker.ts`)\n * instead — tsx handles the `.js` → `.ts` extension rewrite automatically.\n */\n const selfUrl = import.meta.url;\n const isDevMode = selfUrl.endsWith('.ts');\n const workerPath = isDevMode\n ? new URL('../services/watcher-worker/worker.ts', selfUrl).pathname\n : new URL('./worker.js', selfUrl).pathname;\n moduleState.supervisor = createWatcherWorkerSupervisor({\n fork: nodeFork,\n workerPath,\n log: (entry) => moduleState.deps.log(entry),\n metrics: moduleState.supervisorMetrics ?? undefined,\n now: () => moduleState.deps.now(),\n setTimeout: (fn, ms) => moduleState.deps.setTimeout(fn, ms),\n clearTimeout: (timer) => moduleState.deps.clearTimeout(timer),\n });\n return moduleState.supervisor;\n}\n\nconst nodeFork: Parameters<typeof createWatcherWorkerSupervisor>[0]['fork'] = (\n modulePath,\n args,\n options\n) =>\n /**\n * Fork the watcher worker against the resolved Node binary + Electron-node\n * env overlay — NOT the default `process.execPath`. In the packaged Electron\n * utility process the default resolves to a helper executable that may be\n * absent from the bundle (spawn ENOENT) and, even when present, needs\n * `ELECTRON_RUN_AS_NODE=1` to run a JS file instead of booting an Electron\n * app. Same root cause as the cursor-runner crash loop — this path is lazy\n * (first worktree subscription) so it broke file watching rather than boot.\n */\n childProcessFork(modulePath, args, {\n ...options,\n execPath: resolveNodeExecPath(),\n env: buildNodeSpawnEnv(process.env),\n });\n\n/** Total occupancy = active + outstanding reservations. */\nfunction occupancy(): number {\n return moduleState.active.size + moduleState.reservations.size;\n}\n\n/**\n * Production wiring: replace only the `log` callback (and not `now`/`setTimeout`/\n * `subscribe`) so structured events reach Pino + the metrics collector.\n * Idempotent: callers may invoke this multiple times; later calls win. Optionally\n * accepts a `metrics` capture so the supervisor's restart-class telemetry is\n * routed through the same collector that already receives the guard's events.\n */\nexport function configureFileWatcherGuard(deps: {\n log: FileWatcherGuardLogger;\n metrics?: SupervisorMetrics;\n}): void {\n moduleState.deps = { ...moduleState.deps, log: deps.log };\n if (deps.metrics) {\n moduleState.supervisorMetrics = deps.metrics;\n }\n}\n\n/**\n * Test-only: replace deps and reset internal state. NOT exported as part of\n * the production surface — guarded by underscore convention.\n *\n * Resets to a `subscribe` impl that dynamically imports `@parcel/watcher`\n * (the pre-Commit-2 default), so existing `vi.mock('@parcel/watcher')`\n * patterns in consumer tests continue to work without each test needing to\n * mock the worker supervisor module. Production `defaultDeps()` routes\n * through the supervisor; tests opt into that path explicitly.\n */\nexport function _resetGuardForTesting(deps?: Partial<FileWatcherGuardDeps>): void {\n moduleState = makeModuleState();\n moduleState.deps = {\n ...moduleState.deps,\n subscribe: legacyDirectSubscribe,\n };\n if (deps) moduleState.deps = { ...moduleState.deps, ...deps };\n}\n\n/**\n * Test-only fallback that calls `@parcel/watcher.subscribe` directly. Kept\n * isolated from `defaultDeps()` (which goes through the worker supervisor)\n * so we never accidentally ship the direct-import path into production.\n *\n * The guard's `subscribe` deps callback type accepts `GuardedEvent[]` (a\n * superset of `ParcelEvent[]`), so we adapt the direction by widening each\n * ParcelEvent to a GuardedEvent before forwarding — no type assertion\n * needed because every ParcelEvent is structurally a GuardedEvent.\n */\nconst legacyDirectSubscribe: FileWatcherGuardDeps['subscribe'] = async (path, fn, opts) => {\n const mod = await import('@parcel/watcher');\n return mod.subscribe(\n path,\n (err, events) => {\n const widened: GuardedEvent[] = events.map((e) => ({ type: e.type, path: e.path }));\n fn(err, widened);\n },\n opts\n );\n};\n\n/**\n * Graceful shutdown for the lazily-instantiated supervisor. No-op if the\n * supervisor was never created (test envs that override `deps.subscribe`).\n */\nexport async function shutdownFileWatcherGuard(): Promise<void> {\n const supervisor = moduleState.supervisor;\n if (!supervisor) return;\n moduleState.supervisor = null;\n await supervisor.shutdown();\n}\n\nexport function _getActiveCountForTesting(): number {\n return moduleState.active.size;\n}\n\nexport function _getPerPathSizeForTesting(): number {\n return moduleState.perPath.size;\n}\n\nexport function _getReservationCountForTesting(): number {\n return moduleState.reservations.size;\n}\n\n/**\n * Single chokepoint for every file watcher in the daemon. See AGENTS.md\n * Invariant #17 for the contract and rationale.\n */\nexport async function guardedSubscribe(\n path: string,\n callback: GuardedSubscribeCallback,\n opts: GuardedSubscribeOptions,\n tier: GuardTier,\n reason: GuardReason\n): Promise<AsyncSubscription> {\n const { deps } = moduleState;\n const now = deps.now();\n const priorState = moduleState.perPath.get(path) ?? makeInitialAttemptState();\n const decision = decideAction(\n priorState,\n { kind: 'request_subscribe', reason, activeCount: occupancy() },\n now\n );\n moduleState.perPath.set(path, decision.state);\n emitSignals(path, decision.signals);\n\n switch (decision.action.kind) {\n case 'reject_stub':\n return makeStubSubscription();\n case 'wait': {\n const waitMs = decision.action.ms;\n await new Promise<void>((resolve) => deps.setTimeout(() => resolve(), waitMs));\n return guardedSubscribe(path, callback, opts, tier, 'escalation');\n }\n case 'evict_and_subscribe':\n evictOne(path);\n return performSubscribe(path, callback, opts, tier);\n case 'subscribe':\n return performSubscribe(path, callback, opts, tier);\n case 'noop':\n /** decideRequest never returns 'noop'; satisfy exhaustiveness. */\n throw new Error('guardedSubscribe: unexpected noop on request');\n default:\n return assertNever(decision.action);\n }\n}\n\nasync function performSubscribe(\n path: string,\n consumerCallback: GuardedSubscribeCallback,\n opts: GuardedSubscribeOptions,\n tier: GuardTier\n): Promise<AsyncSubscription> {\n const { deps } = moduleState;\n let entry: ActiveEntry | null = null;\n\n /**\n * Reserve the slot synchronously (TOCTOU fix). Without this, N concurrent\n * subscribes at activeCount = MAX-1 all read \"below budget\" and skip the\n * eviction path; on resolve they all add entries and we silently overrun\n * the FSEvents stream limit.\n */\n const reservation: Reservation = { path, tier, reservedAt: deps.now() };\n moduleState.reservations.add(reservation);\n\n const wrapped = (err: Error | null, events: GuardedEvent[]) => {\n if (entry) entry.lastUsedAt = deps.now();\n consumerCallback(err, events);\n };\n\n try {\n const sub = await deps.subscribe(path, wrapped, opts);\n moduleState.reservations.delete(reservation);\n const now = deps.now();\n /** Mark success in the per-path state (closes any half-open circuit). */\n const priorState = moduleState.perPath.get(path) ?? makeInitialAttemptState();\n const successDecision = decideAction(priorState, { kind: 'subscribe_success' }, now);\n if (isCleanAttemptState(successDecision.state)) {\n moduleState.perPath.delete(path);\n } else {\n moduleState.perPath.set(path, successDecision.state);\n }\n emitSignals(path, successDecision.signals);\n\n entry = {\n path,\n tier,\n lastUsedAt: now,\n unsubscribe: () => sub.unsubscribe(),\n consumerCallback,\n };\n moduleState.active.add(entry);\n deps.log({ event: 'file_watcher_active_count', count: moduleState.active.size });\n\n /** Wrap unsubscribe so guard accounting stays consistent on consumer-driven teardown. */\n return {\n unsubscribe: async () => {\n if (entry && moduleState.active.has(entry)) {\n moduleState.active.delete(entry);\n }\n /**\n * Per-path cleanup (F6): drop the AttemptState entry when the watcher\n * unsubscribes cleanly and no failures have accumulated. Long-running\n * daemon with task churn would otherwise grow this map without bound.\n */\n const current = moduleState.perPath.get(path);\n if (current && isCleanAttemptState(current)) {\n moduleState.perPath.delete(path);\n }\n await sub.unsubscribe();\n },\n };\n } catch (err) {\n moduleState.reservations.delete(reservation);\n const now = deps.now();\n const priorState = moduleState.perPath.get(path) ?? makeInitialAttemptState();\n const failureDecision = decideAction(priorState, { kind: 'subscribe_failure' }, now);\n moduleState.perPath.set(path, failureDecision.state);\n const parentExists = pathExistsForLog(dirname(path));\n deps.log({\n event: 'file_watcher_subscribe_failed',\n path,\n pathExists: pathExistsForLog(path),\n parentExists,\n err: err instanceof Error ? err.message : String(err),\n /**\n * The expected-ENOENT fallback path (file doesn't exist yet but\n * parent does) is a normal poll cycle, not a failure. Downgrade\n * the log level so log triage doesn't flag 40+ of these per task.\n */\n ...(parentExists ? { level: 'debug' as const } : {}),\n });\n emitSignals(path, failureDecision.signals);\n\n /** If the failure tipped us into circuit-open, return a stub. Otherwise rethrow so caller can handle. */\n if (failureDecision.state.circuitOpenedAt !== null) {\n return makeStubSubscription();\n }\n throw err;\n }\n}\n\nfunction pathExistsForLog(path: string): boolean {\n try {\n return existsSync(path);\n } catch {\n return false;\n }\n}\n\n/**\n * Pick the oldest entry in the given tier (or any tier if `tier` is null).\n * Pure: depends only on `entries`. Returns null if no candidate matches.\n */\nfunction pickOldest(entries: Iterable<ActiveEntry>, tier: 'lazy' | null): ActiveEntry | null {\n let target: ActiveEntry | null = null;\n for (const entry of entries) {\n if (tier !== null && entry.tier !== tier) continue;\n if (target === null || entry.lastUsedAt < target.lastUsedAt) target = entry;\n }\n return target;\n}\n\nfunction selectEvictionVictim(\n entries: Iterable<ActiveEntry>\n): { victim: ActiveEntry; evictingEssential: boolean } | null {\n const lazy = pickOldest(entries, 'lazy');\n if (lazy !== null) return { victim: lazy, evictingEssential: false };\n const any = pickOldest(entries, null);\n if (any !== null) return { victim: any, evictingEssential: true };\n return null;\n}\n\nfunction evictOne(incomingPath: string): void {\n const { deps } = moduleState;\n if (moduleState.active.size === 0) {\n throw new Error('file-watcher-guard: cannot evict — active set is empty (counter is broken)');\n }\n\n const selection = selectEvictionVictim(moduleState.active);\n if (selection === null) {\n throw new Error('file-watcher-guard: eviction selection failed');\n }\n const { victim, evictingEssential } = selection;\n\n moduleState.active.delete(victim);\n deps.log({\n event: evictingEssential ? 'file_watcher_essential_evicted' : 'file_watcher_evicted',\n path: victim.path,\n tier: victim.tier,\n incomingPath,\n });\n\n /**\n * Re-entry safety (Invariant #11): defer the synthetic dispatch via\n * queueMicrotask so a callback that re-enters the guard cannot do so from\n * inside the dispatching frame.\n */\n queueMicrotask(() => {\n victim.consumerCallback(null, [{ type: 'evicted', path: victim.path }]);\n });\n /** Fire-and-forget unsubscribe; we don't block the new subscribe on it. */\n victim.unsubscribe().catch((err) => {\n deps.log({\n event: 'file_watcher_unsubscribe_failed_during_eviction',\n path: victim.path,\n err: err instanceof Error ? err.message : String(err),\n });\n });\n\n /**\n * Self-healing essentials (F4): consumers of essential watchers (capability\n * watcher, plan-file-bridge, published-artifact-store) don't currently\n * re-subscribe on `evicted` — they treat the synthetic event as fatal. The\n * guard schedules a retry on their behalf so an essential watcher that lost\n * its slot to a budget squeeze comes back online once the new arrival\n * settles. Bounded by the circuit breaker — repeated failures will trip it.\n */\n if (evictingEssential) {\n scheduleEssentialResubscribe(victim);\n }\n}\n\nfunction scheduleEssentialResubscribe(victim: ActiveEntry): void {\n const { deps } = moduleState;\n deps.log({\n event: 'file_watcher_essential_re_subscribe_scheduled',\n path: victim.path,\n delayMs: ESSENTIAL_RESUBSCRIBE_DELAY_MS,\n });\n deps.setTimeout(() => {\n void retryEssentialSubscribe(victim);\n }, ESSENTIAL_RESUBSCRIBE_DELAY_MS);\n}\n\nasync function retryEssentialSubscribe(victim: ActiveEntry): Promise<void> {\n const { deps } = moduleState;\n try {\n await guardedSubscribe(victim.path, victim.consumerCallback, {}, 'essential', 'rescan');\n deps.log({ event: 'file_watcher_essential_re_subscribed', path: victim.path });\n } catch (err) {\n deps.log({\n event: 'file_watcher_essential_re_subscribe_failed',\n path: victim.path,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\nfunction emitSignals(path: string, signals: DecisionResult['signals']): void {\n const { deps } = moduleState;\n for (const sig of signals) {\n switch (sig.kind) {\n case 'circuit_opened':\n deps.log({ event: 'file_watcher_circuit_open', path });\n moduleState.loggedOpenAt.set(path, deps.now());\n break;\n case 'circuit_closed':\n deps.log({ event: 'file_watcher_circuit_closed', path });\n moduleState.loggedOpenAt.delete(path);\n break;\n case 'circuit_probe':\n /** Probe attempts are attempt-level — no log to avoid spam. */\n break;\n default:\n assertNever(sig);\n }\n }\n}\n\nfunction makeStubSubscription(): AsyncSubscription {\n return { unsubscribe: async () => {} };\n}\n","import type { ChildProcess } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport {\n createServiceSupervisor,\n type ServiceSupervisor,\n type StdioChildProcessLike,\n} from '@shipyard/local-runtime';\nimport { assertNever } from '../../shared/assert-never.js';\nimport {\n type AttemptState,\n decideAction,\n makeInitialAttemptState,\n} from '../../shared/file-watcher-guard.js';\nimport {\n type WorkerCommand,\n WorkerCommandCodec,\n type WorkerParcelEvent,\n type WorkerReply,\n WorkerReplyCodec,\n} from './worker-protocol.js';\n\n/**\n * Parent-side supervisor for the watcher worker subprocess. Owns the\n * Map<id, SubscriptionEntry> source of truth and replays it onto a fresh\n * worker after a crash, firing a synthetic 'evicted' event so consumers\n * can readdir-catch-up across the gap.\n *\n * Process lifecycle (spawn, peer attach, generation tokens, exit dispatch)\n * is delegated to `createServiceSupervisor` from @shipyard/local-runtime.\n * This module retains the watcher-specific concerns:\n * - subscription map + replay-on-respawn\n * - the file-watcher-guard-based half-open circuit (separate from the\n * primitive's simpler deaths-per-window circuit, which is opted out of)\n * - pendingRespawnGeneration accounting tied to subscribe-ack delivery\n * - synthetic 'evicted' eviction across the death gap\n * - SIGTERM → SIGKILL shutdown escalation\n *\n * The supervisor's restart-circuit (5 deaths in 60s -> 5min cooldown) is\n * SEPARATE from `file-watcher-guard.ts`'s per-path subscribe-failure\n * circuit. Both apply: a single bad path won't crash the worker, but a\n * worker that keeps SIGABRTing will eventually trip this layer.\n */\n\nexport interface WatcherWorkerEventLike {\n type: 'create' | 'update' | 'delete' | 'evicted';\n path: string;\n}\n\nexport type WatcherWorkerCallback = (\n err: Error | null,\n events: WatcherWorkerEventLike[]\n) => unknown;\n\nexport interface WatcherWorkerSubscribeOptions {\n ignore?: string[];\n backend?: 'default' | 'brute-force' | 'watchman';\n}\n\nexport interface WatcherWorkerSubscriptionHandle {\n unsubscribe(): Promise<void>;\n}\n\nexport interface WatcherWorkerSupervisor {\n /** Spawn the worker if not yet running. Idempotent. */\n start(): Promise<void>;\n subscribe(\n path: string,\n callback: WatcherWorkerCallback,\n opts: WatcherWorkerSubscribeOptions\n ): Promise<WatcherWorkerSubscriptionHandle>;\n /** Graceful shutdown. Sends shutdown command, escalates SIGTERM, then SIGKILL. */\n shutdown(): Promise<void>;\n}\n\nexport interface SupervisorMetrics {\n capture(eventType: string, properties: Record<string, unknown>): void;\n}\n\nexport type SupervisorLogger = (entry: { event: string; [key: string]: unknown }) => void;\n\nexport interface WatcherWorkerSupervisorDeps {\n readonly fork: (\n modulePath: string,\n args: string[],\n options: { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }\n ) => ChildProcess;\n readonly workerPath: string;\n readonly log: SupervisorLogger;\n readonly metrics?: SupervisorMetrics;\n readonly now: () => number;\n readonly setTimeout: (fn: () => void, ms: number) => unknown;\n readonly clearTimeout: (timer: unknown) => void;\n}\n\n/** Empirically: SIGSEGV -> 139, SIGBUS -> 138, SIGABRT -> 134 on POSIX. */\nconst ABNORMAL_SIGNALS = new Set<NodeJS.Signals>(['SIGABRT', 'SIGSEGV', 'SIGBUS', 'SIGKILL']);\n\nconst SHUTDOWN_GRACEFUL_MS = 2_000;\nconst SHUTDOWN_SIGTERM_MS = 500;\n\ninterface SubscriptionEntry {\n id: string;\n path: string;\n opts: WatcherWorkerSubscribeOptions;\n callback: WatcherWorkerCallback;\n status: 'pending' | 'subscribed' | 'unsubscribing';\n resolveSubscribed?: (handle: WatcherWorkerSubscriptionHandle) => void;\n rejectSubscribed?: (err: Error) => void;\n resolveUnsubscribed?: () => void;\n}\n\nexport function createWatcherWorkerSupervisor(\n deps: WatcherWorkerSupervisorDeps\n): WatcherWorkerSupervisor {\n const subscriptions = new Map<string, SubscriptionEntry>();\n let restartState: AttemptState = makeInitialAttemptState();\n let liveGeneration = 0;\n let lastSpawnedAt = 0;\n let lastPid: number | null = null;\n let shutdownInitiated = false;\n let restartTimer: unknown = null;\n /**\n * Tracks the generation of a worker that was forked as a respawn and is\n * still awaiting its first `subscribed` ack. The ack from THAT generation\n * fires decideAction({kind:'subscribe_success'}) to close the cooldown\n * circuit. A boolean would mis-attribute: if worker B (respawn) dies before\n * acking and we respawn worker C, B's stale window would reset on C's first\n * ack — but C's ack should only count for C's own respawn (which it does,\n * because we re-set this on each respawn). More importantly, when B dies\n * before acking we MUST clear the pending generation so a future ack from\n * C is judged on its own respawn fork, not on the abandoned B window.\n */\n let pendingRespawnGeneration: number | null = null;\n let nextReplayStartedAt: number | null = null;\n\n function log(entry: { event: string; [key: string]: unknown }): void {\n deps.log(entry);\n }\n\n function captureMetric(eventType: string, properties: Record<string, unknown>): void {\n deps.metrics?.capture(eventType, properties);\n }\n\n /**\n * Adapt deps.fork to the primitive's ForkLike shape. ChildProcess\n * structurally satisfies StdioChildProcessLike; we just match the\n * argument signature (readonly args -> string[]).\n */\n const adaptedFork = (\n modulePath: string,\n args: readonly string[],\n options: { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }\n ): StdioChildProcessLike => {\n return deps.fork(modulePath, Array.from(args), options);\n };\n\n const supervisor: ServiceSupervisor<WorkerCommand> = createServiceSupervisor<\n WorkerCommand,\n WorkerReply\n >({\n fork: adaptedFork,\n workerPath: deps.workerPath,\n commandCodec: WorkerCommandCodec,\n replyCodec: WorkerReplyCodec,\n onReply: (reply, fromGeneration) => dispatchReply(reply, fromGeneration),\n onChildSpawned: ({ generation, pid }) => {\n liveGeneration = generation;\n lastPid = pid;\n lastSpawnedAt = deps.now();\n log({ event: 'watcher_worker_started', pid });\n captureMetric('watcher_worker_started', { pid });\n },\n onChildExit: ({ code, signal, generation, uptimeMs }) => {\n handleChildExit(generation, code, signal, uptimeMs);\n },\n onDecodeFailed: ({ line }) => log({ event: 'watcher_worker_decode_failed', line }),\n onStderrLine: (line) => {\n if (!line.trim()) return;\n log({ event: 'watcher_worker_stderr', line });\n },\n onMissingStdout: () => log({ event: 'watcher_worker_no_stdout' }),\n onOverflow: ({ stream, bytes, maxBytes }) =>\n log({ event: 'watcher_worker_stdio_overflow', stream, bytes, maxBytes }),\n onPeerError: (err) => log({ event: 'watcher_worker_error', err: err.message }),\n log,\n now: deps.now,\n /** Watcher uses its own file-watcher-guard circuit; opt out of the primitive's. */\n });\n\n function sendCommand(cmd: WorkerCommand): boolean {\n const sent = supervisor.send(cmd);\n if (!sent && supervisor.getCurrentProcess() !== null) {\n log({\n event: 'watcher_worker_stdin_write_failed',\n err: 'stdio peer send returned false',\n });\n }\n return sent;\n }\n\n function dispatchReply(reply: WorkerReply, fromGeneration: number): void {\n if (fromGeneration !== liveGeneration) {\n log({\n event: 'watcher_worker_stale_reply_dropped',\n replyType: reply.type,\n id: reply.id,\n generation: fromGeneration,\n liveGeneration,\n });\n return;\n }\n switch (reply.type) {\n case 'subscribed':\n handleSubscribedReply(reply.id, fromGeneration);\n return;\n case 'subscribe_failed':\n handleSubscribeFailedReply(reply.id, reply.error);\n return;\n case 'unsubscribed':\n handleUnsubscribedReply(reply.id);\n return;\n case 'events':\n handleEventsReply(reply.id, reply.events);\n return;\n default:\n assertNever(reply);\n }\n }\n\n function handleSubscribedReply(id: string, fromGeneration: number): void {\n const entry = subscriptions.get(id);\n if (!entry) return;\n /**\n * Respawn-ack accounting (F4): the ack that closes the cooldown window\n * must come from the SAME generation we marked pending when respawning.\n * Pipe drains can deliver replies from a dead generation after a\n * successor is live, and only the original generation should consume\n * its own pending-ack window.\n */\n if (pendingRespawnGeneration !== null && fromGeneration === pendingRespawnGeneration) {\n pendingRespawnGeneration = null;\n const successDecision = decideAction(restartState, { kind: 'subscribe_success' }, deps.now());\n restartState = successDecision.state;\n for (const sig of successDecision.signals) {\n if (sig.kind === 'circuit_closed') {\n log({ event: 'watcher_worker_circuit_closed' });\n }\n }\n }\n if (entry.status === 'pending' && entry.resolveSubscribed) {\n entry.status = 'subscribed';\n entry.resolveSubscribed(makeHandle(entry));\n entry.resolveSubscribed = undefined;\n entry.rejectSubscribed = undefined;\n return;\n }\n entry.status = 'subscribed';\n }\n\n function handleSubscribeFailedReply(id: string, error: string): void {\n const entry = subscriptions.get(id);\n if (!entry) return;\n subscriptions.delete(id);\n entry.rejectSubscribed?.(new Error(error));\n }\n\n function handleUnsubscribedReply(id: string): void {\n const entry = subscriptions.get(id);\n if (!entry) return;\n subscriptions.delete(id);\n entry.resolveUnsubscribed?.();\n }\n\n function handleEventsReply(id: string, events: WorkerParcelEvent[]): void {\n const entry = subscriptions.get(id);\n if (!entry || entry.status === 'unsubscribing') return;\n entry.callback(null, toCallbackEvents(events));\n }\n\n function handleChildExit(\n generation: number,\n code: number | null,\n signal: NodeJS.Signals | null,\n uptimeMs: number\n ): void {\n /**\n * The primitive only fires onChildExit for the currently-live generation,\n * so this always matches `liveGeneration`. After this returns, the\n * primitive has set its internal live to null.\n */\n if (pendingRespawnGeneration === generation) {\n pendingRespawnGeneration = null;\n }\n\n const replayCount = subscriptions.size;\n\n if (shutdownInitiated) {\n log({ event: 'watcher_worker_exited_during_shutdown', code, signal, uptimeMs });\n return;\n }\n\n const abnormal = isAbnormalExit(code, signal) || replayCount > 0;\n if (!abnormal) {\n log({ event: 'watcher_worker_exited_clean', code, signal, uptimeMs });\n return;\n }\n\n log({ event: 'watcher_worker_died', code, signal, replayCount, uptimeMs });\n captureMetric('watcher_worker_died', {\n code,\n signal,\n replayCount,\n uptimeMs,\n });\n\n recordRestartFailure(uptimeMs);\n\n /** Fire synthetic 'evicted' so consumers know there was a gap. */\n fireSyntheticEvictionToAll();\n\n /** Schedule a respawn unless the circuit is open. */\n scheduleRespawn();\n }\n\n /**\n * Reuse the guard's pure restart core: a 'subscribe_failure' event drives\n * the same backoff/circuit logic per-process. activeCount=0 because the\n * supervisor has its own slot, not a shared FSEvents budget.\n */\n function recordRestartFailure(uptimeMs: number): void {\n const failureDecision = decideAction(restartState, { kind: 'subscribe_failure' }, deps.now());\n restartState = failureDecision.state;\n for (const sig of failureDecision.signals) {\n if (sig.kind === 'circuit_opened') {\n log({ event: 'watcher_worker_circuit_open' });\n captureMetric('watcher_worker_circuit_open', {\n deathCount: restartState.failureCount,\n windowMs: uptimeMs,\n });\n } else if (sig.kind === 'circuit_closed') {\n log({ event: 'watcher_worker_circuit_closed' });\n }\n }\n }\n\n function dispatchSyntheticEviction(entry: SubscriptionEntry): void {\n if (!subscriptions.has(entry.id)) return;\n try {\n entry.callback(null, [{ type: 'evicted', path: entry.path }]);\n } catch (err) {\n log({\n event: 'watcher_worker_synthetic_dispatch_failed',\n id: entry.id,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n function fireSyntheticEvictionToAll(): void {\n /**\n * Re-entry safety (Invariant #11): defer dispatch to a fresh microtask\n * so a callback that re-enters supervisor.subscribe() cannot do so from\n * inside the exit-handler frame.\n */\n const snapshot = Array.from(subscriptions.values());\n queueMicrotask(() => {\n for (const entry of snapshot) {\n dispatchSyntheticEviction(entry);\n }\n });\n }\n\n function scheduleRespawn(): void {\n if (shutdownInitiated) return;\n /**\n * Idempotency: pipe-failure and on-exit can both fire scheduleRespawn\n * for the same death. Without this guard both timers fire and the\n * second one runs forkAndReplay while a worker is already alive.\n */\n if (restartTimer !== null) return;\n\n /**\n * Always route through `decideAction({kind:'request_subscribe'})` so the\n * pure core handles half-open transition: when the cooldown expires,\n * `decideRequest` sets `halfOpenAt = now` and returns 'subscribe'.\n */\n const requestDecision = decideAction(\n restartState,\n { kind: 'request_subscribe', reason: 'rescan', activeCount: 0 },\n deps.now()\n );\n restartState = requestDecision.state;\n\n switch (requestDecision.action.kind) {\n case 'reject_stub': {\n const openedAt = restartState.circuitOpenedAt;\n if (openedAt === null) {\n attemptRespawn();\n return;\n }\n const cooldownEnd = openedAt + restartState.circuitCooldownMs;\n const waitMs = Math.max(0, cooldownEnd - deps.now());\n restartTimer = deps.setTimeout(() => {\n restartTimer = null;\n scheduleRespawn();\n }, waitMs);\n return;\n }\n case 'wait': {\n restartTimer = deps.setTimeout(() => {\n restartTimer = null;\n attemptRespawn();\n }, requestDecision.action.ms);\n return;\n }\n case 'subscribe':\n case 'evict_and_subscribe':\n attemptRespawn();\n return;\n case 'noop':\n attemptRespawn();\n return;\n default:\n assertNever(requestDecision.action);\n }\n }\n\n function attemptRespawn(): void {\n if (shutdownInitiated) return;\n if (restartState.circuitOpenedAt !== null) {\n log({ event: 'watcher_worker_probe_attempt' });\n }\n try {\n forkAndReplay();\n } catch (err) {\n log({\n event: 'watcher_worker_fork_failed',\n err: err instanceof Error ? err.message : String(err),\n });\n const failureDecision = decideAction(restartState, { kind: 'subscribe_failure' }, deps.now());\n restartState = failureDecision.state;\n scheduleRespawn();\n }\n }\n\n /**\n * Replay outcome: either we sent every entry (`completed`) or `sendCommand`\n * returned false partway through (`pipe_failed`). The latter means the\n * fresh worker's pipe is already dead; we abandon this generation and\n * trigger another respawn so the next fresh worker gets a clean replay.\n */\n type ReplayResult =\n | { kind: 'completed'; replayedCount: number }\n | { kind: 'pipe_failed'; replayedCount: number };\n\n function forkAndReplay(): void {\n const isRespawn = liveGeneration >= 1;\n nextReplayStartedAt = deps.now();\n /**\n * Whether the spawn we're about to perform is a respawn that should\n * close the circuit on its first ack. We snapshot here BEFORE start/\n * restart fires onChildSpawned (which bumps liveGeneration).\n */\n const shouldMarkPendingRespawn = isRespawn && subscriptions.size > 0;\n\n if (liveGeneration === 0) {\n supervisor.start();\n } else {\n supervisor.restart();\n }\n\n if (shouldMarkPendingRespawn) {\n pendingRespawnGeneration = liveGeneration;\n }\n\n const replayResult = replaySubscriptions();\n\n if (replayResult.kind === 'pipe_failed') {\n handleReplayPipeFailure(replayResult.replayedCount);\n return;\n }\n if (replayResult.replayedCount > 0) {\n log({ event: 'watcher_worker_replayed', replayCount: replayResult.replayedCount });\n }\n if (isRespawn) {\n recordRespawnTelemetry(\n replayResult.replayedCount,\n deps.now() - (nextReplayStartedAt ?? deps.now())\n );\n }\n }\n\n function replaySubscriptions(): ReplayResult {\n let replayedCount = 0;\n for (const entry of subscriptions.values()) {\n /**\n * Reset to 'pending' so the new {type:'subscribed'} reply hits the\n * resolution path. In-flight subscribers from before the death keep\n * their resolve/reject closures and resolve when the new worker\n * acknowledges. New subscribers added after replay get appended.\n */\n entry.status = 'pending';\n const ok = sendCommand({\n cmd: 'subscribe',\n id: entry.id,\n path: entry.path,\n opts: entry.opts,\n });\n if (!ok) {\n return { kind: 'pipe_failed', replayedCount };\n }\n replayedCount++;\n }\n return { kind: 'completed', replayedCount };\n }\n\n function handleReplayPipeFailure(replayedCount: number): void {\n log({\n event: 'watcher_worker_replay_pipe_failed',\n pid: lastPid,\n replayedCount,\n pendingCount: subscriptions.size - replayedCount,\n });\n /**\n * Count this as a failure for the restart-circuit. Without it, a\n * pathological environment where every fresh worker's stdin pipe is\n * dead-on-arrival would respawn forever — `failureCount` never\n * increments because the on-exit failure path requires an exit signal.\n */\n recordRestartFailure(deps.now() - lastSpawnedAt);\n /**\n * Pipe death usually means the worker has already exited or its stdin\n * was closed underneath us. Kill the underlying process so onExit fires\n * and the standard respawn cycle takes over.\n */\n const proc = supervisor.getCurrentProcess();\n if (proc !== null) {\n try {\n proc.kill('SIGKILL');\n } catch (err) {\n log({\n event: 'watcher_worker_kill_failed',\n signal: 'SIGKILL',\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n scheduleRespawn();\n }\n\n function recordRespawnTelemetry(replayCount: number, replayDurationMs: number): void {\n log({\n event: 'watcher_worker_respawned',\n pid: lastPid,\n replayCount,\n replayDurationMs,\n });\n captureMetric('watcher_worker_respawned', {\n newPid: lastPid,\n replayCount,\n replayDurationMs,\n });\n }\n\n function makeHandle(entry: SubscriptionEntry): WatcherWorkerSubscriptionHandle {\n return {\n unsubscribe: () => unsubscribeEntry(entry),\n };\n }\n\n function unsubscribeEntry(entry: SubscriptionEntry): Promise<void> {\n if (!subscriptions.has(entry.id)) return Promise.resolve();\n entry.status = 'unsubscribing';\n if (supervisor.getCurrentProcess() === null) {\n /** No worker -> just drop the entry; the next worker won't replay it. */\n subscriptions.delete(entry.id);\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n entry.resolveUnsubscribed = resolve;\n const sent = sendCommand({ cmd: 'unsubscribe', id: entry.id });\n if (!sent) {\n /** Pipe gone — drop the entry so we don't leak. */\n subscriptions.delete(entry.id);\n resolve();\n }\n });\n }\n\n function makeStubHandle(): WatcherWorkerSubscriptionHandle {\n return { unsubscribe: async () => {} };\n }\n\n async function start(): Promise<void> {\n if (supervisor.getCurrentProcess() !== null) return;\n if (shutdownInitiated) {\n throw new Error('WatcherWorkerSupervisor: cannot start after shutdown');\n }\n forkAndReplay();\n }\n\n async function subscribe(\n path: string,\n callback: WatcherWorkerCallback,\n opts: WatcherWorkerSubscribeOptions\n ): Promise<WatcherWorkerSubscriptionHandle> {\n if (shutdownInitiated) {\n throw new Error('WatcherWorkerSupervisor: cannot subscribe after shutdown');\n }\n /**\n * Honor the supervisor's own circuit. Stub subscriptions still occupy a\n * slot in the map only briefly — we don't add to subscriptions on the\n * stub path, so a future probe doesn't try to replay them.\n */\n if (restartState.circuitOpenedAt !== null) {\n const cooldownEnd = restartState.circuitOpenedAt + restartState.circuitCooldownMs;\n if (deps.now() < cooldownEnd) {\n log({ event: 'watcher_worker_subscribe_stubbed', path });\n return makeStubHandle();\n }\n }\n\n const id = randomUUID();\n const entry: SubscriptionEntry = {\n id,\n path,\n opts,\n callback,\n status: 'pending',\n };\n const promise = new Promise<WatcherWorkerSubscriptionHandle>((resolve, reject) => {\n entry.resolveSubscribed = resolve;\n entry.rejectSubscribed = reject;\n });\n subscriptions.set(id, entry);\n\n if (supervisor.getCurrentProcess() === null) {\n await start();\n } else {\n sendCommand({ cmd: 'subscribe', id, path, opts });\n }\n return promise;\n }\n\n async function shutdown(): Promise<void> {\n if (shutdownInitiated) return;\n shutdownInitiated = true;\n if (restartTimer !== null) {\n deps.clearTimeout(restartTimer);\n restartTimer = null;\n }\n\n /**\n * Reject in-flight subscribes AND resolve pending unsubscribes (F5).\n * A consumer awaiting `handle.unsubscribe()` at shutdown-time would\n * otherwise hang forever.\n */\n for (const entry of subscriptions.values()) {\n entry.rejectSubscribed?.(new Error('WatcherWorkerSupervisor: shutdown'));\n entry.rejectSubscribed = undefined;\n entry.resolveSubscribed = undefined;\n entry.resolveUnsubscribed?.();\n entry.resolveUnsubscribed = undefined;\n }\n subscriptions.clear();\n\n const proc = supervisor.getCurrentProcess();\n supervisor.dispose();\n if (proc === null) return;\n\n sendCommand({ cmd: 'shutdown' });\n\n await escalateShutdown(proc);\n }\n\n function killWithSignal(proc: StdioChildProcessLike, signal: 'SIGTERM' | 'SIGKILL'): void {\n log({\n event:\n signal === 'SIGTERM'\n ? 'watcher_worker_shutdown_sigterm'\n : 'watcher_worker_shutdown_sigkill',\n });\n try {\n proc.kill(signal);\n } catch (err) {\n log({\n event: 'watcher_worker_kill_failed',\n signal,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n function escalateShutdown(proc: StdioChildProcessLike): Promise<void> {\n return new Promise<void>((resolve) => {\n const ctx = { done: false, exited: false };\n const finish = () => {\n if (ctx.done) return;\n ctx.done = true;\n resolve();\n };\n\n proc.once('exit', () => {\n ctx.exited = true;\n finish();\n });\n\n deps.setTimeout(() => onSigtermDeadline(proc, ctx, finish), SHUTDOWN_GRACEFUL_MS);\n });\n }\n\n function onSigtermDeadline(\n proc: StdioChildProcessLike,\n ctx: { done: boolean; exited: boolean },\n finish: () => void\n ): void {\n if (ctx.done || ctx.exited) return;\n killWithSignal(proc, 'SIGTERM');\n deps.setTimeout(() => onSigkillDeadline(proc, ctx, finish), SHUTDOWN_SIGTERM_MS);\n }\n\n function onSigkillDeadline(\n proc: StdioChildProcessLike,\n ctx: { done: boolean; exited: boolean },\n finish: () => void\n ): void {\n if (ctx.done || ctx.exited) return;\n killWithSignal(proc, 'SIGKILL');\n finish();\n }\n\n return { start, subscribe, shutdown };\n}\n\nfunction isAbnormalExit(code: number | null, signal: NodeJS.Signals | null): boolean {\n if (signal !== null && ABNORMAL_SIGNALS.has(signal)) return true;\n if (code !== null && code !== 0) return true;\n return false;\n}\n\nfunction toCallbackEvents(events: WorkerParcelEvent[]): WatcherWorkerEventLike[] {\n return events.map((e) => ({ type: e.type, path: e.path }));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0HO,IAAM,eAA+B;AAAA,EAC1C,UAAU;AAAA,EAAC;AAAA,EACX,UAAU;AAAA,EAAC;AACb;AAUA,IAAM,WAA0B,MAAM;AAAC;AAGvC,IAAM,0BAA0B;AAezB,IAAM,mBAAN,MAAuB;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA,EACT,UAA2B,CAAC;AAAA,EAC5B,SAAgD;AAAA,EAChD,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EAEtB,YAAY,WAAmB,WAAoC,MAA6B;AAC9F,SAAK,aAAa,UAAU,QAAQ,OAAO,EAAE;AAC7C,SAAK,aAAa;AAClB,SAAK,gBAAgB,MAAM,gBAAgB;AAC3C,SAAK,OAAO,MAAM,OAAO;AACzB,SAAK,iBAAiB,2BAA2B;AAEjD,UAAM,aAAa,MAAM,mBAAmB;AAC5C,SAAK,SAAS,YAAY,MAAM;AAC9B,WAAK,MAAM;AAAA,IACb,GAAG,UAAU;AACb,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA,EAEA,QAAQ,WAAmB,YAA4C;AACrE,QAAI,KAAK,UAAW;AAEpB,UAAM,SAAS,OAAO,YAAY,WAAW,WAAW,WAAW,SAAS;AAE5E,SAAK,QAAQ,KAAK;AAAA,MAChB;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,SAAS,EAAE,GAAI,cAAc,CAAC,GAAI,eAAe,KAAK,eAAe;AAAA,MACrE,iBAAiB,KAAK,IAAI;AAAA,IAC5B,CAAC;AAED,QAAI,KAAK,QAAQ,UAAU,KAAK,eAAe;AAC7C,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,QAAQ,WAAW,EAAG;AAE/B,UAAM,SAAS,KAAK;AACpB,SAAK,UAAU,CAAC;AAEhB,UAAM,OAA6B,EAAE,OAAO;AAE5C,UAAM,QAAQ,OAAO,KAAK,eAAe,aAAa,KAAK,WAAW,IAAI,KAAK;AAC/E,UAAM,GAAG,KAAK,UAAU,WAAW;AAAA,MACjC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK;AAAA,MAChC;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC,EACE,KAAK,CAAC,QAAQ;AACb,UAAI,IAAI,IAAI;AACV,aAAK,sBAAsB;AAC3B;AAAA,MACF;AACA,WAAK,eAAe,EAAE,QAAQ,IAAI,QAAQ,eAAe,OAAO,OAAO,CAAC;AAAA,IAC1E,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,WAAK,eAAe;AAAA,QAClB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACtD,eAAe,OAAO;AAAA,MACxB,CAAC;AAAA,IACH,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,QAAuC;AACpD,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,KAAK,oBAAoB,yBAAyB;AAC1D,WAAK,uBAAuB;AAC5B;AAAA,IACF;AACA,SAAK,KAAK;AAAA,MACR,OAAO;AAAA,MACP,wBAAwB,KAAK;AAAA,MAC7B,GAAG;AAAA,IACL,CAAC;AACD,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,UAAgB;AACd,SAAK,YAAY;AACjB,QAAI,KAAK,QAAQ;AACf,oBAAc,KAAK,MAAM;AACzB,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,MAAM;AAAA,EACb;AACF;AAEA,SAAS,6BAAqC;AAC5C,MAAI;AACF,WAAO,iBAAiB;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,uBACd,WACA,WACA,kBACA,KACgB;AAChB,MAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,UAAW,QAAO;AAC1D,SAAO,IAAI,iBAAiB,WAAW,WAAW,EAAE,IAAI,CAAC;AAC3D;;;ACpSA,SAAS,QAAQ,wBAAwB;AACzC,SAAS,kBAAkB;AAC3B,SAAS,eAAe;;;ACDxB,SAAS,kBAAkB;AA8F3B,IAAM,mBAAmB,oBAAI,IAAoB,CAAC,WAAW,WAAW,UAAU,SAAS,CAAC;AAE5F,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAarB,SAAS,8BACd,MACyB;AACzB,QAAM,gBAAgB,oBAAI,IAA+B;AACzD,MAAI,eAA6B,wBAAwB;AACzD,MAAI,iBAAiB;AACrB,MAAI,gBAAgB;AACpB,MAAI,UAAyB;AAC7B,MAAI,oBAAoB;AACxB,MAAI,eAAwB;AAY5B,MAAI,2BAA0C;AAC9C,MAAI,sBAAqC;AAEzC,WAAS,IAAI,OAAwD;AACnE,SAAK,IAAI,KAAK;AAAA,EAChB;AAEA,WAAS,cAAc,WAAmB,YAA2C;AACnF,SAAK,SAAS,QAAQ,WAAW,UAAU;AAAA,EAC7C;AAOA,QAAM,cAAc,CAClB,YACA,MACA,YAC0B;AAC1B,WAAO,KAAK,KAAK,YAAY,MAAM,KAAK,IAAI,GAAG,OAAO;AAAA,EACxD;AAEA,QAAM,aAA+C,wBAGnD;AAAA,IACA,MAAM;AAAA,IACN,YAAY,KAAK;AAAA,IACjB,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,SAAS,CAAC,OAAO,mBAAmB,cAAc,OAAO,cAAc;AAAA,IACvE,gBAAgB,CAAC,EAAE,YAAY,IAAI,MAAM;AACvC,uBAAiB;AACjB,gBAAU;AACV,sBAAgB,KAAK,IAAI;AACzB,UAAI,EAAE,OAAO,0BAA0B,IAAI,CAAC;AAC5C,oBAAc,0BAA0B,EAAE,IAAI,CAAC;AAAA,IACjD;AAAA,IACA,aAAa,CAAC,EAAE,MAAM,QAAQ,YAAY,SAAS,MAAM;AACvD,sBAAgB,YAAY,MAAM,QAAQ,QAAQ;AAAA,IACpD;AAAA,IACA,gBAAgB,CAAC,EAAE,KAAK,MAAM,IAAI,EAAE,OAAO,gCAAgC,KAAK,CAAC;AAAA,IACjF,cAAc,CAAC,SAAS;AACtB,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAI,EAAE,OAAO,yBAAyB,KAAK,CAAC;AAAA,IAC9C;AAAA,IACA,iBAAiB,MAAM,IAAI,EAAE,OAAO,2BAA2B,CAAC;AAAA,IAChE,YAAY,CAAC,EAAE,QAAQ,OAAO,SAAS,MACrC,IAAI,EAAE,OAAO,iCAAiC,QAAQ,OAAO,SAAS,CAAC;AAAA,IACzE,aAAa,CAAC,QAAQ,IAAI,EAAE,OAAO,wBAAwB,KAAK,IAAI,QAAQ,CAAC;AAAA,IAC7E;AAAA,IACA,KAAK,KAAK;AAAA;AAAA,EAEZ,CAAC;AAED,WAAS,YAAY,KAA6B;AAChD,UAAM,OAAO,WAAW,KAAK,GAAG;AAChC,QAAI,CAAC,QAAQ,WAAW,kBAAkB,MAAM,MAAM;AACpD,UAAI;AAAA,QACF,OAAO;AAAA,QACP,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAoB,gBAA8B;AACvE,QAAI,mBAAmB,gBAAgB;AACrC,UAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,MAAM;AAAA,QACjB,IAAI,MAAM;AAAA,QACV,YAAY;AAAA,QACZ;AAAA,MACF,CAAC;AACD;AAAA,IACF;AACA,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,8BAAsB,MAAM,IAAI,cAAc;AAC9C;AAAA,MACF,KAAK;AACH,mCAA2B,MAAM,IAAI,MAAM,KAAK;AAChD;AAAA,MACF,KAAK;AACH,gCAAwB,MAAM,EAAE;AAChC;AAAA,MACF,KAAK;AACH,0BAAkB,MAAM,IAAI,MAAM,MAAM;AACxC;AAAA,MACF;AACE,oBAAY,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,WAAS,sBAAsB,IAAY,gBAA8B;AACvE,UAAM,QAAQ,cAAc,IAAI,EAAE;AAClC,QAAI,CAAC,MAAO;AAQZ,QAAI,6BAA6B,QAAQ,mBAAmB,0BAA0B;AACpF,iCAA2B;AAC3B,YAAM,kBAAkB,aAAa,cAAc,EAAE,MAAM,oBAAoB,GAAG,KAAK,IAAI,CAAC;AAC5F,qBAAe,gBAAgB;AAC/B,iBAAW,OAAO,gBAAgB,SAAS;AACzC,YAAI,IAAI,SAAS,kBAAkB;AACjC,cAAI,EAAE,OAAO,gCAAgC,CAAC;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AACA,QAAI,MAAM,WAAW,aAAa,MAAM,mBAAmB;AACzD,YAAM,SAAS;AACf,YAAM,kBAAkB,WAAW,KAAK,CAAC;AACzC,YAAM,oBAAoB;AAC1B,YAAM,mBAAmB;AACzB;AAAA,IACF;AACA,UAAM,SAAS;AAAA,EACjB;AAEA,WAAS,2BAA2B,IAAY,OAAqB;AACnE,UAAM,QAAQ,cAAc,IAAI,EAAE;AAClC,QAAI,CAAC,MAAO;AACZ,kBAAc,OAAO,EAAE;AACvB,UAAM,mBAAmB,IAAI,MAAM,KAAK,CAAC;AAAA,EAC3C;AAEA,WAAS,wBAAwB,IAAkB;AACjD,UAAM,QAAQ,cAAc,IAAI,EAAE;AAClC,QAAI,CAAC,MAAO;AACZ,kBAAc,OAAO,EAAE;AACvB,UAAM,sBAAsB;AAAA,EAC9B;AAEA,WAAS,kBAAkB,IAAY,QAAmC;AACxE,UAAM,QAAQ,cAAc,IAAI,EAAE;AAClC,QAAI,CAAC,SAAS,MAAM,WAAW,gBAAiB;AAChD,UAAM,SAAS,MAAM,iBAAiB,MAAM,CAAC;AAAA,EAC/C;AAEA,WAAS,gBACP,YACA,MACA,QACA,UACM;AAMN,QAAI,6BAA6B,YAAY;AAC3C,iCAA2B;AAAA,IAC7B;AAEA,UAAM,cAAc,cAAc;AAElC,QAAI,mBAAmB;AACrB,UAAI,EAAE,OAAO,yCAAyC,MAAM,QAAQ,SAAS,CAAC;AAC9E;AAAA,IACF;AAEA,UAAM,WAAW,eAAe,MAAM,MAAM,KAAK,cAAc;AAC/D,QAAI,CAAC,UAAU;AACb,UAAI,EAAE,OAAO,+BAA+B,MAAM,QAAQ,SAAS,CAAC;AACpE;AAAA,IACF;AAEA,QAAI,EAAE,OAAO,uBAAuB,MAAM,QAAQ,aAAa,SAAS,CAAC;AACzE,kBAAc,uBAAuB;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,yBAAqB,QAAQ;AAG7B,+BAA2B;AAG3B,oBAAgB;AAAA,EAClB;AAOA,WAAS,qBAAqB,UAAwB;AACpD,UAAM,kBAAkB,aAAa,cAAc,EAAE,MAAM,oBAAoB,GAAG,KAAK,IAAI,CAAC;AAC5F,mBAAe,gBAAgB;AAC/B,eAAW,OAAO,gBAAgB,SAAS;AACzC,UAAI,IAAI,SAAS,kBAAkB;AACjC,YAAI,EAAE,OAAO,8BAA8B,CAAC;AAC5C,sBAAc,+BAA+B;AAAA,UAC3C,YAAY,aAAa;AAAA,UACzB,UAAU;AAAA,QACZ,CAAC;AAAA,MACH,WAAW,IAAI,SAAS,kBAAkB;AACxC,YAAI,EAAE,OAAO,gCAAgC,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,WAAS,0BAA0B,OAAgC;AACjE,QAAI,CAAC,cAAc,IAAI,MAAM,EAAE,EAAG;AAClC,QAAI;AACF,YAAM,SAAS,MAAM,CAAC,EAAE,MAAM,WAAW,MAAM,MAAM,KAAK,CAAC,CAAC;AAAA,IAC9D,SAAS,KAAK;AACZ,UAAI;AAAA,QACF,OAAO;AAAA,QACP,IAAI,MAAM;AAAA,QACV,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACtD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,WAAS,6BAAmC;AAM1C,UAAM,WAAW,MAAM,KAAK,cAAc,OAAO,CAAC;AAClD,mBAAe,MAAM;AACnB,iBAAW,SAAS,UAAU;AAC5B,kCAA0B,KAAK;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,kBAAwB;AAC/B,QAAI,kBAAmB;AAMvB,QAAI,iBAAiB,KAAM;AAO3B,UAAM,kBAAkB;AAAA,MACtB;AAAA,MACA,EAAE,MAAM,qBAAqB,QAAQ,UAAU,aAAa,EAAE;AAAA,MAC9D,KAAK,IAAI;AAAA,IACX;AACA,mBAAe,gBAAgB;AAE/B,YAAQ,gBAAgB,OAAO,MAAM;AAAA,MACnC,KAAK,eAAe;AAClB,cAAM,WAAW,aAAa;AAC9B,YAAI,aAAa,MAAM;AACrB,yBAAe;AACf;AAAA,QACF;AACA,cAAM,cAAc,WAAW,aAAa;AAC5C,cAAM,SAAS,KAAK,IAAI,GAAG,cAAc,KAAK,IAAI,CAAC;AACnD,uBAAe,KAAK,WAAW,MAAM;AACnC,yBAAe;AACf,0BAAgB;AAAA,QAClB,GAAG,MAAM;AACT;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,uBAAe,KAAK,WAAW,MAAM;AACnC,yBAAe;AACf,yBAAe;AAAA,QACjB,GAAG,gBAAgB,OAAO,EAAE;AAC5B;AAAA,MACF;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AACH,uBAAe;AACf;AAAA,MACF,KAAK;AACH,uBAAe;AACf;AAAA,MACF;AACE,oBAAY,gBAAgB,MAAM;AAAA,IACtC;AAAA,EACF;AAEA,WAAS,iBAAuB;AAC9B,QAAI,kBAAmB;AACvB,QAAI,aAAa,oBAAoB,MAAM;AACzC,UAAI,EAAE,OAAO,+BAA+B,CAAC;AAAA,IAC/C;AACA,QAAI;AACF,oBAAc;AAAA,IAChB,SAAS,KAAK;AACZ,UAAI;AAAA,QACF,OAAO;AAAA,QACP,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACtD,CAAC;AACD,YAAM,kBAAkB,aAAa,cAAc,EAAE,MAAM,oBAAoB,GAAG,KAAK,IAAI,CAAC;AAC5F,qBAAe,gBAAgB;AAC/B,sBAAgB;AAAA,IAClB;AAAA,EACF;AAYA,WAAS,gBAAsB;AAC7B,UAAM,YAAY,kBAAkB;AACpC,0BAAsB,KAAK,IAAI;AAM/B,UAAM,2BAA2B,aAAa,cAAc,OAAO;AAEnE,QAAI,mBAAmB,GAAG;AACxB,iBAAW,MAAM;AAAA,IACnB,OAAO;AACL,iBAAW,QAAQ;AAAA,IACrB;AAEA,QAAI,0BAA0B;AAC5B,iCAA2B;AAAA,IAC7B;AAEA,UAAM,eAAe,oBAAoB;AAEzC,QAAI,aAAa,SAAS,eAAe;AACvC,8BAAwB,aAAa,aAAa;AAClD;AAAA,IACF;AACA,QAAI,aAAa,gBAAgB,GAAG;AAClC,UAAI,EAAE,OAAO,2BAA2B,aAAa,aAAa,cAAc,CAAC;AAAA,IACnF;AACA,QAAI,WAAW;AACb;AAAA,QACE,aAAa;AAAA,QACb,KAAK,IAAI,KAAK,uBAAuB,KAAK,IAAI;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,WAAS,sBAAoC;AAC3C,QAAI,gBAAgB;AACpB,eAAW,SAAS,cAAc,OAAO,GAAG;AAO1C,YAAM,SAAS;AACf,YAAM,KAAK,YAAY;AAAA,QACrB,KAAK;AAAA,QACL,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,MACd,CAAC;AACD,UAAI,CAAC,IAAI;AACP,eAAO,EAAE,MAAM,eAAe,cAAc;AAAA,MAC9C;AACA;AAAA,IACF;AACA,WAAO,EAAE,MAAM,aAAa,cAAc;AAAA,EAC5C;AAEA,WAAS,wBAAwB,eAA6B;AAC5D,QAAI;AAAA,MACF,OAAO;AAAA,MACP,KAAK;AAAA,MACL;AAAA,MACA,cAAc,cAAc,OAAO;AAAA,IACrC,CAAC;AAOD,yBAAqB,KAAK,IAAI,IAAI,aAAa;AAM/C,UAAM,OAAO,WAAW,kBAAkB;AAC1C,QAAI,SAAS,MAAM;AACjB,UAAI;AACF,aAAK,KAAK,SAAS;AAAA,MACrB,SAAS,KAAK;AACZ,YAAI;AAAA,UACF,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACtD,CAAC;AAAA,MACH;AAAA,IACF;AACA,oBAAgB;AAAA,EAClB;AAEA,WAAS,uBAAuB,aAAqB,kBAAgC;AACnF,QAAI;AAAA,MACF,OAAO;AAAA,MACP,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF,CAAC;AACD,kBAAc,4BAA4B;AAAA,MACxC,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,WAAW,OAA2D;AAC7E,WAAO;AAAA,MACL,aAAa,MAAM,iBAAiB,KAAK;AAAA,IAC3C;AAAA,EACF;AAEA,WAAS,iBAAiB,OAAyC;AACjE,QAAI,CAAC,cAAc,IAAI,MAAM,EAAE,EAAG,QAAO,QAAQ,QAAQ;AACzD,UAAM,SAAS;AACf,QAAI,WAAW,kBAAkB,MAAM,MAAM;AAE3C,oBAAc,OAAO,MAAM,EAAE;AAC7B,aAAO,QAAQ,QAAQ;AAAA,IACzB;AACA,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,YAAM,sBAAsB;AAC5B,YAAM,OAAO,YAAY,EAAE,KAAK,eAAe,IAAI,MAAM,GAAG,CAAC;AAC7D,UAAI,CAAC,MAAM;AAET,sBAAc,OAAO,MAAM,EAAE;AAC7B,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,iBAAkD;AACzD,WAAO,EAAE,aAAa,YAAY;AAAA,IAAC,EAAE;AAAA,EACvC;AAEA,iBAAe,QAAuB;AACpC,QAAI,WAAW,kBAAkB,MAAM,KAAM;AAC7C,QAAI,mBAAmB;AACrB,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AACA,kBAAc;AAAA,EAChB;AAEA,iBAAe,UACb,MACA,UACA,MAC0C;AAC1C,QAAI,mBAAmB;AACrB,YAAM,IAAI,MAAM,0DAA0D;AAAA,IAC5E;AAMA,QAAI,aAAa,oBAAoB,MAAM;AACzC,YAAM,cAAc,aAAa,kBAAkB,aAAa;AAChE,UAAI,KAAK,IAAI,IAAI,aAAa;AAC5B,YAAI,EAAE,OAAO,oCAAoC,KAAK,CAAC;AACvD,eAAO,eAAe;AAAA,MACxB;AAAA,IACF;AAEA,UAAM,KAAK,WAAW;AACtB,UAAM,QAA2B;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV;AACA,UAAM,UAAU,IAAI,QAAyC,CAAC,SAAS,WAAW;AAChF,YAAM,oBAAoB;AAC1B,YAAM,mBAAmB;AAAA,IAC3B,CAAC;AACD,kBAAc,IAAI,IAAI,KAAK;AAE3B,QAAI,WAAW,kBAAkB,MAAM,MAAM;AAC3C,YAAM,MAAM;AAAA,IACd,OAAO;AACL,kBAAY,EAAE,KAAK,aAAa,IAAI,MAAM,KAAK,CAAC;AAAA,IAClD;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,WAA0B;AACvC,QAAI,kBAAmB;AACvB,wBAAoB;AACpB,QAAI,iBAAiB,MAAM;AACzB,WAAK,aAAa,YAAY;AAC9B,qBAAe;AAAA,IACjB;AAOA,eAAW,SAAS,cAAc,OAAO,GAAG;AAC1C,YAAM,mBAAmB,IAAI,MAAM,mCAAmC,CAAC;AACvE,YAAM,mBAAmB;AACzB,YAAM,oBAAoB;AAC1B,YAAM,sBAAsB;AAC5B,YAAM,sBAAsB;AAAA,IAC9B;AACA,kBAAc,MAAM;AAEpB,UAAM,OAAO,WAAW,kBAAkB;AAC1C,eAAW,QAAQ;AACnB,QAAI,SAAS,KAAM;AAEnB,gBAAY,EAAE,KAAK,WAAW,CAAC;AAE/B,UAAM,iBAAiB,IAAI;AAAA,EAC7B;AAEA,WAAS,eAAe,MAA6B,QAAqC;AACxF,QAAI;AAAA,MACF,OACE,WAAW,YACP,oCACA;AAAA,IACR,CAAC;AACD,QAAI;AACF,WAAK,KAAK,MAAM;AAAA,IAClB,SAAS,KAAK;AACZ,UAAI;AAAA,QACF,OAAO;AAAA,QACP;AAAA,QACA,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACtD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,WAAS,iBAAiB,MAA4C;AACpE,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,YAAM,MAAM,EAAE,MAAM,OAAO,QAAQ,MAAM;AACzC,YAAM,SAAS,MAAM;AACnB,YAAI,IAAI,KAAM;AACd,YAAI,OAAO;AACX,gBAAQ;AAAA,MACV;AAEA,WAAK,KAAK,QAAQ,MAAM;AACtB,YAAI,SAAS;AACb,eAAO;AAAA,MACT,CAAC;AAED,WAAK,WAAW,MAAM,kBAAkB,MAAM,KAAK,MAAM,GAAG,oBAAoB;AAAA,IAClF,CAAC;AAAA,EACH;AAEA,WAAS,kBACP,MACA,KACA,QACM;AACN,QAAI,IAAI,QAAQ,IAAI,OAAQ;AAC5B,mBAAe,MAAM,SAAS;AAC9B,SAAK,WAAW,MAAM,kBAAkB,MAAM,KAAK,MAAM,GAAG,mBAAmB;AAAA,EACjF;AAEA,WAAS,kBACP,MACA,KACA,QACM;AACN,QAAI,IAAI,QAAQ,IAAI,OAAQ;AAC5B,mBAAe,MAAM,SAAS;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,OAAO,WAAW,SAAS;AACtC;AAEA,SAAS,eAAe,MAAqB,QAAwC;AACnF,MAAI,WAAW,QAAQ,iBAAiB,IAAI,MAAM,EAAG,QAAO;AAC5D,MAAI,SAAS,QAAQ,SAAS,EAAG,QAAO;AACxC,SAAO;AACT;AAEA,SAAS,iBAAiB,QAAuD;AAC/E,SAAO,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAC3D;;;ADvqBA,IAAM,8BAA8B;AAC7B,IAAM,sBACX,YAAY,EAAE,6BAA6B;AAEtC,IAAM,8BAA8B;AACpC,IAAM,iCAAiC;AACvC,IAAM,iBAAiB;AACvB,IAAM,yBAAyB,IAAI;AAEnC,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,kBAAkB,IAAI;AAC5B,IAAM,sBAAsB,KAAK;AAGjC,IAAM,iCAAiC;AAmBvC,SAAS,0BAAwC;AACtD,SAAO;AAAA,IACL,cAAc;AAAA,IACd,WAAW;AAAA,IACX,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,gBAAgB,CAAC;AAAA,IACjB,YAAY;AAAA,EACd;AACF;AAEA,SAAS,oBAAoB,OAA8B;AACzD,SACE,MAAM,oBAAoB,QAC1B,MAAM,iBAAiB,KACvB,MAAM,cAAc,KACpB,MAAM,eAAe,WAAW,KAChC,MAAM,eAAe;AAEzB;AA2BO,SAAS,aACd,OACA,OACA,KACgB;AAChB,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,cAAc,OAAO,OAAO,GAAG;AAAA,IACxC,KAAK;AACH,aAAO,cAAc,OAAO,GAAG;AAAA,IACjC,KAAK;AACH,aAAO,cAAc,OAAO,GAAG;AAAA,IACjC;AACE,aAAO,YAAY,KAAK;AAAA,EAC5B;AACF;AAEA,SAAS,cACP,OACA,OACA,KACgB;AAEhB,QAAM,UAAU,aAAa,OAAO,GAAG;AAEvC,MAAI,QAAQ,oBAAoB,MAAM;AACpC,UAAM,cAAc,QAAQ,kBAAkB,QAAQ;AACtD,QAAI,MAAM,aAAa;AACrB,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,EAAE,MAAM,eAAe,QAAQ,eAAe;AAAA,QACtD,SAAS,CAAC;AAAA,MACZ;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO,EAAE,GAAG,SAAS,YAAY,IAAI;AAAA,MACrC,QACE,MAAM,eAAe,sBACjB,EAAE,MAAM,sBAAsB,IAC9B,EAAE,MAAM,YAAY;AAAA,MAC1B,SAAS,CAAC,EAAE,MAAM,gBAAgB,CAAC;AAAA,IACrC;AAAA,EACF;AAMA,MAAI,QAAQ,YAAY,KAAK,MAAM,QAAQ,gBAAgB,QAAQ,WAAW;AAC5E,UAAM,YAAY,QAAQ,gBAAgB,QAAQ,YAAY;AAC9D,WAAO,EAAE,OAAO,SAAS,QAAQ,EAAE,MAAM,QAAQ,IAAI,UAAU,GAAG,SAAS,CAAC,EAAE;AAAA,EAChF;AAMA,MAAI,YAAY;AAChB,MAAI,QAAQ,cAAc,KAAK,MAAM,WAAW,cAAc;AAC5D,gBAAY,EAAE,GAAG,SAAS,WAAW,+BAA+B;AAAA,EACtE;AAEA,MAAI,MAAM,eAAe,qBAAqB;AAC5C,WAAO,EAAE,OAAO,WAAW,QAAQ,EAAE,MAAM,sBAAsB,GAAG,SAAS,CAAC,EAAE;AAAA,EAClF;AACA,SAAO,EAAE,OAAO,WAAW,QAAQ,EAAE,MAAM,YAAY,GAAG,SAAS,CAAC,EAAE;AACxE;AAEA,SAAS,cAAc,OAAqB,MAA8B;AACxE,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAqB;AAAA,IACzB,cAAc;AAAA,IACd,WAAW;AAAA,IACX,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,gBAAgB,CAAC;AAAA,IACjB,YAAY;AAAA,EACd;AACA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ,EAAE,MAAM,OAAO;AAAA,IACvB,SAAS,cAAc,CAAC,EAAE,MAAM,iBAAiB,CAAC,IAAI,CAAC;AAAA,EACzD;AACF;AAEA,SAAS,cAAc,OAAqB,KAA6B;AACvE,QAAM,SAAS,CAAC,GAAG,MAAM,eAAe,OAAO,CAAC,MAAM,MAAM,IAAI,iBAAiB,GAAG,GAAG;AACvF,QAAM,cACJ,MAAM,cAAc,IAChB,8BACA,KAAK,IAAI,MAAM,YAAY,GAAG,cAAc;AAGlD,MAAI,MAAM,eAAe,MAAM;AAC7B,UAAM,eAAe,KAAK,IAAI,MAAM,oBAAoB,GAAG,mBAAmB;AAC9E,WAAO;AAAA,MACL,OAAO;AAAA,QACL,GAAG;AAAA,QACH,cAAc,MAAM,eAAe;AAAA,QACnC,WAAW;AAAA,QACX,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,QACnB,YAAY;AAAA,MACd;AAAA,MACA,QAAQ,EAAE,MAAM,OAAO;AAAA,MACvB,SAAS,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAAA,IACtC;AAAA,EACF;AAGA,MAAI,OAAO,UAAU,oBAAoB,MAAM,oBAAoB,MAAM;AACvE,WAAO;AAAA,MACL,OAAO;AAAA,QACL,GAAG;AAAA,QACH,cAAc,MAAM,eAAe;AAAA,QACnC,WAAW;AAAA,QACX,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,QACnB,YAAY;AAAA,MACd;AAAA,MACA,QAAQ,EAAE,MAAM,OAAO;AAAA,MACvB,SAAS,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAAA,IACtC;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,MACL,GAAG;AAAA,MACH,cAAc,MAAM,eAAe;AAAA,MACnC,WAAW;AAAA,MACX,eAAe;AAAA,MACf,gBAAgB;AAAA,IAClB;AAAA,IACA,QAAQ,EAAE,MAAM,OAAO;AAAA,IACvB,SAAS,CAAC;AAAA,EACZ;AACF;AAEA,SAAS,aAAa,OAAqB,KAA2B;AACpE,MAAI,MAAM,kBAAkB,KAAK,MAAM,cAAc,EAAG,QAAO;AAC/D,QAAM,YAAY,MAAM,MAAM;AAC9B,MAAI,aAAa,wBAAwB;AACvC,WAAO,EAAE,GAAG,OAAO,WAAW,GAAG,cAAc,GAAG,gBAAgB,CAAC,EAAE;AAAA,EACvE;AAEA,QAAM,WAAW,KAAK,MAAM,YAAY,GAAM;AAC9C,MAAI,aAAa,EAAG,QAAO;AAC3B,QAAM,OAAO,KAAK,IAAI,6BAA6B,MAAM,aAAa,QAAQ;AAC9E,SAAO,EAAE,GAAG,OAAO,WAAW,KAAK;AACrC;AAuCA,IAAI,cAAgC,gBAAgB;AAEpD,SAAS,kBAAoC;AAC3C,SAAO;AAAA,IACL,QAAQ,oBAAI,IAAiB;AAAA,IAC7B,cAAc,oBAAI,IAAiB;AAAA,IACnC,SAAS,oBAAI,IAA0B;AAAA,IACvC,cAAc,oBAAI,IAAoB;AAAA,IACtC,MAAM,YAAY;AAAA,IAClB,YAAY;AAAA,IACZ,mBAAmB;AAAA,EACrB;AACF;AAsBA,IAAM,YAAY,QAAQ,IAAI,WAAW,UAAU,QAAQ,IAAI,aAAa;AAE5E,SAAS,cAAoC;AAC3C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQL,WAAW,CAAC,MAAM,IAAI,SAAS;AAC7B,UAAI,UAAW,QAAO,sBAAsB,MAAM,IAAI,IAAI;AAC1D,aAAO,oBAAoB,MAAM,IAAI,IAAI;AAAA,IAC3C;AAAA,IACA,KAAK,MAAM;AAAA,IAAC;AAAA,IACZ,KAAK,MAAM,KAAK,IAAI;AAAA,IACpB,YAAY,CAAC,IAAI,OAAO,WAAW,IAAI,EAAE;AAAA,IACzC,cAAc,CAAC,UAAU;AAMvB,UAAI,UAAU,QAAQ,UAAU,OAAW;AAC3C,mBAAa,KAAc;AAAA,IAC7B;AAAA,EACF;AACF;AAEA,IAAM,sBAAyD,OAAO,MAAM,IAAI,SAAS;AACvF,QAAM,aAAa,iBAAiB;AACpC,QAAM,SAAS,MAAM,WAAW;AAAA,IAC9B;AAAA,IACA,CAAC,KAAK,WAAW;AACf,SAAG,KAAK,OAAO,IAAI,cAAc,CAAC;AAAA,IACpC;AAAA,IACA,iBAAiB,IAAI;AAAA,EACvB;AACA,SAAO,EAAE,aAAa,MAAM,OAAO,YAAY,EAAE;AACnD;AAEA,SAAS,iBAAiB,MAGxB;AACA,QAAM,MAA+E,CAAC;AACtF,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,KAAK,OAAQ,KAAI,SAAS,KAAK;AACnC,MAAI,KAAK,YAAY,iBAAiB,KAAK,YAAY,YAAY;AACjE,QAAI,UAAU,KAAK;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,eAAe,GAAyC;AAC/D,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,EAAE,MAAM,WAAW,MAAM,EAAE,KAAK;AAAA,IACzC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK;AAAA,IACtC;AACE,aAAO,YAAY,EAAE,IAAI;AAAA,EAC7B;AACF;AAEA,SAAS,mBAA4C;AACnD,MAAI,YAAY,WAAY,QAAO,YAAY;AAmB/C,QAAM,UAAU,YAAY;AAC5B,QAAM,YAAY,QAAQ,SAAS,KAAK;AACxC,QAAM,aAAa,YACf,IAAI,IAAI,wCAAwC,OAAO,EAAE,WACzD,IAAI,IAAI,eAAe,OAAO,EAAE;AACpC,cAAY,aAAa,8BAA8B;AAAA,IACrD,MAAM;AAAA,IACN;AAAA,IACA,KAAK,CAAC,UAAU,YAAY,KAAK,IAAI,KAAK;AAAA,IAC1C,SAAS,YAAY,qBAAqB;AAAA,IAC1C,KAAK,MAAM,YAAY,KAAK,IAAI;AAAA,IAChC,YAAY,CAAC,IAAI,OAAO,YAAY,KAAK,WAAW,IAAI,EAAE;AAAA,IAC1D,cAAc,CAAC,UAAU,YAAY,KAAK,aAAa,KAAK;AAAA,EAC9D,CAAC;AACD,SAAO,YAAY;AACrB;AAEA,IAAM,WAAwE,CAC5E,YACA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAAiB,YAAY,MAAM;AAAA,IACjC,GAAG;AAAA,IACH,UAAU,oBAAoB;AAAA,IAC9B,KAAK,kBAAkB,QAAQ,GAAG;AAAA,EACpC,CAAC;AAAA;AAGH,SAAS,YAAoB;AAC3B,SAAO,YAAY,OAAO,OAAO,YAAY,aAAa;AAC5D;AASO,SAAS,0BAA0B,MAGjC;AACP,cAAY,OAAO,EAAE,GAAG,YAAY,MAAM,KAAK,KAAK,IAAI;AACxD,MAAI,KAAK,SAAS;AAChB,gBAAY,oBAAoB,KAAK;AAAA,EACvC;AACF;AA+BA,IAAM,wBAA2D,OAAO,MAAM,IAAI,SAAS;AACzF,QAAM,MAAM,MAAM,OAAO,iBAAiB;AAC1C,SAAO,IAAI;AAAA,IACT;AAAA,IACA,CAAC,KAAK,WAAW;AACf,YAAM,UAA0B,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAClF,SAAG,KAAK,OAAO;AAAA,IACjB;AAAA,IACA;AAAA,EACF;AACF;AAMA,eAAsB,2BAA0C;AAC9D,QAAM,aAAa,YAAY;AAC/B,MAAI,CAAC,WAAY;AACjB,cAAY,aAAa;AACzB,QAAM,WAAW,SAAS;AAC5B;AAkBA,eAAsB,iBACpB,MACA,UACA,MACA,MACA,QAC4B;AAC5B,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,aAAa,YAAY,QAAQ,IAAI,IAAI,KAAK,wBAAwB;AAC5E,QAAM,WAAW;AAAA,IACf;AAAA,IACA,EAAE,MAAM,qBAAqB,QAAQ,aAAa,UAAU,EAAE;AAAA,IAC9D;AAAA,EACF;AACA,cAAY,QAAQ,IAAI,MAAM,SAAS,KAAK;AAC5C,cAAY,MAAM,SAAS,OAAO;AAElC,UAAQ,SAAS,OAAO,MAAM;AAAA,IAC5B,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B,KAAK,QAAQ;AACX,YAAM,SAAS,SAAS,OAAO;AAC/B,YAAM,IAAI,QAAc,CAAC,YAAY,KAAK,WAAW,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC7E,aAAO,iBAAiB,MAAM,UAAU,MAAM,MAAM,YAAY;AAAA,IAClE;AAAA,IACA,KAAK;AACH,eAAS,IAAI;AACb,aAAO,iBAAiB,MAAM,UAAU,MAAM,IAAI;AAAA,IACpD,KAAK;AACH,aAAO,iBAAiB,MAAM,UAAU,MAAM,IAAI;AAAA,IACpD,KAAK;AAEH,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AACE,aAAO,YAAY,SAAS,MAAM;AAAA,EACtC;AACF;AAEA,eAAe,iBACb,MACA,kBACA,MACA,MAC4B;AAC5B,QAAM,EAAE,KAAK,IAAI;AACjB,MAAI,QAA4B;AAQhC,QAAM,cAA2B,EAAE,MAAM,MAAM,YAAY,KAAK,IAAI,EAAE;AACtE,cAAY,aAAa,IAAI,WAAW;AAExC,QAAM,UAAU,CAAC,KAAmB,WAA2B;AAC7D,QAAI,MAAO,OAAM,aAAa,KAAK,IAAI;AACvC,qBAAiB,KAAK,MAAM;AAAA,EAC9B;AAEA,MAAI;AACF,UAAM,MAAM,MAAM,KAAK,UAAU,MAAM,SAAS,IAAI;AACpD,gBAAY,aAAa,OAAO,WAAW;AAC3C,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,aAAa,YAAY,QAAQ,IAAI,IAAI,KAAK,wBAAwB;AAC5E,UAAM,kBAAkB,aAAa,YAAY,EAAE,MAAM,oBAAoB,GAAG,GAAG;AACnF,QAAI,oBAAoB,gBAAgB,KAAK,GAAG;AAC9C,kBAAY,QAAQ,OAAO,IAAI;AAAA,IACjC,OAAO;AACL,kBAAY,QAAQ,IAAI,MAAM,gBAAgB,KAAK;AAAA,IACrD;AACA,gBAAY,MAAM,gBAAgB,OAAO;AAEzC,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ,aAAa,MAAM,IAAI,YAAY;AAAA,MACnC;AAAA,IACF;AACA,gBAAY,OAAO,IAAI,KAAK;AAC5B,SAAK,IAAI,EAAE,OAAO,6BAA6B,OAAO,YAAY,OAAO,KAAK,CAAC;AAG/E,WAAO;AAAA,MACL,aAAa,YAAY;AACvB,YAAI,SAAS,YAAY,OAAO,IAAI,KAAK,GAAG;AAC1C,sBAAY,OAAO,OAAO,KAAK;AAAA,QACjC;AAMA,cAAM,UAAU,YAAY,QAAQ,IAAI,IAAI;AAC5C,YAAI,WAAW,oBAAoB,OAAO,GAAG;AAC3C,sBAAY,QAAQ,OAAO,IAAI;AAAA,QACjC;AACA,cAAM,IAAI,YAAY;AAAA,MACxB;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,gBAAY,aAAa,OAAO,WAAW;AAC3C,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,YAAY,QAAQ,IAAI,IAAI,KAAK,wBAAwB;AAC5E,UAAM,kBAAkB,aAAa,YAAY,EAAE,MAAM,oBAAoB,GAAG,GAAG;AACnF,gBAAY,QAAQ,IAAI,MAAM,gBAAgB,KAAK;AACnD,UAAM,eAAe,iBAAiB,QAAQ,IAAI,CAAC;AACnD,SAAK,IAAI;AAAA,MACP,OAAO;AAAA,MACP;AAAA,MACA,YAAY,iBAAiB,IAAI;AAAA,MACjC;AAAA,MACA,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMpD,GAAI,eAAe,EAAE,OAAO,QAAiB,IAAI,CAAC;AAAA,IACpD,CAAC;AACD,gBAAY,MAAM,gBAAgB,OAAO;AAGzC,QAAI,gBAAgB,MAAM,oBAAoB,MAAM;AAClD,aAAO,qBAAqB;AAAA,IAC9B;AACA,UAAM;AAAA,EACR;AACF;AAEA,SAAS,iBAAiB,MAAuB;AAC/C,MAAI;AACF,WAAO,WAAW,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,WAAW,SAAgC,MAAyC;AAC3F,MAAI,SAA6B;AACjC,aAAW,SAAS,SAAS;AAC3B,QAAI,SAAS,QAAQ,MAAM,SAAS,KAAM;AAC1C,QAAI,WAAW,QAAQ,MAAM,aAAa,OAAO,WAAY,UAAS;AAAA,EACxE;AACA,SAAO;AACT;AAEA,SAAS,qBACP,SAC4D;AAC5D,QAAM,OAAO,WAAW,SAAS,MAAM;AACvC,MAAI,SAAS,KAAM,QAAO,EAAE,QAAQ,MAAM,mBAAmB,MAAM;AACnE,QAAM,MAAM,WAAW,SAAS,IAAI;AACpC,MAAI,QAAQ,KAAM,QAAO,EAAE,QAAQ,KAAK,mBAAmB,KAAK;AAChE,SAAO;AACT;AAEA,SAAS,SAAS,cAA4B;AAC5C,QAAM,EAAE,KAAK,IAAI;AACjB,MAAI,YAAY,OAAO,SAAS,GAAG;AACjC,UAAM,IAAI,MAAM,iFAA4E;AAAA,EAC9F;AAEA,QAAM,YAAY,qBAAqB,YAAY,MAAM;AACzD,MAAI,cAAc,MAAM;AACtB,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,QAAM,EAAE,QAAQ,kBAAkB,IAAI;AAEtC,cAAY,OAAO,OAAO,MAAM;AAChC,OAAK,IAAI;AAAA,IACP,OAAO,oBAAoB,mCAAmC;AAAA,IAC9D,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb;AAAA,EACF,CAAC;AAOD,iBAAe,MAAM;AACnB,WAAO,iBAAiB,MAAM,CAAC,EAAE,MAAM,WAAW,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,EACxE,CAAC;AAED,SAAO,YAAY,EAAE,MAAM,CAAC,QAAQ;AAClC,SAAK,IAAI;AAAA,MACP,OAAO;AAAA,MACP,MAAM,OAAO;AAAA,MACb,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACtD,CAAC;AAAA,EACH,CAAC;AAUD,MAAI,mBAAmB;AACrB,iCAA6B,MAAM;AAAA,EACrC;AACF;AAEA,SAAS,6BAA6B,QAA2B;AAC/D,QAAM,EAAE,KAAK,IAAI;AACjB,OAAK,IAAI;AAAA,IACP,OAAO;AAAA,IACP,MAAM,OAAO;AAAA,IACb,SAAS;AAAA,EACX,CAAC;AACD,OAAK,WAAW,MAAM;AACpB,SAAK,wBAAwB,MAAM;AAAA,EACrC,GAAG,8BAA8B;AACnC;AAEA,eAAe,wBAAwB,QAAoC;AACzE,QAAM,EAAE,KAAK,IAAI;AACjB,MAAI;AACF,UAAM,iBAAiB,OAAO,MAAM,OAAO,kBAAkB,CAAC,GAAG,aAAa,QAAQ;AACtF,SAAK,IAAI,EAAE,OAAO,wCAAwC,MAAM,OAAO,KAAK,CAAC;AAAA,EAC/E,SAAS,KAAK;AACZ,SAAK,IAAI;AAAA,MACP,OAAO;AAAA,MACP,MAAM,OAAO;AAAA,MACb,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACtD,CAAC;AAAA,EACH;AACF;AAEA,SAAS,YAAY,MAAc,SAA0C;AAC3E,QAAM,EAAE,KAAK,IAAI;AACjB,aAAW,OAAO,SAAS;AACzB,YAAQ,IAAI,MAAM;AAAA,MAChB,KAAK;AACH,aAAK,IAAI,EAAE,OAAO,6BAA6B,KAAK,CAAC;AACrD,oBAAY,aAAa,IAAI,MAAM,KAAK,IAAI,CAAC;AAC7C;AAAA,MACF,KAAK;AACH,aAAK,IAAI,EAAE,OAAO,+BAA+B,KAAK,CAAC;AACvD,oBAAY,aAAa,OAAO,IAAI;AACpC;AAAA,MACF,KAAK;AAEH;AAAA,MACF;AACE,oBAAY,GAAG;AAAA,IACnB;AAAA,EACF;AACF;AAEA,SAAS,uBAA0C;AACjD,SAAO,EAAE,aAAa,YAAY;AAAA,EAAC,EAAE;AACvC;","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveNodeExecPath
4
- } from "./chunk-MLEGFDFW.js";
4
+ } from "./chunk-Z37T5W6S.js";
5
5
 
6
6
  // src/shared/spawn-as-node.ts
7
7
  var isElectronOverride = null;
@@ -34,4 +34,4 @@ export {
34
34
  buildNodeSpawnEnv,
35
35
  rewriteElectronNodeScriptSpawn
36
36
  };
37
- //# sourceMappingURL=chunk-WYP4NTFE.js.map
37
+ //# sourceMappingURL=chunk-GM6MH4CD.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getLogFilePath
4
- } from "./chunk-3SXIJEPM.js";
4
+ } from "./chunk-QJP7JCIS.js";
5
5
 
6
6
  // src/shared/commands/output.ts
7
7
  import { appendFileSync } from "fs";
@@ -40,4 +40,4 @@ export {
40
40
  print,
41
41
  printError
42
42
  };
43
- //# sourceMappingURL=chunk-G7W4GFUC.js.map
43
+ //# sourceMappingURL=chunk-IJHF4OM4.js.map
@@ -2,24 +2,24 @@
2
2
  import {
3
3
  print,
4
4
  printError
5
- } from "./chunk-G7W4GFUC.js";
5
+ } from "./chunk-IJHF4OM4.js";
6
6
  import {
7
7
  getConfigPath,
8
8
  loadAuthToken,
9
9
  readConfig,
10
10
  writeConfig
11
- } from "./chunk-CVMNGYPR.js";
11
+ } from "./chunk-3WEEGJJN.js";
12
12
  import {
13
13
  DeviceExchangeCodeResponseSchema,
14
14
  DevicePollPendingSchema,
15
15
  DevicePollResponseSchema,
16
16
  DeviceStartResponseSchema,
17
17
  ROUTES
18
- } from "./chunk-DGX2QR6G.js";
18
+ } from "./chunk-4PBXNWJV.js";
19
19
  import {
20
20
  isDevMode,
21
21
  validateEnv
22
- } from "./chunk-MLEGFDFW.js";
22
+ } from "./chunk-Z37T5W6S.js";
23
23
  import {
24
24
  external_exports
25
25
  } from "./chunk-CNR7O5YH.js";
@@ -223,4 +223,4 @@ export {
223
223
  ensureAuthenticated,
224
224
  loginCommand
225
225
  };
226
- //# sourceMappingURL=chunk-2I5XDMUD.js.map
226
+ //# sourceMappingURL=chunk-KRX7OJER.js.map