@schoolai/shipyard 3.5.0-rc.20260504.0 → 3.5.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.
- package/dist/{auth-LS3NBD42.js → auth-SS7LV5XK.js} +4 -3
- package/dist/{chunk-GLH3V7NG.js → chunk-2J3WSIAF.js} +5 -3
- package/dist/{chunk-GLH3V7NG.js.map → chunk-2J3WSIAF.js.map} +1 -1
- package/dist/{chunk-YUG27SAR.js → chunk-2UN5AR7V.js} +2 -2
- package/dist/{chunk-ODCN6W33.js → chunk-3CAEALVL.js} +7 -5
- package/dist/{chunk-ODCN6W33.js.map → chunk-3CAEALVL.js.map} +1 -1
- package/dist/chunk-3MNPDCO5.js +1011 -0
- package/dist/chunk-3MNPDCO5.js.map +1 -0
- package/dist/{chunk-JQ7HCEFS.js → chunk-BNEE7ZPW.js} +8 -6
- package/dist/{chunk-JQ7HCEFS.js.map → chunk-BNEE7ZPW.js.map} +1 -1
- package/dist/{chunk-5LIPEC7P.js → chunk-GIFN3IPT.js} +4 -4
- package/dist/{chunk-3TB4VNFG.js → chunk-IISLTKYY.js} +2 -2
- package/dist/chunk-PI77CUEP.js +49 -0
- package/dist/chunk-PI77CUEP.js.map +1 -0
- package/dist/chunk-SNYEQHUK.js +64 -0
- package/dist/chunk-SNYEQHUK.js.map +1 -0
- package/dist/{chunk-XXTIKBCU.js → chunk-VBPHGPBR.js} +2 -2
- package/dist/{chunk-M5M6VC5F.js → chunk-VPMN47TL.js} +31 -72
- package/dist/chunk-VPMN47TL.js.map +1 -0
- package/dist/{git-repo-CNIKBYPB.js → git-repo-364VANDM.js} +5 -4
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/{logger-7XW3I4XN.js → logger-GQCSLSZH.js} +4 -3
- package/dist/{login-RHZDNC74.js → login-D6USDG5M.js} +7 -6
- package/dist/{logout-CUAAF5IK.js → logout-VUNCW5B2.js} +6 -5
- package/dist/{logout-CUAAF5IK.js.map → logout-VUNCW5B2.js.map} +1 -1
- package/dist/mcp-servers-FZV2P2ZO.js +16 -0
- package/dist/{roi-LN7MMRH7.js → roi-Y3MX5UW4.js} +4 -3
- package/dist/{roi-LN7MMRH7.js.map → roi-Y3MX5UW4.js.map} +1 -1
- package/dist/{serve-E7CHPJD4.js → serve-IVUGCBEE.js} +73 -462
- package/dist/{serve-E7CHPJD4.js.map → serve-IVUGCBEE.js.map} +1 -1
- package/dist/services/watcher-worker/worker.d.ts +49 -0
- package/dist/services/watcher-worker/worker.js +157 -0
- package/dist/services/watcher-worker/worker.js.map +1 -0
- package/dist/{skills-OMDIMU7D.js → skills-GPGRNV4R.js} +2 -2
- package/dist/start-I7ZONWK7.js +285 -0
- package/dist/start-I7ZONWK7.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-M5M6VC5F.js.map +0 -1
- package/dist/mcp-servers-3SHS2PEJ.js +0 -15
- package/dist/start-HQ42GOYF.js +0 -36
- package/dist/start-HQ42GOYF.js.map +0 -1
- /package/dist/{auth-LS3NBD42.js.map → auth-SS7LV5XK.js.map} +0 -0
- /package/dist/{chunk-YUG27SAR.js.map → chunk-2UN5AR7V.js.map} +0 -0
- /package/dist/{chunk-5LIPEC7P.js.map → chunk-GIFN3IPT.js.map} +0 -0
- /package/dist/{chunk-3TB4VNFG.js.map → chunk-IISLTKYY.js.map} +0 -0
- /package/dist/{chunk-XXTIKBCU.js.map → chunk-VBPHGPBR.js.map} +0 -0
- /package/dist/{git-repo-CNIKBYPB.js.map → git-repo-364VANDM.js.map} +0 -0
- /package/dist/{logger-7XW3I4XN.js.map → logger-GQCSLSZH.js.map} +0 -0
- /package/dist/{login-RHZDNC74.js.map → login-D6USDG5M.js.map} +0 -0
- /package/dist/{mcp-servers-3SHS2PEJ.js.map → mcp-servers-FZV2P2ZO.js.map} +0 -0
- /package/dist/{skills-OMDIMU7D.js.map → skills-GPGRNV4R.js.map} +0 -0
|
@@ -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';\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).\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 *\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 | '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\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\ninterface MetricsCollectorOpts {\n flushIntervalMs?: number;\n maxBatchSize?: number;\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 readonly #authToken: string;\n readonly #maxBatchSize: number;\n #buffer: BufferedEvent[] = [];\n #timer: ReturnType<typeof setInterval> | null = null;\n #disposed = false;\n\n constructor(workerUrl: string, authToken: string, opts?: MetricsCollectorOpts) {\n this.#workerUrl = workerUrl.replace(/\\/$/, '');\n this.#authToken = authToken;\n this.#maxBatchSize = opts?.maxBatchSize ?? 50;\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 payload: properties ?? {},\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 fetch(`${this.#workerUrl}/ingest`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.#authToken}`,\n },\n body: JSON.stringify(body),\n }).catch(() => {});\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\nexport function createMetricsCollector(\n workerUrl: string | undefined,\n authToken: string | undefined,\n telemetryEnabled: boolean\n): MetricsCapture {\n if (!telemetryEnabled || !workerUrl || !authToken) return NOOP_METRICS;\n return new MetricsCollector(workerUrl, authToken);\n}\n","import { fork as childProcessFork } from 'node:child_process';\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';\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;\nconst ENV_MAX = process.env.SHIPYARD_FILE_WATCHER_MAX;\nconst PARSED_ENV_MAX = ENV_MAX ? Number.parseInt(ENV_MAX, 10) : Number.NaN;\nexport const MAX_ACTIVE_WATCHERS =\n Number.isFinite(PARSED_ENV_MAX) && PARSED_ENV_MAX > 0\n ? PARSED_ENV_MAX\n : 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 const workerPath = new URL('./worker.js', import.meta.url).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) => childProcessFork(modulePath, args, options);\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 deps.log({\n event: 'file_watcher_subscribe_failed',\n path,\n err: err instanceof Error ? err.message : String(err),\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\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 { assertNever } from '../../shared/assert-never.js';\nimport {\n type AttemptState,\n decideAction,\n makeInitialAttemptState,\n} from '../../shared/file-watcher-guard.js';\nimport {\n decodeLine,\n encodeLine,\n type WorkerCommand,\n type WorkerParcelEvent,\n type WorkerReply,\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 * 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 fork: (\n modulePath: string,\n args: string[],\n options: { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }\n ) => ChildProcess;\n workerPath: string;\n log: SupervisorLogger;\n metrics?: SupervisorMetrics;\n now: () => number;\n setTimeout: (fn: () => void, ms: number) => unknown;\n 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\ninterface ChildHolder {\n process: ChildProcess;\n spawnedAt: number;\n /** Captured exit listeners can match against this to drop stale events. */\n generation: number;\n exited: boolean;\n}\n\nexport function createWatcherWorkerSupervisor(\n deps: WatcherWorkerSupervisorDeps\n): WatcherWorkerSupervisor {\n const subscriptions = new Map<string, SubscriptionEntry>();\n let child: ChildHolder | null = null;\n let restartState: AttemptState = makeInitialAttemptState();\n let nextGeneration = 1;\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\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 function sendCommand(holder: ChildHolder, cmd: WorkerCommand): boolean {\n const stdin = holder.process.stdin;\n if (!stdin || stdin.destroyed || holder.exited) return false;\n try {\n /**\n * Ignore the boolean return: `false` signals kernel-pipe backpressure,\n * but Node still queues the data in the user-land Writable buffer and\n * delivers it once the consumer drains. Treating backpressure as\n * failure would force unnecessary respawns under burst load. Real\n * pipe death is caught above (`stdin.destroyed`) or as an EPIPE throw.\n */\n stdin.write(`${encodeLine(cmd)}\\n`);\n return true;\n } catch (err) {\n log({\n event: 'watcher_worker_stdin_write_failed',\n err: err instanceof Error ? err.message : String(err),\n });\n return false;\n }\n }\n\n function dispatchReply(reply: WorkerReply, fromGeneration: number): void {\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 in forkAndReplay.\n * `fromGeneration` is captured in the attachChildIo closure of the holder\n * that wrote this reply — pipe drains can deliver replies from a dead\n * generation after a successor is live, and only the original generation\n * should consume 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 attachChildIo(holder: ChildHolder): void {\n const proc = holder.process;\n const { stdout, stderr } = proc;\n if (!stdout) {\n log({ event: 'watcher_worker_no_stdout' });\n return;\n }\n\n /**\n * Per-holder stdout buffer. Node's 'exit' event fires before stdio\n * drain completes, so a previous worker's residual chunks can arrive\n * after this listener is installed for the successor. Scoping the\n * buffer to the holder closure keeps each generation's partial-line\n * state isolated — generation N's leftover bytes can't corrupt\n * generation N+1's decode. fromGeneration is captured for the same\n * reason: a lagged ack from holder N must be attributed to N's\n * pending-respawn window, not whichever generation is currently live.\n */\n let stdoutBuffer = '';\n const fromGeneration = holder.generation;\n stdout.setEncoding('utf8');\n stdout.on('data', (chunk: string) => {\n stdoutBuffer += chunk;\n let nl = stdoutBuffer.indexOf('\\n');\n while (nl !== -1) {\n const line = stdoutBuffer.slice(0, nl);\n stdoutBuffer = stdoutBuffer.slice(nl + 1);\n nl = stdoutBuffer.indexOf('\\n');\n if (line.length === 0) continue;\n const decoded = decodeLine(line);\n if (decoded === null) {\n log({ event: 'watcher_worker_decode_failed', line });\n continue;\n }\n if (!('type' in decoded)) {\n log({ event: 'watcher_worker_unexpected_command', cmd: decoded.cmd });\n continue;\n }\n dispatchReply(decoded, fromGeneration);\n }\n });\n\n if (stderr) {\n stderr.setEncoding('utf8');\n let stderrBuffer = '';\n stderr.on('data', (chunk: string) => {\n stderrBuffer += chunk;\n let nl = stderrBuffer.indexOf('\\n');\n while (nl !== -1) {\n const line = stderrBuffer.slice(0, nl);\n stderrBuffer = stderrBuffer.slice(nl + 1);\n nl = stderrBuffer.indexOf('\\n');\n if (line.length > 0) {\n log({ event: 'watcher_worker_stderr', line });\n }\n }\n });\n }\n\n proc.on('exit', (code, signal) => {\n handleChildExit(holder, code, signal);\n });\n proc.on('error', (err) => {\n log({ event: 'watcher_worker_error', err: err.message });\n });\n }\n\n function handleChildExit(\n holder: ChildHolder,\n code: number | null,\n signal: NodeJS.Signals | null\n ): void {\n holder.exited = true;\n if (child !== holder) return;\n child = null;\n /**\n * If this child was the respawn we were awaiting an ack for, clear the\n * pending generation so the NEXT respawn is judged on its own window\n * rather than collecting a stale subscribe_success the moment the\n * post-exit successor happens to ack first.\n */\n if (pendingRespawnGeneration === holder.generation) {\n pendingRespawnGeneration = null;\n }\n\n const replayCount = subscriptions.size;\n const uptimeMs = deps.now() - holder.spawnedAt;\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(holder);\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(holder: ChildHolder): 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: deps.now() - holder.spawnedAt,\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 (pipe-failure schedules first, kill triggers\n * on-exit which schedules again). Without this guard both timers fire\n * and the second one runs forkAndReplay while a worker is already\n * alive — leaking the original process and replaying subscriptions to\n * the wrong target. The first scheduled timer is sufficient; let it\n * run with whatever restartState accumulated during the dual path.\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'. Without\n * this, `halfOpenAt` stays null forever and `decideFailure`'s doubled-\n * cooldown defense (line 240 of file-watcher-guard.ts) never fires —\n * a SIGABRT loop churns at the base 5min cooldown instead of backing\n * off toward the 30min cap.\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 /**\n * Circuit open and cooldown not yet expired. Schedule a wake-up\n * at the cooldown boundary; on wake, scheduleRespawn re-runs and\n * `decideRequest` will then transition to half-open.\n */\n const openedAt = restartState.circuitOpenedAt;\n if (openedAt === null) {\n /** Defensive: should never happen with reject_stub. */\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 /** evict_and_subscribe never fires here — activeCount is always 0. */\n attemptRespawn();\n return;\n case 'noop':\n /** decideRequest never returns 'noop'; defensive. */\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 /**\n * Half-open probe: the pure core flagged this attempt by setting\n * halfOpenAt; if the new worker dies, the circuit re-opens with\n * doubled cooldown.\n */\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 /** Treat fork-time failure same as runtime crash. */\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 * (F2). Without this, entries N+1..M would stay in the map with their\n * original resolve/reject closures and never receive an ack — their\n * `subscribe()` callers would hang forever.\n */\n type ReplayResult =\n | { kind: 'completed'; replayedCount: number }\n | { kind: 'pipe_failed'; replayedCount: number };\n\n function forkAndReplay(): void {\n const isRespawn = nextGeneration > 1;\n const replayStart = deps.now();\n const holder = spawnChildHolder();\n if (isRespawn && subscriptions.size > 0) {\n pendingRespawnGeneration = holder.generation;\n }\n\n const replayResult = replaySubscriptions(holder);\n\n if (replayResult.kind === 'pipe_failed') {\n handleReplayPipeFailure(holder, 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(holder, replayResult.replayedCount, deps.now() - replayStart);\n }\n }\n\n function spawnChildHolder(): ChildHolder {\n const proc = deps.fork(deps.workerPath, [], {\n stdio: ['pipe', 'pipe', 'pipe', 'ipc'],\n });\n const holder: ChildHolder = {\n process: proc,\n spawnedAt: deps.now(),\n generation: nextGeneration++,\n exited: false,\n };\n child = holder;\n log({ event: 'watcher_worker_started', pid: proc.pid ?? null });\n captureMetric('watcher_worker_started', { pid: proc.pid ?? null });\n attachChildIo(holder);\n return holder;\n }\n\n function replaySubscriptions(holder: ChildHolder): 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(holder, {\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(holder: ChildHolder, replayedCount: number): void {\n log({\n event: 'watcher_worker_replay_pipe_failed',\n pid: holder.process.pid ?? null,\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 * we may not see if the worker is alive but the pipe is closed.\n * If on-exit fires later, it records a second failure for the same\n * generation; that's acceptable — failure-count is a circuit-tripping\n * heuristic, not an exact crash count.\n */\n recordRestartFailure(holder);\n /**\n * Pipe death usually means the worker has already exited or its stdin\n * was closed underneath us. The on-exit handler will fire (if it hasn't\n * already) and trigger its own respawn cycle. Schedule one explicitly\n * here too — if the worker survives but the pipe is wedged, we still\n * need to recover, and `scheduleRespawn` is idempotent under the\n * shutdown / circuit-open guards.\n */\n if (!holder.exited) {\n try {\n holder.process.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(\n holder: ChildHolder,\n replayCount: number,\n replayDurationMs: number\n ): void {\n log({\n event: 'watcher_worker_respawned',\n pid: holder.process.pid ?? null,\n replayCount,\n replayDurationMs,\n });\n captureMetric('watcher_worker_respawned', {\n newPid: holder.process.pid ?? null,\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 (!child) {\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 = child !== null && sendCommand(child, { 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 (child !== 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 (child === null) {\n /** First subscribe: forkAndReplay sends the subscribe as part of replay. */\n await start();\n } else {\n sendCommand(child, { cmd: 'subscribe', id, path, opts });\n }\n /**\n * If the worker died while this command was buffered, the exit handler\n * will replay this entry to the next worker; the original promise stays\n * pending until the replay's 'subscribed' arrives.\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 — its resolve closure is captured in the entry\n * and never fired because we don't wait for an `unsubscribed` reply\n * during shutdown. Resolve them so callers awaiting unsubscribe see\n * shutdown as a successful tear-down (the watcher is going away either\n * way; from the consumer's perspective the subscription is gone).\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 holder = child;\n if (holder === null) return;\n\n sendCommand(holder, { cmd: 'shutdown' });\n\n await escalateShutdown(holder);\n }\n\n function killWithSignal(holder: ChildHolder, 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 holder.process.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(holder: ChildHolder): Promise<void> {\n return new Promise<void>((resolve) => {\n const ctx = { done: false };\n const finish = () => {\n if (ctx.done) return;\n ctx.done = true;\n resolve();\n };\n\n holder.process.once('exit', finish);\n if (holder.exited) {\n finish();\n return;\n }\n\n deps.setTimeout(() => onSigtermDeadline(holder, ctx, finish), SHUTDOWN_GRACEFUL_MS);\n });\n }\n\n function onSigtermDeadline(\n holder: ChildHolder,\n ctx: { done: boolean },\n finish: () => void\n ): void {\n if (ctx.done || holder.exited) return;\n killWithSignal(holder, 'SIGTERM');\n deps.setTimeout(() => onSigkillDeadline(holder, ctx, finish), SHUTDOWN_SIGTERM_MS);\n }\n\n function onSigkillDeadline(\n holder: ChildHolder,\n ctx: { done: boolean },\n finish: () => void\n ): void {\n if (ctx.done || holder.exited) return;\n killWithSignal(holder, '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":"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","names":[]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
logger
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2UN5AR7V.js";
|
|
5
5
|
import {
|
|
6
|
-
external_exports,
|
|
7
6
|
getShipyardHome
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PI77CUEP.js";
|
|
8
|
+
import {
|
|
9
|
+
external_exports
|
|
10
|
+
} from "./chunk-VPMN47TL.js";
|
|
9
11
|
import {
|
|
10
12
|
TIMEOUT_MS,
|
|
11
13
|
run,
|
|
@@ -292,7 +294,7 @@ async function detectEnvironments(lastKnown) {
|
|
|
292
294
|
repoPaths = await findGitRepos(homedir());
|
|
293
295
|
} catch (err) {
|
|
294
296
|
if (lastKnown && lastKnown.length > 0) {
|
|
295
|
-
const { logger: logger2 } = await import("./logger-
|
|
297
|
+
const { logger: logger2 } = await import("./logger-GQCSLSZH.js");
|
|
296
298
|
logger2.warn(
|
|
297
299
|
{ err, lastKnownCount: lastKnown.length },
|
|
298
300
|
"detectEnvironments findGitRepos threw \u2014 preserving lastKnown"
|
|
@@ -302,7 +304,7 @@ async function detectEnvironments(lastKnown) {
|
|
|
302
304
|
return [];
|
|
303
305
|
}
|
|
304
306
|
if (repoPaths.length === 0 && lastKnown && lastKnown.length > 0) {
|
|
305
|
-
const { logger: logger2 } = await import("./logger-
|
|
307
|
+
const { logger: logger2 } = await import("./logger-GQCSLSZH.js");
|
|
306
308
|
logger2.warn(
|
|
307
309
|
{ lastKnownCount: lastKnown.length },
|
|
308
310
|
"detectEnvironments walk empty but lastKnown non-empty \u2014 preserving lastKnown"
|
|
@@ -390,4 +392,4 @@ export {
|
|
|
390
392
|
detectEnvironments,
|
|
391
393
|
_testing
|
|
392
394
|
};
|
|
393
|
-
//# sourceMappingURL=chunk-
|
|
395
|
+
//# sourceMappingURL=chunk-BNEE7ZPW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/capabilities/git-repo.ts","../src/shared/capabilities/environments-cache.ts","../src/shared/fs-utils.ts","../src/shared/capabilities/repo-paths-cache.ts"],"sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { GitRepoInfo } from '@shipyard/session';\nimport {\n type CacheEntry,\n getHeadMtime,\n isCacheValid,\n loadCache,\n saveCache,\n} from './environments-cache.js';\nimport { getCachedRepoPaths, setCachedRepoPaths } from './repo-paths-cache.js';\nimport { run, runWithTimeout, TIMEOUT_MS } from './shell.js';\n\n/** Cache of directory paths to whether they are inside a git repository. */\nexport const gitRepoCache = new Map<string, boolean>();\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 = gitRepoCache.get(cwd);\n if (cached !== undefined) return cached;\n try {\n await runWithTimeout('git', ['rev-parse', '--git-dir'], cwd, TIMEOUT_MS);\n gitRepoCache.set(cwd, true);\n return true;\n } catch {\n gitRepoCache.set(cwd, false);\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 */\nexport async function getRepoDefaultBranch(cwd: string): Promise<string | null> {\n try {\n const head = (\n await runWithTimeout(\n 'git',\n ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'],\n cwd,\n TIMEOUT_MS\n )\n ).trim();\n if (!head) return null;\n return head.replace(/^origin\\//, '') || null;\n } catch {\n return null;\n }\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;\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\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 const [branchResult, remoteResult] = await Promise.allSettled([\n run('git', ['branch', '--show-current'], repoPath),\n run('git', ['remote', 'get-url', 'origin'], repoPath),\n ]);\n\n const branch = branchResult.status === 'fulfilled' ? branchResult.value || 'HEAD' : 'HEAD';\n const remote =\n remoteResult.status === 'fulfilled' ? remoteResult.value || 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 * 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` mtime 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 headMtimes = await Promise.all(allPaths.map(getHeadMtime));\n const nextCache = new Map<string, CacheEntry>();\n\n const resolved = await Promise.all(\n allPaths.map(async (path, idx) => {\n const currentMtime = headMtimes[idx] ?? null;\n const prev = prevCache.get(path);\n const cacheHit =\n prev !== undefined && currentMtime !== null && isCacheValid(prev, currentMtime);\n const info = await getRepoMetadata(path, cacheHit ? prev : undefined);\n if (info !== null && currentMtime !== null) {\n nextCache.set(path, {\n path,\n branch: info.branch,\n remote: info.remote ?? null,\n headMtimeMs: currentMtime,\n cachedAt: cacheHit && prev ? prev.cachedAt : Date.now(),\n });\n }\n return info;\n })\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 */\nasync function discoverWorktrees(repoPaths: string[]): Promise<string[]> {\n const results = await Promise.allSettled(repoPaths.map(listWorktreePaths));\n return results.flatMap((r) => (r.status === 'fulfilled' ? r.value : []));\n}\n\nasync function listWorktreePaths(repoPath: string): Promise<string[]> {\n try {\n const output = await runWithTimeout(\n 'git',\n ['worktree', 'list', '--porcelain'],\n repoPath,\n TIMEOUT_MS\n );\n return parseWorktreeListOutput(output, repoPath);\n } catch {\n return [];\n }\n}\n\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 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 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>;\n\nexport const CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction cacheFilePath(): string {\n return join(getShipyardHome(), 'data', 'environments-cache.json');\n}\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 return new Map();\n }\n return new Map(Object.entries(result.data.entries));\n } catch (err) {\n if (isEnoent(err)) {\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 * Resolve the actual path of HEAD for the given worktree.\n *\n * - If `<path>/.git` is a directory, HEAD lives at `<path>/.git/HEAD`.\n * - If `<path>/.git` is a file (linked worktree), it contains `gitdir: <path>`;\n * HEAD lives at `<gitdir>/HEAD`. The gitdir may be relative to `<path>`.\n * - Returns null on any error (missing, unreadable, malformed).\n */\nexport async function resolveHeadPath(worktreePath: string): Promise<string | null> {\n const dotGitPath = join(worktreePath, '.git');\n try {\n const dotGitStat = await stat(dotGitPath);\n if (dotGitStat.isDirectory()) {\n return join(dotGitPath, 'HEAD');\n }\n if (dotGitStat.isFile()) {\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 const gitdir = isAbsolute(gitdirRaw) ? gitdirRaw : resolve(dirname(dotGitPath), gitdirRaw);\n return join(gitdir, 'HEAD');\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport async function getHeadMtime(worktreePath: string): Promise<number | null> {\n const headPath = await resolveHeadPath(worktreePath);\n if (!headPath) return null;\n try {\n const headStat = await stat(headPath);\n return headStat.mtimeMs;\n } catch {\n return null;\n }\n}\n\nexport function isCacheValid(entry: CacheEntry, currentMtimeMs: number): boolean {\n if (entry.headMtimeMs !== currentMtimeMs) return false;\n if (Date.now() - entry.cachedAt >= CACHE_TTL_MS) return false;\n return true;\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","import { existsSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\n\nexport function isEnoent(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT';\n}\n\nexport function isEexist(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'EEXIST';\n}\n\n/**\n * Detect JSON parse errors or Zod validation failures that indicate\n * a corrupt store file. Shared across all file-backed stores.\n */\nexport function isCorruptionError(err: unknown): boolean {\n if (err instanceof SyntaxError) return true;\n if (typeof err === 'object' && err !== null && 'name' in err && err.name === 'ZodError')\n return true;\n return false;\n}\n\n/**\n * Walk up the directory tree from `startDir` to find the project root.\n *\n * Looks for `.git` (directory or file — worktrees use a `.git` file) or\n * `pnpm-workspace.yaml` as root markers.\n *\n * Returns a discriminated result so callers can distinguish \"marker found\"\n * (genuinely scoped, even if the marker is in startDir itself) from\n * \"no marker anywhere\" (genuinely unscoped). The bare-string return shape\n * conflated these, since both cases produced `startDir` — breaking the\n * unscoped detection in `serve.ts` whenever the daemon was launched from\n * a project's own root directory.\n *\n * In dev: `startDir` is typically `apps/daemon/` (pnpm runs from package dir),\n * so this walks up to find the monorepo root. In prod install: `startDir` is\n * the user's project directory where the daemon is launched, and `.git` is\n * found immediately. Called once at daemon startup — the result is used as\n * the workspace root for file-io, LSP, and file watching.\n */\nexport function findProjectRoot(startDir: string): { root: string; foundMarker: boolean } {\n let dir = startDir;\n while (true) {\n if (existsSync(join(dir, 'pnpm-workspace.yaml')) || existsSync(join(dir, '.git'))) {\n return { root: dir, foundMarker: true };\n }\n const parent = dirname(dir);\n if (parent === dir || parent === parse(dir).root) {\n return { root: startDir, foundMarker: false };\n }\n dir = parent;\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 info on\n * disk, keyed by `.git/HEAD` mtime). 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-15s cost on a busy machine.\n *\n * Lifetime: daemon-lifetime, invalidated only on explicit triggers. The\n * previous 24h TTL was a backstop for the 5-min capability-refresh tick that\n * used to walk $HOME on a periodic timer; that tick is gone (B0) and the\n * walk is event-driven now.\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\nexport {\n getCachedRepoPaths,\n setCachedRepoPaths,\n invalidateRepoPaths,\n addWorktreeToCache,\n removeWorktreeFromCache,\n};\n\ninterface CachedEntry {\n repos: string[];\n worktrees: string[];\n populatedAt: number;\n}\n\nlet cache: CachedEntry | null = null;\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}\n\nfunction invalidateRepoPaths(): void {\n cache = null;\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}\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}\n\nexport const _testing = {\n reset: invalidateRepoPaths,\n inspect: () => cache,\n};\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,eAAe;AACxB,SAAS,UAAU,QAAAA,aAAY;;;ACD/B,SAAS,OAAO,UAAU,QAAQ,MAAM,QAAQ,iBAAiB;AACjE,SAAS,WAAAC,UAAS,YAAY,QAAAC,OAAM,eAAe;;;ACDnD,SAAS,kBAAkB;AAC3B,SAAS,SAAS,MAAM,aAAa;AAE9B,SAAS,SAAS,KAAuB;AAC9C,SAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,UAAU,OAAO,IAAI,SAAS;AAClF;AAEO,SAAS,SAAS,KAAuB;AAC9C,SAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,UAAU,OAAO,IAAI,SAAS;AAClF;AAMO,SAAS,kBAAkB,KAAuB;AACvD,MAAI,eAAe,YAAa,QAAO;AACvC,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,UAAU,OAAO,IAAI,SAAS;AAC3E,WAAO;AACT,SAAO;AACT;AAqBO,SAAS,gBAAgB,UAA0D;AACxF,MAAI,MAAM;AACV,SAAO,MAAM;AACX,QAAI,WAAW,KAAK,KAAK,qBAAqB,CAAC,KAAK,WAAW,KAAK,KAAK,MAAM,CAAC,GAAG;AACjF,aAAO,EAAE,MAAM,KAAK,aAAa,KAAK;AAAA,IACxC;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,OAAO,WAAW,MAAM,GAAG,EAAE,MAAM;AAChD,aAAO,EAAE,MAAM,UAAU,aAAa,MAAM;AAAA,IAC9C;AACA,UAAM;AAAA,EACR;AACF;;;AD9CA,IAAM,mBAAmB,iBAAE,OAAO;AAAA,EAChC,MAAM,iBAAE,OAAO;AAAA,EACf,QAAQ,iBAAE,OAAO;AAAA,EACjB,QAAQ,iBAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,aAAa,iBAAE,OAAO;AAAA,EACtB,UAAU,iBAAE,OAAO;AACrB,CAAC;AAED,IAAM,kBAAkB,iBAAE,OAAO;AAAA,EAC/B,SAAS,iBAAE,OAAO,iBAAE,OAAO,GAAG,gBAAgB;AAChD,CAAC;AAKM,IAAM,eAAe,KAAK,KAAK,KAAK;AAE3C,SAAS,gBAAwB;AAC/B,SAAOC,MAAK,gBAAgB,GAAG,QAAQ,yBAAyB;AAClE;AAEA,eAAsB,YAA8C;AAClE,QAAM,WAAW,cAAc;AAC/B,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,UAAU,MAAM;AAC3C,UAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,UAAM,SAAS,gBAAgB,UAAU,MAAM;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,MAAM,EAAE,MAAM,SAAS,GAAG,kDAAkD;AACnF,aAAO,oBAAI,IAAI;AAAA,IACjB;AACA,WAAO,IAAI,IAAI,OAAO,QAAQ,OAAO,KAAK,OAAO,CAAC;AAAA,EACpD,SAAS,KAAK;AACZ,QAAI,SAAS,GAAG,GAAG;AACjB,aAAO,oBAAI,IAAI;AAAA,IACjB;AACA,WAAO,MAAM,EAAE,IAAI,GAAG,oDAAoD;AAC1E,WAAO,oBAAI,IAAI;AAAA,EACjB;AACF;AAUA,eAAsB,gBAAgB,cAA8C;AAClF,QAAM,aAAaA,MAAK,cAAc,MAAM;AAC5C,MAAI;AACF,UAAM,aAAa,MAAM,KAAK,UAAU;AACxC,QAAI,WAAW,YAAY,GAAG;AAC5B,aAAOA,MAAK,YAAY,MAAM;AAAA,IAChC;AACA,QAAI,WAAW,OAAO,GAAG;AACvB,YAAM,WAAW,MAAM,SAAS,YAAY,MAAM;AAClD,YAAM,QAAQ,SAAS,MAAM,sBAAsB;AACnD,UAAI,CAAC,QAAQ,CAAC,EAAG,QAAO;AACxB,YAAM,YAAY,MAAM,CAAC,EAAE,KAAK;AAChC,YAAM,SAAS,WAAW,SAAS,IAAI,YAAY,QAAQC,SAAQ,UAAU,GAAG,SAAS;AACzF,aAAOD,MAAK,QAAQ,MAAM;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,aAAa,cAA8C;AAC/E,QAAM,WAAW,MAAM,gBAAgB,YAAY;AACnD,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,WAAW,MAAM,KAAK,QAAQ;AACpC,WAAO,SAAS;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,OAAmB,gBAAiC;AAC/E,MAAI,MAAM,gBAAgB,eAAgB,QAAO;AACjD,MAAI,KAAK,IAAI,IAAI,MAAM,YAAY,aAAc,QAAO;AACxD,SAAO;AACT;AAEA,eAAsB,UAAU,SAAiD;AAC/E,QAAM,WAAW,cAAc;AAC/B,QAAM,UAAU,GAAG,QAAQ;AAC3B,MAAI;AACF,UAAM,MAAMC,SAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAMlD,UAAM,UAAU,MAAM,UAAU;AAChC,eAAW,CAAC,MAAM,KAAK,KAAK,SAAS;AACnC,cAAQ,IAAI,MAAM,KAAK;AAAA,IACzB;AACA,UAAM,UAAqB,EAAE,SAAS,OAAO,YAAY,OAAO,EAAE;AAClE,UAAM,UAAU,SAAS,KAAK,UAAU,OAAO,GAAG,MAAM;AACxD,UAAM,OAAO,SAAS,QAAQ;AAAA,EAChC,SAAS,KAAK;AACZ,WAAO,MAAM,EAAE,KAAK,SAAS,GAAG,iCAAiC;AACjE,SAAK,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrC;AACF;;;AErEA,IAAI,QAA4B;AAEhC,SAAS,qBAAsE;AAC7E,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,EAAE,OAAO,MAAM,OAAO,WAAW,MAAM,UAAU;AAC1D;AAEA,SAAS,mBAAmB,OAAiB,WAAqB,MAAc,KAAK,IAAI,GAAS;AAChG,UAAQ,EAAE,OAAO,WAAW,aAAa,IAAI;AAC/C;AAaA,SAAS,mBAAmB,cAAsB,gBAA8B;AAC9E,MAAI,CAAC,MAAO;AACZ,QAAM,QAAQ,MAAM,MAAM,SAAS,cAAc,IAC7C,MAAM,QACN,CAAC,GAAG,MAAM,OAAO,cAAc;AACnC,QAAM,YAAY,MAAM,UAAU,SAAS,YAAY,IACnD,MAAM,YACN,CAAC,GAAG,MAAM,WAAW,YAAY;AACrC,UAAQ,EAAE,OAAO,WAAW,aAAa,MAAM,YAAY;AAC7D;AAOA,SAAS,wBAAwB,cAA4B;AAC3D,MAAI,CAAC,MAAO;AACZ,QAAM,OAAO,MAAM,UAAU,OAAO,CAAC,MAAM,MAAM,YAAY;AAC7D,MAAI,KAAK,WAAW,MAAM,UAAU,OAAQ;AAC5C,UAAQ,EAAE,OAAO,MAAM,OAAO,WAAW,MAAM,aAAa,MAAM,YAAY;AAChF;;;AH3EO,IAAM,eAAe,oBAAI,IAAqB;AAOrD,eAAsB,UAAU,KAA+B;AAC7D,QAAM,SAAS,aAAa,IAAI,GAAG;AACnC,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI;AACF,UAAM,eAAe,OAAO,CAAC,aAAa,WAAW,GAAG,KAAK,UAAU;AACvE,iBAAa,IAAI,KAAK,IAAI;AAC1B,WAAO;AAAA,EACT,QAAQ;AACN,iBAAa,IAAI,KAAK,KAAK;AAC3B,WAAO;AAAA,EACT;AACF;AAEA,IAAI,mBAAmB;AAEvB,eAAsB,gBAAkC;AACtD,MAAI,iBAAkB,QAAO;AAC7B,MAAI;AACF,UAAM,IAAI,SAAS,CAAC,IAAI,CAAC;AACzB,uBAAmB;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,gBAAgB,oBAAI,IAAoB;AAE9C,eAAsB,eAAe,KAA8B;AACjE,QAAM,SAAS,cAAc,IAAI,GAAG;AACpC,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,YACJ,MAAM,eAAe,OAAO,CAAC,aAAa,iBAAiB,GAAG,KAAK,UAAU,GAC7E,KAAK;AACP,gBAAc,IAAI,KAAK,QAAQ;AAC/B,SAAO;AACT;AAEO,SAAS,eAAe,WAA2D;AACxF,QAAM,QAAQ,UAAU,MAAM,kCAAkC;AAChE,MAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAG,QAAO;AACrC,SAAO,EAAE,OAAO,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE;AAC3C;AAQA,eAAsB,qBAAqB,KAAqC;AAC9E,MAAI;AACF,UAAM,QACJ,MAAM;AAAA,MACJ;AAAA,MACA,CAAC,gBAAgB,WAAW,0BAA0B;AAAA,MACtD;AAAA,MACA;AAAA,IACF,GACA,KAAK;AACP,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,KAAK,QAAQ,aAAa,EAAE,KAAK;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,WACpB,UACA,YACA,KACkB;AAClB,MAAI;AACF,UAAM,eAAe,OAAO,CAAC,cAAc,iBAAiB,UAAU,UAAU,GAAG,KAAK,GAAK;AAC7F,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,eAAe,oBAAI,IAAI;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,YAAY;AAElB,eAAsB,aAAa,KAAa,QAAQ,GAAsB;AAC5E,MAAI,QAAQ,UAAW,QAAO,CAAC;AAE/B,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAE1D,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,SAAS,QAAQ;AACzB,eAAO,CAAC,GAAG;AAAA,MACb;AAAA,IACF;AAEA,QAAI,SAAS,UAAW,QAAO,CAAC;AAEhC,UAAM,WAAgC,CAAC;AACvC,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,UAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAChC,UAAI,aAAa,IAAI,MAAM,IAAI,EAAG;AAClC,eAAS,KAAK,aAAaC,MAAK,KAAK,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC;AAAA,IAC9D;AAEA,UAAM,UAAU,MAAM,QAAQ,IAAI,QAAQ;AAC1C,WAAO,QAAQ,KAAK;AAAA,EACtB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAsB,gBACpB,UACA,QAC6B;AAC7B,MAAI;AACF,QAAI,QAAQ;AACV,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,SAAS,QAAQ;AAAA,QACvB,QAAQ,OAAO;AAAA,QACf,GAAI,OAAO,WAAW,QAAQ,EAAE,QAAQ,OAAO,OAAO;AAAA,MACxD;AAAA,IACF;AAEA,UAAM,CAAC,cAAc,YAAY,IAAI,MAAM,QAAQ,WAAW;AAAA,MAC5D,IAAI,OAAO,CAAC,UAAU,gBAAgB,GAAG,QAAQ;AAAA,MACjD,IAAI,OAAO,CAAC,UAAU,WAAW,QAAQ,GAAG,QAAQ;AAAA,IACtD,CAAC;AAED,UAAM,SAAS,aAAa,WAAW,cAAc,aAAa,SAAS,SAAS;AACpF,UAAM,SACJ,aAAa,WAAW,cAAc,aAAa,SAAS,SAAY;AAE1E,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,SAAS,QAAQ;AAAA,MACvB;AAAA,MACA,GAAI,UAAU,EAAE,OAAO;AAAA,IACzB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAoBA,eAAsB,mBAAmB,WAAmD;AAC1F,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,IAAS;AAY1C,QAAM,cAAc,mBAAmB;AACvC,MAAI;AACJ,MAAI;AAEJ,MAAI,aAAa;AACf,gBAAY,YAAY;AACxB,oBAAgB,YAAY;AAAA,EAC9B,OAAO;AACL,QAAI;AACF,kBAAY,MAAM,aAAa,QAAQ,CAAC;AAAA,IAC1C,SAAS,KAAK;AACZ,UAAI,aAAa,UAAU,SAAS,GAAG;AACrC,cAAM,EAAE,QAAAC,QAAO,IAAI,MAAM,OAAO,sBAAc;AAC9C,QAAAA,QAAO;AAAA,UACL,EAAE,KAAK,gBAAgB,UAAU,OAAO;AAAA,UACxC;AAAA,QACF;AACA,eAAO;AAAA,MACT;AACA,aAAO,CAAC;AAAA,IACV;AAEA,QAAI,UAAU,WAAW,KAAK,aAAa,UAAU,SAAS,GAAG;AAM/D,YAAM,EAAE,QAAAA,QAAO,IAAI,MAAM,OAAO,sBAAc;AAC9C,MAAAA,QAAO;AAAA,QACL,EAAE,gBAAgB,UAAU,OAAO;AAAA,QACnC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,oBAAgB,MAAM,kBAAkB,SAAS;AACjD,uBAAmB,WAAW,aAAa;AAAA,EAC7C;AAEA,QAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,WAAW,GAAG,aAAa,CAAC,CAAC;AAE9D,QAAM,YAAY,MAAM,UAAU;AAClC,QAAM,aAAa,MAAM,QAAQ,IAAI,SAAS,IAAI,YAAY,CAAC;AAC/D,QAAM,YAAY,oBAAI,IAAwB;AAE9C,QAAM,WAAW,MAAM,QAAQ;AAAA,IAC7B,SAAS,IAAI,OAAO,MAAM,QAAQ;AAChC,YAAM,eAAe,WAAW,GAAG,KAAK;AACxC,YAAM,OAAO,UAAU,IAAI,IAAI;AAC/B,YAAM,WACJ,SAAS,UAAa,iBAAiB,QAAQ,aAAa,MAAM,YAAY;AAChF,YAAM,OAAO,MAAM,gBAAgB,MAAM,WAAW,OAAO,MAAS;AACpE,UAAI,SAAS,QAAQ,iBAAiB,MAAM;AAC1C,kBAAU,IAAI,MAAM;AAAA,UAClB;AAAA,UACA,QAAQ,KAAK;AAAA,UACb,QAAQ,KAAK,UAAU;AAAA,UACvB,aAAa;AAAA,UACb,UAAU,YAAY,OAAO,KAAK,WAAW,KAAK,IAAI;AAAA,QACxD,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,SAAS;AAEzB,SAAO,SAAS,OAAO,CAAC,SAA8B,SAAS,IAAI;AACrE;AAMA,eAAe,kBAAkB,WAAwC;AACvE,QAAM,UAAU,MAAM,QAAQ,WAAW,UAAU,IAAI,iBAAiB,CAAC;AACzE,SAAO,QAAQ,QAAQ,CAAC,MAAO,EAAE,WAAW,cAAc,EAAE,QAAQ,CAAC,CAAE;AACzE;AAEA,eAAe,kBAAkB,UAAqC;AACpE,MAAI;AACF,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,CAAC,YAAY,QAAQ,aAAa;AAAA,MAClC;AAAA,MACA;AAAA,IACF;AACA,WAAO,wBAAwB,QAAQ,QAAQ;AAAA,EACjD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,wBAAwB,QAAgB,UAA4B;AAC3E,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,QAAI,KAAK,WAAW,WAAW,GAAG;AAChC,YAAM,KAAK,KAAK,MAAM,YAAY,MAAM,EAAE,KAAK;AAC/C,UAAI,MAAM,OAAO,SAAU,OAAM,KAAK,EAAE;AAAA,IAC1C;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,WAAW;AAAA,EACtB;AAAA,EACA,uBAAuB,MAAM;AAC3B,uBAAmB;AAAA,EACrB;AACF;","names":["join","dirname","join","join","dirname","join","logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/capabilities/git-repo.ts","../src/shared/capabilities/environments-cache.ts","../src/shared/fs-utils.ts","../src/shared/capabilities/repo-paths-cache.ts"],"sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { GitRepoInfo } from '@shipyard/session';\nimport {\n type CacheEntry,\n getHeadMtime,\n isCacheValid,\n loadCache,\n saveCache,\n} from './environments-cache.js';\nimport { getCachedRepoPaths, setCachedRepoPaths } from './repo-paths-cache.js';\nimport { run, runWithTimeout, TIMEOUT_MS } from './shell.js';\n\n/** Cache of directory paths to whether they are inside a git repository. */\nexport const gitRepoCache = new Map<string, boolean>();\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 = gitRepoCache.get(cwd);\n if (cached !== undefined) return cached;\n try {\n await runWithTimeout('git', ['rev-parse', '--git-dir'], cwd, TIMEOUT_MS);\n gitRepoCache.set(cwd, true);\n return true;\n } catch {\n gitRepoCache.set(cwd, false);\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 */\nexport async function getRepoDefaultBranch(cwd: string): Promise<string | null> {\n try {\n const head = (\n await runWithTimeout(\n 'git',\n ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'],\n cwd,\n TIMEOUT_MS\n )\n ).trim();\n if (!head) return null;\n return head.replace(/^origin\\//, '') || null;\n } catch {\n return null;\n }\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;\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\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 const [branchResult, remoteResult] = await Promise.allSettled([\n run('git', ['branch', '--show-current'], repoPath),\n run('git', ['remote', 'get-url', 'origin'], repoPath),\n ]);\n\n const branch = branchResult.status === 'fulfilled' ? branchResult.value || 'HEAD' : 'HEAD';\n const remote =\n remoteResult.status === 'fulfilled' ? remoteResult.value || 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 * 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` mtime 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 headMtimes = await Promise.all(allPaths.map(getHeadMtime));\n const nextCache = new Map<string, CacheEntry>();\n\n const resolved = await Promise.all(\n allPaths.map(async (path, idx) => {\n const currentMtime = headMtimes[idx] ?? null;\n const prev = prevCache.get(path);\n const cacheHit =\n prev !== undefined && currentMtime !== null && isCacheValid(prev, currentMtime);\n const info = await getRepoMetadata(path, cacheHit ? prev : undefined);\n if (info !== null && currentMtime !== null) {\n nextCache.set(path, {\n path,\n branch: info.branch,\n remote: info.remote ?? null,\n headMtimeMs: currentMtime,\n cachedAt: cacheHit && prev ? prev.cachedAt : Date.now(),\n });\n }\n return info;\n })\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 */\nasync function discoverWorktrees(repoPaths: string[]): Promise<string[]> {\n const results = await Promise.allSettled(repoPaths.map(listWorktreePaths));\n return results.flatMap((r) => (r.status === 'fulfilled' ? r.value : []));\n}\n\nasync function listWorktreePaths(repoPath: string): Promise<string[]> {\n try {\n const output = await runWithTimeout(\n 'git',\n ['worktree', 'list', '--porcelain'],\n repoPath,\n TIMEOUT_MS\n );\n return parseWorktreeListOutput(output, repoPath);\n } catch {\n return [];\n }\n}\n\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 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 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>;\n\nexport const CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction cacheFilePath(): string {\n return join(getShipyardHome(), 'data', 'environments-cache.json');\n}\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 return new Map();\n }\n return new Map(Object.entries(result.data.entries));\n } catch (err) {\n if (isEnoent(err)) {\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 * Resolve the actual path of HEAD for the given worktree.\n *\n * - If `<path>/.git` is a directory, HEAD lives at `<path>/.git/HEAD`.\n * - If `<path>/.git` is a file (linked worktree), it contains `gitdir: <path>`;\n * HEAD lives at `<gitdir>/HEAD`. The gitdir may be relative to `<path>`.\n * - Returns null on any error (missing, unreadable, malformed).\n */\nexport async function resolveHeadPath(worktreePath: string): Promise<string | null> {\n const dotGitPath = join(worktreePath, '.git');\n try {\n const dotGitStat = await stat(dotGitPath);\n if (dotGitStat.isDirectory()) {\n return join(dotGitPath, 'HEAD');\n }\n if (dotGitStat.isFile()) {\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 const gitdir = isAbsolute(gitdirRaw) ? gitdirRaw : resolve(dirname(dotGitPath), gitdirRaw);\n return join(gitdir, 'HEAD');\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport async function getHeadMtime(worktreePath: string): Promise<number | null> {\n const headPath = await resolveHeadPath(worktreePath);\n if (!headPath) return null;\n try {\n const headStat = await stat(headPath);\n return headStat.mtimeMs;\n } catch {\n return null;\n }\n}\n\nexport function isCacheValid(entry: CacheEntry, currentMtimeMs: number): boolean {\n if (entry.headMtimeMs !== currentMtimeMs) return false;\n if (Date.now() - entry.cachedAt >= CACHE_TTL_MS) return false;\n return true;\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","import { existsSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\n\nexport function isEnoent(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT';\n}\n\nexport function isEexist(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'EEXIST';\n}\n\n/**\n * Detect JSON parse errors or Zod validation failures that indicate\n * a corrupt store file. Shared across all file-backed stores.\n */\nexport function isCorruptionError(err: unknown): boolean {\n if (err instanceof SyntaxError) return true;\n if (typeof err === 'object' && err !== null && 'name' in err && err.name === 'ZodError')\n return true;\n return false;\n}\n\n/**\n * Walk up the directory tree from `startDir` to find the project root.\n *\n * Looks for `.git` (directory or file — worktrees use a `.git` file) or\n * `pnpm-workspace.yaml` as root markers.\n *\n * Returns a discriminated result so callers can distinguish \"marker found\"\n * (genuinely scoped, even if the marker is in startDir itself) from\n * \"no marker anywhere\" (genuinely unscoped). The bare-string return shape\n * conflated these, since both cases produced `startDir` — breaking the\n * unscoped detection in `serve.ts` whenever the daemon was launched from\n * a project's own root directory.\n *\n * In dev: `startDir` is typically `apps/daemon/` (pnpm runs from package dir),\n * so this walks up to find the monorepo root. In prod install: `startDir` is\n * the user's project directory where the daemon is launched, and `.git` is\n * found immediately. Called once at daemon startup — the result is used as\n * the workspace root for file-io, LSP, and file watching.\n */\nexport function findProjectRoot(startDir: string): { root: string; foundMarker: boolean } {\n let dir = startDir;\n while (true) {\n if (existsSync(join(dir, 'pnpm-workspace.yaml')) || existsSync(join(dir, '.git'))) {\n return { root: dir, foundMarker: true };\n }\n const parent = dirname(dir);\n if (parent === dir || parent === parse(dir).root) {\n return { root: startDir, foundMarker: false };\n }\n dir = parent;\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 info on\n * disk, keyed by `.git/HEAD` mtime). 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-15s cost on a busy machine.\n *\n * Lifetime: daemon-lifetime, invalidated only on explicit triggers. The\n * previous 24h TTL was a backstop for the 5-min capability-refresh tick that\n * used to walk $HOME on a periodic timer; that tick is gone (B0) and the\n * walk is event-driven now.\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\nexport {\n getCachedRepoPaths,\n setCachedRepoPaths,\n invalidateRepoPaths,\n addWorktreeToCache,\n removeWorktreeFromCache,\n};\n\ninterface CachedEntry {\n repos: string[];\n worktrees: string[];\n populatedAt: number;\n}\n\nlet cache: CachedEntry | null = null;\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}\n\nfunction invalidateRepoPaths(): void {\n cache = null;\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}\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}\n\nexport const _testing = {\n reset: invalidateRepoPaths,\n inspect: () => cache,\n};\n"],"mappings":"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","names":["join","dirname","join","join","dirname","join","logger"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
logger
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2UN5AR7V.js";
|
|
5
5
|
import {
|
|
6
6
|
external_exports
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VPMN47TL.js";
|
|
8
8
|
|
|
9
9
|
// src/shared/capabilities/mcp-servers.ts
|
|
10
10
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -529,7 +529,7 @@ async function detectMCPServers(environments, tokenStore, log, lastKnown) {
|
|
|
529
529
|
try {
|
|
530
530
|
return await detectMCPServersInner(environments, tokenStore, log);
|
|
531
531
|
} catch (err) {
|
|
532
|
-
const { logger: logger2 } = await import("./logger-
|
|
532
|
+
const { logger: logger2 } = await import("./logger-GQCSLSZH.js");
|
|
533
533
|
if (lastKnown && lastKnown.length > 0) {
|
|
534
534
|
logger2.warn(
|
|
535
535
|
{ err, lastKnownCount: lastKnown.length },
|
|
@@ -609,4 +609,4 @@ export {
|
|
|
609
609
|
redactEnv,
|
|
610
610
|
detectMCPServers
|
|
611
611
|
};
|
|
612
|
-
//# sourceMappingURL=chunk-
|
|
612
|
+
//# sourceMappingURL=chunk-GIFN3IPT.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getLogFilePath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2UN5AR7V.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-
|
|
43
|
+
//# sourceMappingURL=chunk-IISLTKYY.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
external_exports
|
|
4
|
+
} from "./chunk-VPMN47TL.js";
|
|
5
|
+
|
|
6
|
+
// src/shared/env.ts
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
process.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING = "1";
|
|
10
|
+
process.env.CLAUDE_CODE_ENABLE_TASKS = "1";
|
|
11
|
+
function isDevMode() {
|
|
12
|
+
return process.env.SHIPYARD_DEV === "1" || process.env.SHIPYARD_DEV === "true";
|
|
13
|
+
}
|
|
14
|
+
function isVanillaAgentMode() {
|
|
15
|
+
return process.env.SHIPYARD_VANILLA_AGENT === "1" || process.env.SHIPYARD_VANILLA_AGENT === "true";
|
|
16
|
+
}
|
|
17
|
+
function shipyardDirName() {
|
|
18
|
+
return isDevMode() ? ".shipyard-dev" : ".shipyard";
|
|
19
|
+
}
|
|
20
|
+
function getShipyardHome() {
|
|
21
|
+
if (process.env.SHIPYARD_HOME) return process.env.SHIPYARD_HOME;
|
|
22
|
+
return join(homedir(), shipyardDirName());
|
|
23
|
+
}
|
|
24
|
+
var EnvSchema = external_exports.object({
|
|
25
|
+
ANTHROPIC_API_KEY: external_exports.string().min(1).optional(),
|
|
26
|
+
SHIPYARD_DEV: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true"),
|
|
27
|
+
SHIPYARD_DATA_DIR: external_exports.string().optional().transform((v) => v ?? `~/${shipyardDirName()}/data`),
|
|
28
|
+
LOG_LEVEL: external_exports.enum(["debug", "info", "warn", "error"]).default("warn"),
|
|
29
|
+
SHIPYARD_SIGNALING_URL: external_exports.string().url().optional(),
|
|
30
|
+
SHIPYARD_USER_TOKEN: external_exports.string().optional(),
|
|
31
|
+
SHIPYARD_USER_ID: external_exports.string().optional(),
|
|
32
|
+
SHIPYARD_MACHINE_ID: external_exports.string().optional(),
|
|
33
|
+
SHIPYARD_MACHINE_NAME: external_exports.string().optional(),
|
|
34
|
+
SHIPYARD_USER_DISPLAY_NAME: external_exports.string().optional(),
|
|
35
|
+
SHIPYARD_HOME: external_exports.string().optional(),
|
|
36
|
+
SHIPYARD_WORKTREE_NAME: external_exports.string().optional(),
|
|
37
|
+
SHIPYARD_VANILLA_AGENT: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true")
|
|
38
|
+
});
|
|
39
|
+
function validateEnv() {
|
|
40
|
+
return EnvSchema.parse(process.env);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
isDevMode,
|
|
45
|
+
isVanillaAgentMode,
|
|
46
|
+
getShipyardHome,
|
|
47
|
+
validateEnv
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=chunk-PI77CUEP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/env.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { z } from 'zod';\n\n/**\n * Enable fine-grained tool streaming so MCP tool inputs stream token-by-token\n * via input_json_delta events instead of being buffered as complete JSON.\n * Set early so the subprocess inherits it.\n */\nprocess.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING = '1';\n\n/**\n * Enable the Task system (TaskCreate/TaskGet/TaskList/TaskUpdate) which provides\n * structured task management with dependency tracking (blocks/blockedBy).\n * Replaces TodoWrite — when enabled, CC only has TaskCreate (not TodoWrite).\n */\nprocess.env.CLAUDE_CODE_ENABLE_TASKS = '1';\n\nexport function isDevMode(): boolean {\n return process.env.SHIPYARD_DEV === '1' || process.env.SHIPYARD_DEV === 'true';\n}\n\n/**\n * When enabled, the agent runs with only Shipyard builtins (system prompt,\n * comment + visualize MCP servers) and no user/plugin MCP servers.\n * Useful for testing the harness in isolation without external interference.\n *\n * Set `SHIPYARD_VANILLA_AGENT=1` to activate.\n */\nexport function isVanillaAgentMode(): boolean {\n return (\n process.env.SHIPYARD_VANILLA_AGENT === '1' || process.env.SHIPYARD_VANILLA_AGENT === 'true'\n );\n}\n\nfunction shipyardDirName(): string {\n return isDevMode() ? '.shipyard-dev' : '.shipyard';\n}\n\nexport function getShipyardHome(): string {\n if (process.env.SHIPYARD_HOME) return process.env.SHIPYARD_HOME;\n return join(homedir(), shipyardDirName());\n}\n\nexport const EnvSchema = z.object({\n ANTHROPIC_API_KEY: z.string().min(1).optional(),\n SHIPYARD_DEV: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_DATA_DIR: z\n .string()\n .optional()\n .transform((v) => v ?? `~/${shipyardDirName()}/data`),\n LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('warn'),\n SHIPYARD_SIGNALING_URL: z.string().url().optional(),\n SHIPYARD_USER_TOKEN: z.string().optional(),\n SHIPYARD_USER_ID: z.string().optional(),\n SHIPYARD_MACHINE_ID: z.string().optional(),\n SHIPYARD_MACHINE_NAME: z.string().optional(),\n SHIPYARD_USER_DISPLAY_NAME: z.string().optional(),\n SHIPYARD_HOME: z.string().optional(),\n SHIPYARD_WORKTREE_NAME: z.string().optional(),\n SHIPYARD_VANILLA_AGENT: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n});\n\nexport type Env = z.infer<typeof EnvSchema>;\n\nexport function validateEnv(): Env {\n return EnvSchema.parse(process.env);\n}\n"],"mappings":";;;;;;AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AAQrB,QAAQ,IAAI,iDAAiD;AAO7D,QAAQ,IAAI,2BAA2B;AAEhC,SAAS,YAAqB;AACnC,SAAO,QAAQ,IAAI,iBAAiB,OAAO,QAAQ,IAAI,iBAAiB;AAC1E;AASO,SAAS,qBAA8B;AAC5C,SACE,QAAQ,IAAI,2BAA2B,OAAO,QAAQ,IAAI,2BAA2B;AAEzF;AAEA,SAAS,kBAA0B;AACjC,SAAO,UAAU,IAAI,kBAAkB;AACzC;AAEO,SAAS,kBAA0B;AACxC,MAAI,QAAQ,IAAI,cAAe,QAAO,QAAQ,IAAI;AAClD,SAAO,KAAK,QAAQ,GAAG,gBAAgB,CAAC;AAC1C;AAEO,IAAM,YAAY,iBAAE,OAAO;AAAA,EAChC,mBAAmB,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC9C,cAAc,iBACX,KAAK,CAAC,KAAK,QAAQ,KAAK,SAAS,EAAE,CAAC,EACpC,SAAS,EACT,UAAU,CAAC,MAAM,MAAM,OAAO,MAAM,MAAM;AAAA,EAC7C,mBAAmB,iBAChB,OAAO,EACP,SAAS,EACT,UAAU,CAAC,MAAM,KAAK,KAAK,gBAAgB,CAAC,OAAO;AAAA,EACtD,WAAW,iBAAE,KAAK,CAAC,SAAS,QAAQ,QAAQ,OAAO,CAAC,EAAE,QAAQ,MAAM;AAAA,EACpE,wBAAwB,iBAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EAClD,qBAAqB,iBAAE,OAAO,EAAE,SAAS;AAAA,EACzC,kBAAkB,iBAAE,OAAO,EAAE,SAAS;AAAA,EACtC,qBAAqB,iBAAE,OAAO,EAAE,SAAS;AAAA,EACzC,uBAAuB,iBAAE,OAAO,EAAE,SAAS;AAAA,EAC3C,4BAA4B,iBAAE,OAAO,EAAE,SAAS;AAAA,EAChD,eAAe,iBAAE,OAAO,EAAE,SAAS;AAAA,EACnC,wBAAwB,iBAAE,OAAO,EAAE,SAAS;AAAA,EAC5C,wBAAwB,iBACrB,KAAK,CAAC,KAAK,QAAQ,KAAK,SAAS,EAAE,CAAC,EACpC,SAAS,EACT,UAAU,CAAC,MAAM,MAAM,OAAO,MAAM,MAAM;AAC/C,CAAC;AAIM,SAAS,cAAmB;AACjC,SAAO,UAAU,MAAM,QAAQ,GAAG;AACpC;","names":[]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
external_exports
|
|
4
|
+
} from "./chunk-VPMN47TL.js";
|
|
5
|
+
|
|
6
|
+
// src/shared/assert-never.ts
|
|
7
|
+
function assertNever(value) {
|
|
8
|
+
throw new Error(`Unexpected value: ${JSON.stringify(value)}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/services/watcher-worker/worker-protocol.ts
|
|
12
|
+
var SubscribeOptionsSchema = external_exports.object({
|
|
13
|
+
ignore: external_exports.array(external_exports.string()).optional(),
|
|
14
|
+
/**
|
|
15
|
+
* Constrained to the subset the daemon actually uses. `@parcel/watcher`
|
|
16
|
+
* supports more backends but they are platform-specific and we never set
|
|
17
|
+
* them — accepting the wider union here would just invite bugs.
|
|
18
|
+
*/
|
|
19
|
+
backend: external_exports.enum(["default", "brute-force", "watchman"]).optional()
|
|
20
|
+
});
|
|
21
|
+
var CommandSchema = external_exports.discriminatedUnion("cmd", [
|
|
22
|
+
external_exports.object({
|
|
23
|
+
cmd: external_exports.literal("subscribe"),
|
|
24
|
+
id: external_exports.string(),
|
|
25
|
+
path: external_exports.string(),
|
|
26
|
+
opts: SubscribeOptionsSchema
|
|
27
|
+
}),
|
|
28
|
+
external_exports.object({ cmd: external_exports.literal("unsubscribe"), id: external_exports.string() }),
|
|
29
|
+
external_exports.object({ cmd: external_exports.literal("shutdown") })
|
|
30
|
+
]);
|
|
31
|
+
var ParcelEventSchema = external_exports.object({
|
|
32
|
+
type: external_exports.enum(["create", "update", "delete"]),
|
|
33
|
+
path: external_exports.string()
|
|
34
|
+
});
|
|
35
|
+
var ReplySchema = external_exports.discriminatedUnion("type", [
|
|
36
|
+
external_exports.object({ type: external_exports.literal("subscribed"), id: external_exports.string() }),
|
|
37
|
+
external_exports.object({ type: external_exports.literal("subscribe_failed"), id: external_exports.string(), error: external_exports.string() }),
|
|
38
|
+
external_exports.object({ type: external_exports.literal("unsubscribed"), id: external_exports.string() }),
|
|
39
|
+
external_exports.object({ type: external_exports.literal("events"), id: external_exports.string(), events: external_exports.array(ParcelEventSchema) })
|
|
40
|
+
]);
|
|
41
|
+
function encodeLine(msg) {
|
|
42
|
+
return JSON.stringify(msg);
|
|
43
|
+
}
|
|
44
|
+
function decodeLine(line) {
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(line);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
52
|
+
const asCmd = CommandSchema.safeParse(parsed);
|
|
53
|
+
if (asCmd.success) return asCmd.data;
|
|
54
|
+
const asReply = ReplySchema.safeParse(parsed);
|
|
55
|
+
if (asReply.success) return asReply.data;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
assertNever,
|
|
61
|
+
encodeLine,
|
|
62
|
+
decodeLine
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-SNYEQHUK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/assert-never.ts","../src/services/watcher-worker/worker-protocol.ts"],"sourcesContent":["export function assertNever(value: never): never {\n throw new Error(`Unexpected value: ${JSON.stringify(value)}`);\n}\n","import { z } from 'zod';\n\n/**\n * IPC protocol between the daemon (parent) and the watcher worker subprocess.\n *\n * Wire format: line-delimited JSON over the worker's stdio. Each line is\n * exactly one Command (parent → worker) or one Reply (worker → parent), with\n * no embedded literal newlines — JSON encoding escapes newlines inside string\n * values (e.g. a path containing `\\n`) so the line protocol stays intact.\n *\n * Stdout is the IPC channel; structured worker logs go to stderr only.\n */\n\nexport const SubscribeOptionsSchema = z.object({\n ignore: z.array(z.string()).optional(),\n /**\n * Constrained to the subset the daemon actually uses. `@parcel/watcher`\n * supports more backends but they are platform-specific and we never set\n * them — accepting the wider union here would just invite bugs.\n */\n backend: z.enum(['default', 'brute-force', 'watchman']).optional(),\n});\nexport type WorkerSubscribeOptions = z.infer<typeof SubscribeOptionsSchema>;\n\nexport const CommandSchema = z.discriminatedUnion('cmd', [\n z.object({\n cmd: z.literal('subscribe'),\n id: z.string(),\n path: z.string(),\n opts: SubscribeOptionsSchema,\n }),\n z.object({ cmd: z.literal('unsubscribe'), id: z.string() }),\n z.object({ cmd: z.literal('shutdown') }),\n]);\nexport type WorkerCommand = z.infer<typeof CommandSchema>;\n\nconst ParcelEventSchema = z.object({\n type: z.enum(['create', 'update', 'delete']),\n path: z.string(),\n});\nexport type WorkerParcelEvent = z.infer<typeof ParcelEventSchema>;\n\nexport const ReplySchema = z.discriminatedUnion('type', [\n z.object({ type: z.literal('subscribed'), id: z.string() }),\n z.object({ type: z.literal('subscribe_failed'), id: z.string(), error: z.string() }),\n z.object({ type: z.literal('unsubscribed'), id: z.string() }),\n z.object({ type: z.literal('events'), id: z.string(), events: z.array(ParcelEventSchema) }),\n]);\nexport type WorkerReply = z.infer<typeof ReplySchema>;\n\nexport function encodeLine(msg: WorkerCommand | WorkerReply): string {\n return JSON.stringify(msg);\n}\n\n/**\n * Decode a single line. Returns `null` for any malformed or\n * schema-rejected input — the worker logs and continues, the supervisor\n * drops the bad line. Throwing here would let one corrupt line crash an\n * otherwise-healthy peer, which is the opposite of what process supervision\n * is for.\n */\nexport function decodeLine(line: string): WorkerCommand | WorkerReply | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return null;\n }\n if (parsed === null || typeof parsed !== 'object') return null;\n\n const asCmd = CommandSchema.safeParse(parsed);\n if (asCmd.success) return asCmd.data;\n const asReply = ReplySchema.safeParse(parsed);\n if (asReply.success) return asReply.data;\n return null;\n}\n"],"mappings":";;;;;;AAAO,SAAS,YAAY,OAAqB;AAC/C,QAAM,IAAI,MAAM,qBAAqB,KAAK,UAAU,KAAK,CAAC,EAAE;AAC9D;;;ACWO,IAAM,yBAAyB,iBAAE,OAAO;AAAA,EAC7C,QAAQ,iBAAE,MAAM,iBAAE,OAAO,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMrC,SAAS,iBAAE,KAAK,CAAC,WAAW,eAAe,UAAU,CAAC,EAAE,SAAS;AACnE,CAAC;AAGM,IAAM,gBAAgB,iBAAE,mBAAmB,OAAO;AAAA,EACvD,iBAAE,OAAO;AAAA,IACP,KAAK,iBAAE,QAAQ,WAAW;AAAA,IAC1B,IAAI,iBAAE,OAAO;AAAA,IACb,MAAM,iBAAE,OAAO;AAAA,IACf,MAAM;AAAA,EACR,CAAC;AAAA,EACD,iBAAE,OAAO,EAAE,KAAK,iBAAE,QAAQ,aAAa,GAAG,IAAI,iBAAE,OAAO,EAAE,CAAC;AAAA,EAC1D,iBAAE,OAAO,EAAE,KAAK,iBAAE,QAAQ,UAAU,EAAE,CAAC;AACzC,CAAC;AAGD,IAAM,oBAAoB,iBAAE,OAAO;AAAA,EACjC,MAAM,iBAAE,KAAK,CAAC,UAAU,UAAU,QAAQ,CAAC;AAAA,EAC3C,MAAM,iBAAE,OAAO;AACjB,CAAC;AAGM,IAAM,cAAc,iBAAE,mBAAmB,QAAQ;AAAA,EACtD,iBAAE,OAAO,EAAE,MAAM,iBAAE,QAAQ,YAAY,GAAG,IAAI,iBAAE,OAAO,EAAE,CAAC;AAAA,EAC1D,iBAAE,OAAO,EAAE,MAAM,iBAAE,QAAQ,kBAAkB,GAAG,IAAI,iBAAE,OAAO,GAAG,OAAO,iBAAE,OAAO,EAAE,CAAC;AAAA,EACnF,iBAAE,OAAO,EAAE,MAAM,iBAAE,QAAQ,cAAc,GAAG,IAAI,iBAAE,OAAO,EAAE,CAAC;AAAA,EAC5D,iBAAE,OAAO,EAAE,MAAM,iBAAE,QAAQ,QAAQ,GAAG,IAAI,iBAAE,OAAO,GAAG,QAAQ,iBAAE,MAAM,iBAAiB,EAAE,CAAC;AAC5F,CAAC;AAGM,SAAS,WAAW,KAA0C;AACnE,SAAO,KAAK,UAAU,GAAG;AAC3B;AASO,SAAS,WAAW,MAAkD;AAC3E,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,WAAW,QAAQ,OAAO,WAAW,SAAU,QAAO;AAE1D,QAAM,QAAQ,cAAc,UAAU,MAAM;AAC5C,MAAI,MAAM,QAAS,QAAO,MAAM;AAChC,QAAM,UAAU,YAAY,UAAU,MAAM;AAC5C,MAAI,QAAQ,QAAS,QAAO,QAAQ;AACpC,SAAO;AACT;","names":[]}
|
|
@@ -86,7 +86,7 @@ async function detectSkills(environments, lastKnown) {
|
|
|
86
86
|
try {
|
|
87
87
|
return await detectSkillsInner(environments);
|
|
88
88
|
} catch (err) {
|
|
89
|
-
const { logger } = await import("./logger-
|
|
89
|
+
const { logger } = await import("./logger-GQCSLSZH.js");
|
|
90
90
|
if (lastKnown && lastKnown.length > 0) {
|
|
91
91
|
logger.warn(
|
|
92
92
|
{ err, lastKnownCount: lastKnown.length },
|
|
@@ -123,4 +123,4 @@ async function detectSkillsInner(environments) {
|
|
|
123
123
|
export {
|
|
124
124
|
detectSkills
|
|
125
125
|
};
|
|
126
|
-
//# sourceMappingURL=chunk-
|
|
126
|
+
//# sourceMappingURL=chunk-VBPHGPBR.js.map
|