{"version":3,"sources":["../src/errors.ts","../src/internal/persistence/atomic-write.ts","../src/internal/runtime/context/context-discovery.ts","../src/internal/runtime/context/project-instructions.ts"],"names":["statfs","dirname","randomBytes","open","rename","unlink","isAbsolute","resolve","join","existsSync","realpathSync","readFile"],"mappings":"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","file":"project.cjs","sourcesContent":["import { defaultRetriableForCode } from \"./internal/default-retriable.js\";\nimport { redactSecrets } from \"./internal/security/redact.js\";\nimport type { RunOperation } from \"./types/run.js\";\n\n/**\n * Finite, machine-readable error codes for provider-originated errors\n * (ADR D66). Consumers can `switch (err.metadata?.code)` exhaustively\n * — adding a new variant is an explicit decision + test coverage.\n *\n * @public\n */\nexport type ErrorCode =\n  | \"rate_limit\"\n  | \"auth_failed\"\n  | \"invalid_request\"\n  | \"timeout\"\n  | \"server_error\"\n  | \"context_too_long\"\n  | \"content_filtered\"\n  | \"model_unavailable\"\n  | \"network\"\n  | \"quota_exceeded\"\n  | \"unknown\";\n\n/**\n * Codes used by {@link AgentRunError} (Production-Readiness #3, ADR D311).\n *\n * Superset of {@link ErrorCode} extended with codes that do NOT originate\n * from a provider HTTP response:\n *\n * - `quota_exceeded` — billing limit hit (provider 402 or signalled error)\n * - `tool_runtime_error` — custom tool handler threw inside dispatch\n * - `aborted` — caller's `AbortSignal` fired (Phase 4)\n * - `invalid_model` — model id rejected by provider (400 \"model not found\")\n * - `safety_blocked` — provider safety filter blocked req or resp\n * - `provider_unreachable` — DNS/TCP/timeout/5xx at transport boundary\n *\n * The `& {}` tail keeps the literal-union ergonomics (autocomplete) while\n * accepting any string for forward compatibility with constructor calls\n * that pass arbitrary code values (legacy callers).\n *\n * @public\n */\n/**\n * T1.1 — closed literal union for `AgentRunError.code`. The previous\n * `(string & {})` escape hatch let arbitrary strings slip into the type\n * surface and defeated exhaustive `switch (code)` discrimination. This is\n * the canonical closed form. `AgentRunErrorCode` is re-aliased below for\n * source-level back-compat.\n *\n * Adding a new code: append the literal here AND audit every `switch (err.code)`\n * in callers. Type-checker enforces the audit via the `default: assertNever(code)`\n * convention.\n *\n * @public\n */\nexport type KnownAgentRunErrorCode =\n  | ErrorCode\n  | \"quota_exceeded\"\n  | \"tool_runtime_error\"\n  | \"aborted\"\n  | \"invalid_model\"\n  | \"safety_blocked\"\n  | \"provider_unreachable\";\n\n/**\n * Back-compat alias of {@link KnownAgentRunErrorCode}. Pre-T1.1 callers that\n * imported `AgentRunErrorCode` keep working; new code SHOULD prefer\n * `KnownAgentRunErrorCode` to make the closed-union intent explicit.\n *\n * @public\n */\nexport type AgentRunErrorCode = KnownAgentRunErrorCode;\n\n/** Snapshot of every known code at runtime — used by the boundary coercer. */\nconst KNOWN_AGENT_RUN_ERROR_CODES = new Set<string>([\n  \"rate_limit\",\n  \"auth_failed\",\n  \"invalid_request\",\n  \"timeout\",\n  \"server_error\",\n  \"context_too_long\",\n  \"content_filtered\",\n  \"model_unavailable\",\n  \"network\",\n  \"unknown\",\n  \"quota_exceeded\",\n  \"tool_runtime_error\",\n  \"aborted\",\n  \"invalid_model\",\n  \"safety_blocked\",\n  \"provider_unreachable\",\n]);\n\n/**\n * T1.1 boundary helper — coerce an arbitrary string (typically arriving from\n * a downstream `RunErrorDetail.code` or a deserialized cloud response) into a\n * `KnownAgentRunErrorCode`. Unknown strings collapse to `\"unknown\"` so the\n * closed type contract holds without forcing every caller to switch.\n *\n * @internal\n */\nexport function coerceToKnownAgentRunErrorCode(code: string | undefined): KnownAgentRunErrorCode {\n  if (code !== undefined && KNOWN_AGENT_RUN_ERROR_CODES.has(code)) {\n    return code as KnownAgentRunErrorCode;\n  }\n  return \"unknown\";\n}\n\n/**\n * Structured context for errors that originated from a provider HTTP\n * call (ADR D65). Lets callers retry with the right backoff (`retryAfter`),\n * surface actionable diagnostics (`provider`, `endpoint`), and inspect the\n * raw response body when needed (`raw`, capped at ~2KB by the mapper).\n *\n * @public\n */\nexport interface ErrorMetadata {\n  /** Provider canonical name (e.g., `\"anthropic\"`, `\"openai\"`, `\"openrouter\"`, `\"gemini\"`). */\n  provider: string;\n  /** HTTP endpoint that failed (e.g., `\"/v1/messages\"`, `\"/v1/chat/completions\"`). */\n  endpoint: string;\n  /** Machine-readable error code (finite enum). */\n  code: ErrorCode;\n  /** HTTP status code if applicable. */\n  statusCode?: number;\n  /** Seconds to wait before retry, per provider's `retry-after` header (numeric form only). */\n  retryAfter?: number;\n  /** Raw response body for debugging (truncated to ~2KB by the mapper). */\n  raw?: unknown;\n}\n\n/**\n * Base class for all errors thrown by `@theokit/sdk`.\n *\n * Use `isRetryable` to drive retry/backoff logic. `code` and `protoErrorCode`\n * are populated for server-originated errors when available. `metadata`\n * (ADR D65) carries structured `{ provider, endpoint, code, ... }` when\n * the error originated from a provider HTTP call.\n *\n * @public\n */\nexport class TheokitAgentError extends Error {\n  override readonly name: string = \"TheokitAgentError\";\n  readonly isRetryable: boolean;\n  readonly code?: string;\n  readonly protoErrorCode?: string;\n  readonly metadata?: ErrorMetadata;\n\n  constructor(\n    message: string,\n    options: {\n      isRetryable?: boolean;\n      code?: string;\n      protoErrorCode?: string;\n      cause?: unknown;\n      metadata?: ErrorMetadata;\n    } = {},\n  ) {\n    super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n    this.isRetryable = options.isRetryable ?? false;\n    if (options.code !== undefined) this.code = options.code;\n    if (options.protoErrorCode !== undefined) this.protoErrorCode = options.protoErrorCode;\n    if (options.metadata !== undefined) this.metadata = options.metadata;\n  }\n}\n\n/**\n * Invalid API key, not logged in, insufficient permissions.\n *\n * @public\n */\nexport class AuthenticationError extends TheokitAgentError {\n  override readonly name: string = \"AuthenticationError\";\n\n  constructor(\n    message: string,\n    options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n  ) {\n    super(message, { ...options, isRetryable: false });\n  }\n}\n\n/**\n * Too many requests or usage limits exceeded.\n *\n * @public\n */\nexport class RateLimitError extends TheokitAgentError {\n  override readonly name: string = \"RateLimitError\";\n\n  constructor(\n    message: string,\n    options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n  ) {\n    super(message, { ...options, isRetryable: true });\n  }\n}\n\n/**\n * Invalid model, bad request parameters, malformed options.\n *\n * @public\n */\nexport class ConfigurationError extends TheokitAgentError {\n  override readonly name: string = \"ConfigurationError\";\n\n  constructor(\n    message: string,\n    options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n  ) {\n    super(message, { ...options, isRetryable: false });\n  }\n}\n\n/**\n * Thrown when creating a cloud agent for a repo whose SCM provider is not\n * connected. Use `helpUrl` to point the user at the right reconnect flow.\n *\n * @public\n */\nexport class IntegrationNotConnectedError extends ConfigurationError {\n  override readonly name: string = \"IntegrationNotConnectedError\";\n  readonly provider: string;\n  readonly helpUrl: string;\n\n  constructor(\n    message: string,\n    options: {\n      provider: string;\n      helpUrl: string;\n      code?: string;\n      cause?: unknown;\n      metadata?: ErrorMetadata;\n    },\n  ) {\n    super(message, options);\n    this.provider = options.provider;\n    this.helpUrl = options.helpUrl;\n  }\n}\n\n/**\n * Service unavailable, timeout, transport-level failure.\n *\n * @public\n */\nexport class NetworkError extends TheokitAgentError {\n  override readonly name: string = \"NetworkError\";\n\n  constructor(\n    message: string,\n    options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n  ) {\n    super(message, { ...options, isRetryable: true });\n  }\n}\n\n/**\n * Catch-all for unclassified server or runtime errors.\n *\n * @public\n */\nexport class UnknownAgentError extends TheokitAgentError {\n  override readonly name: string = \"UnknownAgentError\";\n\n  constructor(\n    message: string,\n    options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n  ) {\n    super(message, { ...options, isRetryable: false });\n  }\n}\n\n/**\n * Thrown by `Agent.prompt` (and helpers that go through `run.wait()`) when\n * the option `{ throwOnError: true }` is set and the run terminates with\n * `status: 'error'`. Carries the structured `RunResult.error` fields so\n * callers can `catch` once and branch on `code` / `provider` instead of\n * unwrapping the run.\n *\n * Extends {@link TheokitAgentError} per ADR D65 — no new hierarchy.\n *\n * @example\n *   try {\n *     await Agent.prompt(msg, { apiKey, model, throwOnError: true });\n *   } catch (err) {\n *     if (err instanceof AgentRunError && err.code === 'auth_failed') {\n *       // bad key\n *     }\n *   }\n *\n * @public\n */\nexport class AgentRunError extends TheokitAgentError {\n  override readonly name: string = \"AgentRunError\";\n  readonly provider?: string;\n  readonly raw?: string;\n  /** Provider's request id (`x-request-id` / `request-id` header). Useful for support tickets. */\n  readonly requestId?: string;\n  /** SDK conversation id this error was raised inside. */\n  readonly conversationId?: string;\n\n  constructor(\n    message: string,\n    options: {\n      code: AgentRunErrorCode;\n      provider?: string;\n      raw?: string;\n      requestId?: string;\n      conversationId?: string;\n      retriable?: boolean;\n      cause?: unknown;\n      metadata?: ErrorMetadata;\n    },\n  ) {\n    super(message, {\n      code: options.code,\n      cause: options.cause,\n      metadata: options.metadata,\n      // D311: most AgentRunErrors are not retriable (auth, validation, abort).\n      // Provider mappers (D314) override per-status — explicit `retriable` wins\n      // over the implicit default when supplied.\n      isRetryable: options.retriable ?? defaultRetriableForCode(options.code),\n    });\n    if (options.provider !== undefined) this.provider = options.provider;\n    if (options.raw !== undefined) this.raw = options.raw;\n    if (options.requestId !== undefined) this.requestId = options.requestId;\n    if (options.conversationId !== undefined) this.conversationId = options.conversationId;\n  }\n\n  /**\n   * Production-Readiness #3 (ADR D311): alias for `isRetryable` exposed as\n   * `retriable` to match the handoff contract. Future v2 will deprecate\n   * `isRetryable` in favor of this.\n   */\n  get retriable(): boolean {\n    return this.isRetryable;\n  }\n\n  /**\n   * D312: provider's `Retry-After` header in **milliseconds**. Mappers store\n   * the header value (seconds) in `metadata.retryAfter`; this getter\n   * multiplies by 1000 so the result composes with `Date.now()`/`setTimeout`.\n   *\n   * Returns `undefined` when no hint was provided. `0` is a legitimate value\n   * — use `=== undefined` check rather than truthy check.\n   */\n  get retryAfterMs(): number | undefined {\n    if (this.metadata?.retryAfter === undefined) return undefined;\n    return this.metadata.retryAfter * 1000;\n  }\n\n  /**\n   * D313 + T1.5: alias for `metadata.raw`. Provider response body for\n   * debugging. T1.5 wraps the value in `redactSecrets` at the getter\n   * boundary so secret-shaped substrings (`sk-...`, Bearer JWTs, etc.) are\n   * stripped before reaching the caller. Available but NEVER serialized\n   * into `.message` (anti-leak invariant).\n   */\n  get providerError(): unknown {\n    const raw = this.metadata?.raw;\n    if (raw === undefined) return undefined;\n    if (typeof raw === \"string\") return redactSecrets(raw);\n    // Non-string raw (object/buffer) — stringify then redact.\n    try {\n      return redactSecrets(JSON.stringify(raw));\n    } catch {\n      return redactSecrets(String(raw));\n    }\n  }\n\n  /**\n   * T1.5 — sanitized JSON form. `metadata.raw` is OMITTED by default; opt\n   * in via `THEOKIT_DEBUG_RAW_ERRORS=1` to surface the (redacted) raw\n   * payload for diagnostics. Every other field stays accessible.\n   *\n   * The single env-var gate is read each call so operators can toggle at\n   * runtime without restarting the process.\n   */\n  toJSON(): Record<string, unknown> {\n    const json: Record<string, unknown> = {\n      name: this.name,\n      message: this.message,\n      isRetryable: this.isRetryable,\n    };\n    addOptionalFields(json, this);\n    const safeMeta = sanitizeMetadata(this.metadata);\n    if (safeMeta !== undefined) json.metadata = safeMeta;\n    return json;\n  }\n}\n\nfunction addOptionalFields(json: Record<string, unknown>, err: AgentRunError): void {\n  if (err.code !== undefined) json.code = err.code;\n  if (err.provider !== undefined) json.provider = err.provider;\n  if (err.requestId !== undefined) json.requestId = err.requestId;\n  if (err.conversationId !== undefined) json.conversationId = err.conversationId;\n  if (err.raw !== undefined) json.raw = redactSecrets(err.raw);\n}\n\nfunction sanitizeMetadata(meta: ErrorMetadata | undefined): ErrorMetadata | undefined {\n  if (meta === undefined) return undefined;\n  const { raw, ...rest } = meta;\n  const debugRaw = process.env.THEOKIT_DEBUG_RAW_ERRORS === \"1\";\n  if (debugRaw && raw !== undefined) {\n    const redactedRaw =\n      typeof raw === \"string\" ? redactSecrets(raw) : redactSecrets(safeStringify(raw));\n    return { ...rest, raw: redactedRaw } as ErrorMetadata;\n  }\n  return rest as ErrorMetadata;\n}\n\nfunction safeStringify(value: unknown): string {\n  try {\n    return JSON.stringify(value);\n  } catch {\n    return String(value);\n  }\n}\n\n/**\n * Is this error transient (worth retrying)?\n *\n * Returns the SDK's own retryability verdict: every {@link TheokitAgentError}\n * subclass computes `isRetryable` at construction (rate-limit / network /\n * credential-pool-exhausted are retryable; auth / configuration / unsupported\n * are not), so this predicate is a single source of truth rather than a\n * re-derivation. Non-SDK errors return `false` conservatively — wrap a foreign\n * error in the appropriate SDK error first if you want it considered transient.\n * It never inspects `err.message`.\n *\n * @example\n *   try {\n *     await agent.send(message, { throwOnError: true });\n *   } catch (err) {\n *     if (isTransientError(err)) return retryWithBackoff();\n *     throw err;\n *   }\n *\n * @public\n */\nexport function isTransientError(err: unknown): boolean {\n  return err instanceof TheokitAgentError && err.isRetryable === true;\n}\n\n/**\n * Thrown when a {@link Run} or agent operation is not available on the current\n * runtime. Check first with `run.supports(operation)`.\n *\n * Extends {@link TheokitAgentError} (so error-catching code that branches on\n * `instanceof TheokitAgentError` continues to work) but is never retryable —\n * an unsupported operation will not become supported on retry.\n *\n * @public\n */\nexport class UnsupportedRunOperationError extends TheokitAgentError {\n  override readonly name: string = \"UnsupportedRunOperationError\";\n  readonly operation: RunOperation;\n\n  constructor(\n    message: string,\n    operation: RunOperation,\n    options: { code?: string; cause?: unknown } = {},\n  ) {\n    super(message, {\n      ...options,\n      isRetryable: false,\n      code: options.code ?? \"unsupported_run_operation\",\n    });\n    this.operation = operation;\n  }\n}\n\n/**\n * Thrown when every credential in a per-provider pool is in cooldown\n * and no healthy key is available (ADR D133). The caller's\n * {@link import(\"./internal/llm/fallback-client.js\").FallbackLlmClient}\n * catches this and tries the next provider in the fallback chain.\n *\n * `metadata.nextRetryAt` (epoch ms) tells callers when the soonest\n * pool entry resumes — useful for manual retry scheduling.\n *\n * @public\n */\nexport class CredentialPoolExhaustedError extends TheokitAgentError {\n  override readonly name: string = \"CredentialPoolExhaustedError\";\n  readonly provider: string;\n  readonly nextRetryAt: number | undefined;\n\n  constructor(\n    message: string,\n    options: {\n      provider: string;\n      nextRetryAt?: number;\n      code?: string;\n      cause?: unknown;\n      metadata?: ErrorMetadata;\n    },\n  ) {\n    super(message, {\n      ...options,\n      isRetryable: true,\n      code: options.code ?? \"credential_pool_exhausted\",\n    });\n    this.provider = options.provider;\n    this.nextRetryAt = options.nextRetryAt;\n  }\n}\n\n/**\n * Finite error codes specific to memory adapter operations (ADR D141).\n *\n * @public\n */\nexport type MemoryAdapterErrorCode =\n  | \"auth_failed\"\n  | \"rate_limited\"\n  | \"not_found\"\n  | \"network\"\n  | \"invalid_input\"\n  | \"unknown\";\n\n/**\n * Error raised by `@theokit-memory-*` adapters. Carries `adapterId`\n * so callers can branch on which provider failed (ADR D141).\n *\n * @public\n */\nexport class MemoryAdapterError extends TheokitAgentError {\n  override readonly name: string = \"MemoryAdapterError\";\n  readonly adapterId: string;\n\n  constructor(\n    message: string,\n    options: {\n      adapterId: string;\n      code: MemoryAdapterErrorCode;\n      cause?: unknown;\n      metadata?: ErrorMetadata;\n    },\n  ) {\n    super(message, {\n      isRetryable: options.code === \"rate_limited\" || options.code === \"network\",\n      code: options.code,\n      ...(options.cause !== undefined ? { cause: options.cause } : {}),\n      ...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n    });\n    this.adapterId = options.adapterId;\n  }\n}\n\n/**\n * Thrown when a user-supplied task ID violates the grammar\n * `^[a-z0-9][a-z0-9_-]*$` (D368) OR starts with a reserved adapter\n * prefix (`wf-` / `b-` / `cron-`, EC-5).\n *\n * @public\n */\nexport class InvalidTaskIdError extends TheokitAgentError {\n  override readonly name: string = \"InvalidTaskIdError\";\n  readonly taskId: string;\n\n  constructor(message: string, taskId: string, options: { cause?: unknown } = {}) {\n    super(message, {\n      ...options,\n      isRetryable: false,\n      code: \"invalid_task_id\",\n    });\n    this.taskId = taskId;\n  }\n}\n\n/**\n * Thrown when `Task.subscribe(id)` is called for a task that has been\n * evicted, never submitted, or evicted after retention (D373).\n *\n * @public\n */\nexport class TaskNotFoundError extends TheokitAgentError {\n  override readonly name: string = \"TaskNotFoundError\";\n  readonly taskId: string;\n\n  constructor(taskId: string, options: { cause?: unknown } = {}) {\n    super(`Task not found: ${taskId}`, {\n      ...options,\n      isRetryable: false,\n      code: \"task_not_found\",\n    });\n    this.taskId = taskId;\n  }\n}\n\n/**\n * Thrown when `CloudAgent` is asked to wrap a task (D370). Cloud\n * task observability is deferred until Theo PaaS GA.\n *\n * @public\n */\nexport class UnsupportedTaskOperationError extends TheokitAgentError {\n  override readonly name: string = \"UnsupportedTaskOperationError\";\n  readonly operation: string;\n\n  constructor(operation: string, options: { cause?: unknown } = {}) {\n    super(\n      `Task operation \"${operation}\" is not supported on CloudAgent (pre-release; see ADR D370)`,\n      {\n        ...options,\n        isRetryable: false,\n        code: \"task_op_unsupported\",\n      },\n    );\n    this.operation = operation;\n  }\n}\n\n/**\n * Thrown by `Budget` enforcement (ADR D386) when a `mode: \"block\"`\n * budget would be exceeded by the upcoming LLM call. Caller pega\n * tipado para retry-after-window-reset or surface to the user.\n *\n * @public\n */\nexport class BudgetExceededError extends TheokitAgentError {\n  override readonly name: string = \"BudgetExceededError\";\n  readonly budgetName: string;\n  readonly window: import(\"./types/budget.js\").BudgetWindow;\n  readonly spentUsd: number;\n  readonly limitUsd: number;\n  readonly mode: import(\"./types/budget.js\").BudgetMode;\n\n  constructor(args: {\n    budgetName: string;\n    window: import(\"./types/budget.js\").BudgetWindow;\n    spentUsd: number;\n    limitUsd: number;\n    mode: import(\"./types/budget.js\").BudgetMode;\n    cause?: unknown;\n  }) {\n    super(\n      `Budget \"${args.budgetName}\" exceeded for window ${args.window}: spent $${args.spentUsd.toFixed(4)} > limit $${args.limitUsd.toFixed(4)}`,\n      {\n        ...(args.cause !== undefined ? { cause: args.cause } : {}),\n        isRetryable: false,\n        code: \"budget_exceeded\",\n      },\n    );\n    this.budgetName = args.budgetName;\n    this.window = args.window;\n    this.spentUsd = args.spentUsd;\n    this.limitUsd = args.limitUsd;\n    this.mode = args.mode;\n  }\n}\n\n/**\n * Thrown when `CloudAgent.send({ budget })` is invoked (D388). Cloud\n * budget surface waits for Theo PaaS GA.\n *\n * @public\n */\n/**\n * T1.6 — Thrown when a consumer calls `agent.send()` or any method\n * on an agent that has already been `dispose()`d. Pre-T1.6 this was\n * a generic `new Error(\"Agent has been disposed\")` — consumers\n * couldn't catch it without string-matching the message.\n *\n * @public\n */\nexport class AgentDisposedError extends TheokitAgentError {\n  override readonly name: string = \"AgentDisposedError\";\n  readonly agentId: string;\n\n  constructor(agentId: string) {\n    super(`Agent \"${agentId}\" has been disposed. Create a new agent or use Agent.resume().`, {\n      isRetryable: false,\n      code: \"agent_disposed\",\n    });\n    this.agentId = agentId;\n  }\n}\n\nexport class UnsupportedBudgetOperationError extends TheokitAgentError {\n  override readonly name: string = \"UnsupportedBudgetOperationError\";\n  readonly operation: string;\n\n  constructor(operation: string, options: { cause?: unknown } = {}) {\n    super(\n      `Budget operation \"${operation}\" is not supported on CloudAgent (pre-release; see ADR D388)`,\n      {\n        ...options,\n        isRetryable: false,\n        code: \"budget_op_unsupported\",\n      },\n    );\n    this.operation = operation;\n  }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { mkdir, open, rename, statfs, unlink } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\n// T5.8 — Linux filesystem magic numbers (from `<linux/magic.h>`).\n// Used by `detectNetworkFsName` to identify the parent directory's\n// filesystem type from a `statfs()` return value. The four entries\n// below cover the network/FUSE cases where `rename()` is best-effort\n// rather than strictly atomic; everything else is treated as local.\nconst NETWORK_FS_MAGIC: ReadonlyMap<number, string> = new Map([\n  [0x6969, \"nfs\"],\n  [0x517b, \"smb\"],\n  [0xff534d42, \"cifs\"],\n  [0x65735546, \"fuse\"],\n]);\n\n/**\n * T5.8 — Map a `statfs().type` magic number to a network-FS label, or\n * `null` for local filesystems. Pure function — exported via the\n * `__TESTING__` seam so unit tests can drive the parse logic without\n * needing a network mount.\n *\n * @internal\n */\nfunction detectNetworkFsName(typeMagic: number): string | null {\n  return NETWORK_FS_MAGIC.get(typeMagic) ?? null;\n}\n\nconst warnedNfsDirs = new Set<string>();\n\n/**\n * T5.8 — Best-effort one-shot stderr warning when `dirPath` lives on a\n * network/FUSE filesystem. Silent no-op on local filesystems, on\n * statfs failure (Windows / Node < 18.15 / EACCES), or after the\n * first warning per (dir + label) pair. Mirrors the `sqlite-wal.ts`\n * warn-once-per-label pattern (D63).\n *\n * @internal\n */\nasync function warnOnNetworkFsOnce(dirPath: string, label: string): Promise<void> {\n  const key = `${dirPath}\\0${label}`;\n  if (warnedNfsDirs.has(key)) return;\n  warnedNfsDirs.add(key);\n  try {\n    const info = await statfs(dirPath);\n    const fsName = detectNetworkFsName(info.type);\n    if (fsName === null) return;\n    process.stderr.write(\n      `[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} — ` +\n        \"rename() atomicity guarantees may be weaker than expected.\\n\",\n    );\n  } catch {\n    // statfs unavailable (Windows / Node < 18.15) or unreadable —\n    // silent fallback. The warning is purely informational.\n  }\n}\n\n/**\n * T5.8 — Test seam exposing the pure detection function so unit tests\n * can assert magic-number coverage without spinning up a network FS.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__detectNetworkFsName(typeMagic: number): string | null {\n  return detectNetworkFsName(typeMagic);\n}\n\n/**\n * T5.8 — Test seam: clear the per-directory warn-once registry between\n * tests so warning-emission tests stay deterministic.\n *\n * @internal\n */\nexport function __TESTING__resetNfsWarnings(): void {\n  warnedNfsDirs.clear();\n}\n\n/**\n * Atomic file replacement: write content to a per-call unique tmp path,\n * fsync, then rename over the target. Crash mid-write leaves either the old\n * file intact or the new file complete — never a half-written file.\n *\n * The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent\n * burst writes within one process) never collide on the same tmp path — a\n * race that would manifest as `ENOENT` on `rename` after the rival process\n * already moved its tmp into place.\n *\n * Mirrors OpenClaw's `replaceFileAtomic` from\n * `referencia/openclaw/packages/memory-host-sdk/src/host/fs-utils.ts` with\n * the multi-writer robustness fix.\n *\n * @internal\n */\nexport async function replaceFileAtomic(filePath: string, content: string): Promise<void> {\n  // T5.8 — warn once per parent directory if it lives on a network /\n  // FUSE filesystem where `rename()` atomicity is best-effort. The\n  // write proceeds unchanged; the warning is purely informational so\n  // operators can spot the case in stderr / log aggregators.\n  await warnOnNetworkFsOnce(dirname(filePath), \"atomic-write\");\n  // T5.7 — crypto-random tmp suffix (CSPRNG, 64 bits of entropy)\n  // replaces the predictable `Math.random().toString(36)` source. An\n  // attacker observing the process can no longer predict the next\n  // tmp path and pre-stage a hostile file to be renamed into place.\n  const suffix = randomBytes(8).toString(\"hex\");\n  const tmp = `${filePath}.${process.pid}.${suffix}.tmp`;\n  // T5.7 — mode 0o600 on the tmp file (owner read+write only). The\n  // tmp file holds the FULL in-flight content (credential snapshots,\n  // OAuth tokens) before the rename. World-readable default would\n  // expose secrets during the ms-window between open and rename\n  // (TOCTOU). On modern Linux the post-rename target inherits the\n  // tmp's permission bits, so the final file is also 0o600.\n  const handle = await open(tmp, \"w\", 0o600);\n  try {\n    await handle.writeFile(content, \"utf8\");\n    await handle.sync();\n  } finally {\n    await handle.close();\n  }\n  try {\n    await rename(tmp, filePath);\n  } catch (cause) {\n    // Cleanup tmp on rename failure so we don't leak stale .tmp files.\n    await unlink(tmp).catch(() => undefined);\n    throw cause;\n  }\n}\n\n/**\n * Options for `atomicWriteJson`.\n *\n * @internal\n */\nexport interface AtomicWriteJsonOptions {\n  /** Indent passed to `JSON.stringify`. Default: 2. */\n  indent?: number;\n  /** Whether to append a trailing newline (POSIX convention). Default: true. */\n  trailingNewline?: boolean;\n}\n\n/**\n * Typed JSON atomic write helper.\n *\n * Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The\n * parent directory is auto-created (recursive `mkdir`) to make this helper\n * safe for callers who haven't ensured the directory exists (EC-4 in the\n * persistence-state-hardening plan).\n *\n * Throws `TypeError` on circular refs or `undefined` data (propagates from\n * `JSON.stringify`).\n *\n * @internal\n */\nexport async function atomicWriteJson<T>(\n  filePath: string,\n  data: T,\n  options?: AtomicWriteJsonOptions,\n): Promise<void> {\n  const indent = options?.indent ?? 2;\n  const trailingNewline = options?.trailingNewline ?? true;\n  const json = JSON.stringify(data, null, indent);\n  if (json === undefined) {\n    throw new TypeError(\"atomicWriteJson: cannot serialize undefined\");\n  }\n  const content = trailingNewline ? `${json}\\n` : json;\n  await mkdir(dirname(filePath), { recursive: true });\n  await replaceFileAtomic(filePath, content);\n}\n\n/**\n * Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +\n * auto-mkdir of the parent directory. Used by `theokit-migrate-config`\n * (T4.1, EC-2 MUST FIX) so a crash mid-migration leaves previous MD files\n * intact rather than corrupting them.\n *\n * @internal\n */\nexport async function atomicWriteText(filePath: string, content: string): Promise<void> {\n  await mkdir(dirname(filePath), { recursive: true });\n  await replaceFileAtomic(filePath, content);\n}\n","/**\n * Context file discovery (T1.1, ADRs D150 / D151).\n *\n * Discovers context files via three scopes:\n *  - `cwd-only` — single dir, single path lookup\n *  - `git-root-walk` — walk cwd → git-root, collect every directory's match\n *    (nearest-first ordering)\n *  - `globbed` — glob pattern relative to cwd (e.g. `.cursor/rules/*.mdc`)\n *\n * Pure `existsSync` checks — **no `.gitignore` parsing** (EC-A, KISS) and\n * **no invented `.theokitignore`** (EC-B). Paths normalized via\n * `realpath` to dedup symlink chains pointing to the same physical file\n * (EC-F). Git worktrees work transparently because `.git` exists as a\n * file in that case (EC-N).\n *\n * @internal\n */\n\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\n\n/** Single filename (\"AGENTS.md\") or relative glob (\".cursor/rules/*.mdc\"). */\nexport type DiscoveryScope = \"cwd-only\" | \"git-root-walk\" | \"globbed\";\n\n/** Parser to apply once file is read. */\nexport type DiscoveryParser = \"plain-markdown\" | \"mdc\" | \"frontmatter-zod\";\n\n/**\n * Specification for one discoverable context source. The default registry\n * `DEFAULT_DISCOVERY_SPECS` covers the 2026 industry-standard set.\n *\n * @internal\n */\nexport interface DiscoverySpec {\n  /** Stable identifier — used as `<source name=\"\">` and telemetry key. */\n  readonly id: string;\n  /** Priority for merge (lower = earlier in prompt). */\n  readonly priority: number;\n  /** Filename (cwd-only/git-root-walk) or glob (globbed). */\n  readonly pattern: string;\n  readonly scope: DiscoveryScope;\n  readonly parser: DiscoveryParser;\n  /** Whether to follow `@path` import directives (CLAUDE.md / GEMINI.md). */\n  readonly followImports: boolean;\n}\n\n/**\n * 2026 industry-standard context file registry. Sorted by priority\n * ascending — earlier in prompt = more general, last-writer-wins on\n * conflict (D152 concat-by-priority).\n *\n * @internal\n */\nexport const DEFAULT_DISCOVERY_SPECS: ReadonlyArray<DiscoverySpec> = [\n  {\n    id: \"AGENTS.md\",\n    pattern: \"AGENTS.md\",\n    scope: \"git-root-walk\",\n    parser: \"plain-markdown\",\n    followImports: false,\n    priority: 10,\n  },\n  {\n    id: \"GEMINI.md\",\n    pattern: \"GEMINI.md\",\n    scope: \"git-root-walk\",\n    parser: \"plain-markdown\",\n    followImports: true,\n    priority: 20,\n  },\n  {\n    id: \"CLAUDE.md\",\n    pattern: \"CLAUDE.md\",\n    scope: \"git-root-walk\",\n    parser: \"plain-markdown\",\n    followImports: true,\n    priority: 30,\n  },\n  {\n    id: \"cursor-rules\",\n    pattern: \".cursor/rules/*.mdc\",\n    scope: \"globbed\",\n    parser: \"mdc\",\n    followImports: false,\n    priority: 40,\n  },\n  {\n    id: \"theokit-context\",\n    pattern: \".theokit/context/*.md\",\n    scope: \"globbed\",\n    parser: \"frontmatter-zod\",\n    followImports: false,\n    priority: 50,\n  },\n  {\n    id: \"THEO.md\",\n    pattern: \".theokit/THEO.md\",\n    scope: \"cwd-only\",\n    parser: \"plain-markdown\",\n    followImports: false,\n    priority: 60,\n  },\n];\n\nconst SAFE_FILENAME = /^[a-zA-Z0-9_.\\-/*]+$/;\nconst TRAVERSAL_RE = /(^|\\/)\\.\\.(\\/|$)/;\n\n/**\n * Reject patterns that contain path traversal (`..`) or non-allowed\n * characters (D81 parity, EC-4).\n *\n * @internal\n */\nexport function isSafePattern(pattern: string): boolean {\n  if (typeof pattern !== \"string\" || pattern.length === 0) return false;\n  if (TRAVERSAL_RE.test(pattern)) return false;\n  if (isAbsolute(pattern)) return false;\n  return SAFE_FILENAME.test(pattern);\n}\n\n/**\n * Walk upward from `cwd` looking for the closest directory containing\n * a `.git` entry (file OR directory — worktrees use a `.git` FILE,\n * EC-N). Returns the absolute path of that directory, or `undefined`\n * when no git root exists at or above `cwd`.\n *\n * @internal\n */\nexport function findGitRoot(cwd: string): string | undefined {\n  if (typeof cwd !== \"string\" || cwd.length === 0) return undefined;\n  let current = resolve(cwd);\n  // Guard against infinite loops on weird filesystems.\n  for (let i = 0; i < 64; i += 1) {\n    if (existsSync(join(current, \".git\"))) return current;\n    const parent = dirname(current);\n    if (parent === current) return undefined;\n    current = parent;\n  }\n  return undefined;\n}\n\n/**\n * Walk `cwd` upward to `stopDir` (inclusive) collecting every existing\n * occurrence of `filename`. Returns absolute, realpath-deduped paths in\n * nearest-first order (innermost dir first).\n *\n * No `.gitignore` parsing (EC-A). Realpath collapses symlink chains\n * pointing to the same physical file (EC-F). Filesystem races (file\n * deleted mid-walk) are skipped silently (EC-5).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: walk-up loop combines validation + realpath dedup + FS-race handling + stopDir guard in a single bounded loop; splitting fragments the dedup invariant.\nexport function walkUpForFile(\n  cwd: string,\n  filename: string,\n  stopDir: string | undefined,\n): string[] {\n  if (!isSafePattern(filename)) {\n    return [];\n  }\n  const start = resolve(cwd);\n  const stop = stopDir !== undefined ? resolve(stopDir) : undefined;\n  const found: string[] = [];\n  const seenReal = new Set<string>();\n  let current = start;\n  // 64-level depth cap.\n  for (let i = 0; i < 64; i += 1) {\n    const candidate = join(current, filename);\n    if (existsSync(candidate)) {\n      let real: string;\n      try {\n        real = realpathSync(candidate);\n      } catch {\n        // FS race (deleted mid-walk) — skip.\n        real = candidate;\n      }\n      if (!seenReal.has(real)) {\n        seenReal.add(real);\n        found.push(real);\n      }\n    }\n    if (stop !== undefined && current === stop) break;\n    const parent = dirname(current);\n    if (parent === current) break;\n    current = parent;\n  }\n  return found;\n}\n\n/**\n * Glob-style discovery scoped to a single directory under cwd (e.g.\n * `.cursor/rules/*.mdc`). Flat — does NOT recurse into subdirectories\n * (EC-R: nested directories deferred to v2). Returns absolute,\n * lex-sorted paths.\n *\n * @internal\n */\nexport async function walkUpForGlob(cwd: string, pattern: string): Promise<string[]> {\n  if (!isSafePattern(pattern)) return [];\n  const lastSlash = pattern.lastIndexOf(\"/\");\n  if (lastSlash < 0) {\n    // Single filename — treat as cwd-only single match.\n    const candidate = join(cwd, pattern);\n    return existsSync(candidate) ? [resolve(candidate)] : [];\n  }\n  const dirPart = pattern.slice(0, lastSlash);\n  const filePart = pattern.slice(lastSlash + 1);\n  const dir = join(cwd, dirPart);\n  if (!existsSync(dir)) return [];\n  // Build a regex from the file part — supports only `*` wildcard.\n  const fileRe = filePartToRegex(filePart);\n  let entries: string[];\n  try {\n    entries = await readdir(dir);\n  } catch {\n    return [];\n  }\n  const matched = entries.filter((e) => fileRe.test(e)).sort();\n  return matched.map((e) => resolve(join(dir, e)));\n}\n\nfunction filePartToRegex(filePart: string): RegExp {\n  const escaped = filePart.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n  return new RegExp(`^${escaped}$`);\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport { replaceFileAtomic } from \"../../persistence/atomic-write.js\";\nimport { isSafePattern, walkUpForFile } from \"./context-discovery.js\";\n\n/** How discovered instruction files are reduced to a single `content` string. */\nexport type ProjectInstructionScope = \"nearest\" | \"merged\";\n\n/** One discovered project-instruction file. */\nexport interface ProjectInstructionFile {\n  /** Absolute path to the file. */\n  path: string;\n  /** Full file content (never truncated — the caller bounds it). */\n  content: string;\n}\n\n/** Result of {@link readProjectInstructions}. */\nexport interface ProjectInstructions {\n  /** Found files, nearest-first (innermost directory first). Empty if none. */\n  files: ProjectInstructionFile[];\n  /**\n   * The `scope`-selected reduction: `nearest` → the innermost file's content;\n   * `merged` → all files joined root-first (nearest content last). `undefined`\n   * when no file was found.\n   */\n  content: string | undefined;\n}\n\n/** Options for {@link readProjectInstructions}. */\nexport interface ReadProjectInstructionsOptions {\n  /** Instruction filename to discover. Default `\"THEO.md\"`. */\n  filename?: string;\n  /** How to reduce the found files to `content`. Default `\"nearest\"`. */\n  scope?: ProjectInstructionScope;\n  /** Stop the upward walk at this directory (inclusive). Default: filesystem root. */\n  stopDir?: string;\n}\n\n/** Options for {@link writeProjectInstructions}. */\nexport interface WriteProjectInstructionsOptions {\n  /** Instruction filename to write. Default `\"THEO.md\"`. */\n  filename?: string;\n}\n\nconst DEFAULT_FILENAME = \"THEO.md\";\n\n/**\n * Read hierarchical project instructions by walking up from `cwd`.\n *\n * Discovers every `<dir>/<filename>` from `cwd` up to the filesystem root (or\n * `stopDir`), reads each, and returns them nearest-first in `files` plus a\n * `content` reduction chosen by `scope`. Composes the hardened internal\n * `walkUpForFile` (64-level cap, realpath dedup, FS-race tolerant).\n *\n * NEVER throws: a missing/unreadable directory or a path that exists but is not\n * a readable file is skipped; no instruction file → `{ files: [], content: undefined }`.\n *\n * Public via `@theokit/sdk/project`.\n *\n * @public\n */\nexport async function readProjectInstructions(\n  cwd: string,\n  options?: ReadProjectInstructionsOptions,\n): Promise<ProjectInstructions> {\n  const filename = options?.filename ?? DEFAULT_FILENAME;\n  const scope = options?.scope ?? \"nearest\";\n  const paths = walkUpForFile(cwd, filename, options?.stopDir);\n\n  const files: ProjectInstructionFile[] = [];\n  for (const path of paths) {\n    try {\n      files.push({ path, content: await readFile(path, \"utf8\") });\n    } catch {\n      // FS-race / unreadable / directory-named-like-file → skip (never-throw)\n    }\n  }\n\n  return { files, content: reduceContent(files, scope) };\n}\n\nfunction reduceContent(\n  files: ProjectInstructionFile[],\n  scope: ProjectInstructionScope,\n): string | undefined {\n  if (files.length === 0) return undefined;\n  if (scope === \"merged\") {\n    return [...files]\n      .reverse()\n      .map((f) => f.content)\n      .join(\"\\n\\n\");\n  }\n  return files[0]?.content;\n}\n\n/**\n * Write project instructions to `<cwd>/<filename>` atomically (temp + fsync +\n * rename, via the shipped `replaceFileAtomic`).\n *\n * Unlike the reader, this FAILS LOUD: an unsafe `filename` (path traversal,\n * separators, absolute) is rejected with `ConfigurationError`\n * (`code: \"unsafe_filename\"`) — symmetric with the reader, whose `filename`\n * flows through the same `isSafePattern` guard — and a write error (e.g. the\n * parent directory does not exist) propagates to the caller. A failed mutation\n * is a real error, never silently swallowed.\n *\n * Public via `@theokit/sdk/project`.\n *\n * @public\n */\nexport async function writeProjectInstructions(\n  cwd: string,\n  content: string,\n  options?: WriteProjectInstructionsOptions,\n): Promise<void> {\n  const filename = options?.filename ?? DEFAULT_FILENAME;\n  if (!isSafePattern(filename)) {\n    throw new ConfigurationError(\n      `writeProjectInstructions: unsafe filename ${JSON.stringify(filename)} (no path traversal, separators, or absolute paths)`,\n      { code: \"unsafe_filename\" },\n    );\n  }\n  await replaceFileAtomic(join(cwd, filename), content);\n}\n"]}