docuvia 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/contracts/src/logging/types.ts","../../../lib/contracts/src/logging/ipc-log-message.ts","../../../lib/contracts/src/logging/ipc-logger-client.ts","../../../lib/contracts/src/interfaces/hooks.interfaces.ts","../../../lib/contracts/src/constants/paths.ts","../../../lib/contracts/src/constants/encoding.ts","../../../lib/contracts/src/constants/languages.ts","../../../lib/contracts/src/constants/hooks.ts","../../../lib/contracts/src/logging/logger.ts","../../../lib/contracts/src/logging/ipc-log-router.ts","../../../lib/contracts/src/errors/error-codes.ts","../../../lib/contracts/src/errors/docuvia-error.ts","../../../lib/contracts/src/memory/docuvia-memory.ts","../../../lib/contracts/src/factory/docuvia-factory.ts","../../../lib/contracts/src/factory/tokens.ts","../../../lib/contracts/src/interfaces/git.interfaces.ts","../../../lib/contracts/src/interfaces/edge-resolution.interfaces.ts","../../../lib/contracts/src/interfaces/knowledge-git.interfaces.ts","../../../lib/contracts/src/interfaces/graph-store.interfaces.ts","../../../lib/contracts/src/interfaces/remote-sync.interfaces.ts","../../../lib/contracts/src/interfaces/llm-client.interfaces.ts","../../../lib/contracts/src/interfaces/impact.interfaces.ts","../../../lib/contracts/src/interfaces/query.interfaces.ts","../../../lib/contracts/src/interfaces/topology.interfaces.ts","../../../lib/contracts/src/constants/discovery-tags.ts","../../../lib/contracts/src/constants/git-conventions.ts","../../../lib/contracts/src/constants/source-files.ts","../../../lib/contracts/src/constants/fs.ts","../../../lib/contracts/src/utils/process-lock.ts","../../../lib/contracts/src/utils/git-trailers.ts","../../../lib/contracts/src/utils/tier-b-coverage.ts","../../../lib/contracts/src/interfaces/diagnostic.interfaces.ts"],"sourcesContent":["export const LogLevels = {\n DEBUG: \"debug\",\n INFO: \"info\",\n WARN: \"warn\",\n ERROR: \"error\",\n} as const;\n\n/**\n * Every log emitted anywhere in the workspace must conform to this shape — see\n * docs/gitbook/architecture/logging-architecture.md. `context` carries structured data\n * (file paths, counts, durations); it is never string-interpolated into `message`.\n */\nexport type LogLevel = (typeof LogLevels)[keyof typeof LogLevels];\n\nexport interface LogEvent {\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * The only logging surface implementation libraries and orchestration are allowed to call.\n * No implementation ever writes to stdout/stderr/disk directly — it emits an event, and the\n * Presentation layer's registered listener decides what happens to it.\n */\nexport interface ILogger {\n debug(message: string, context?: Record<string, unknown>): void;\n info(message: string, context?: Record<string, unknown>): void;\n warn(message: string, context?: Record<string, unknown>): void;\n error(message: string, context?: Record<string, unknown>): void;\n onLog(listener: (event: LogEvent) => void): () => void;\n}\n","import type { LogLevel } from \"./types.js\";\n\n/** Discriminant tag identifying an `IIpcLogMessage` on a shared `postMessage`/`process.send` channel. */\nexport const IpcLogMessageType = \"ipc-log\" as const;\n\n/**\n * The wire shape an `IpcLoggerClient` sends across a `postMessage`/`process.send` boundary —\n * see docs/gitbook/guidelines/playbook-ipc-logging.md. No `uuid`/scoping field: the main-thread\n * component that spawned the isolated context already holds the correct per-request `ILogger`\n * (received via Factory params, per the Type-Safe Registry), so routing is a direct forward,\n * not a `docuviaMemory` lookup.\n */\nexport interface IIpcLogMessage {\n type: typeof IpcLogMessageType;\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n}\n\n/** Narrows an arbitrary `postMessage`/`process.send` payload to an `IIpcLogMessage`. */\nexport function isIpcLogMessage(value: unknown): value is IIpcLogMessage {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { type?: unknown }).type === IpcLogMessageType\n );\n}\n","import { LogLevels } from \"./types.js\";\nimport type { ILogger } from \"./types.js\";\nimport { IpcLogMessageType } from \"./ipc-log-message.js\";\nimport type { IIpcLogMessage } from \"./ipc-log-message.js\";\n\n/**\n * `ILogger` implementation for isolated contexts (`worker_threads`, `child_process`) — see\n * docs/gitbook/guidelines/playbook-ipc-logging.md. A live `ILogger` (or its callback closures)\n * cannot cross a `postMessage`/`process.send` boundary (functions are unclonable), so this\n * serializes every call into an `IIpcLogMessage` and hands it to whatever transport function\n * the caller supplied. The worker code itself only ever sees the standard `ILogger` interface —\n * it never needs to know it's running isolated.\n */\nexport class IpcLoggerClient implements ILogger {\n constructor(\n private readonly postMessageFn: (message: IIpcLogMessage) => void,\n ) {}\n\n debug(message: string, context?: Record<string, unknown>): void {\n this.send(LogLevels.DEBUG, message, context);\n }\n\n info(message: string, context?: Record<string, unknown>): void {\n this.send(LogLevels.INFO, message, context);\n }\n\n warn(message: string, context?: Record<string, unknown>): void {\n this.send(LogLevels.WARN, message, context);\n }\n\n error(message: string, context?: Record<string, unknown>): void {\n this.send(LogLevels.ERROR, message, context);\n }\n\n /** An isolated context has no listeners of its own to register — logs only ever flow outward through `postMessageFn`. Returns a no-op unsubscribe for interface conformance. */\n onLog(): () => void {\n return () => {};\n }\n\n private send(\n level: IIpcLogMessage[\"level\"],\n message: string,\n context?: Record<string, unknown>,\n ): void {\n const payload: IIpcLogMessage = context\n ? { type: IpcLogMessageType, level, message, context }\n : { type: IpcLogMessageType, level, message };\n this.postMessageFn(payload);\n }\n}\n","/**\n * Shared vocabulary for Docuvia's three toggleable automation hooks (issue #42, roadmap items\n * 32-34): `context-injection` (the PreToolUse Claude/Cursor script that shells out to `docuvia\n * query` before Grep/Glob/Bash/Read -- roadmap item 26), `commit-l3-write` (the post-commit\n * flush of agent-staged L3 decisions, Decision 2's two-stage stage-and-flush design), and\n * `tier-b-c-prepush` (the pre-push Tier B/C batch). Consumed both by the compiled CLI (`docuvia\n * hooks list/enable/disable`, `lib/ui-core`'s `HooksWorkflow`) and by raw, dependency-free\n * platform scripts written to disk (`artifacts/cli/src/constants/init-templates.ts`'s\n * `DOCUVIA_HOOK_JS`) -- those scripts can't `import` this module at runtime (no\n * `@workspace/contracts` inside a standalone `.js` hook file), so `init-templates.ts` interpolates\n * the resolved literal string values in at template-build time instead; see that file's own doc\n * comment.\n */\nexport const HookNames = {\n CONTEXT_INJECTION: \"context-injection\",\n COMMIT_L3_WRITE: \"commit-l3-write\",\n TIER_B_C_PREPUSH: \"tier-b-c-prepush\",\n} as const;\nexport type HookName = (typeof HookNames)[keyof typeof HookNames];\n\nexport type HooksConfig = Record<HookName, boolean>;\n\n/** Per Decision 2/roadmap item 34: `commit-l3-write` ships enabled by default (matches Docuvia's\n * incremental-accumulation philosophy). `context-injection` and `tier-b-c-prepush` are pre-\n * existing, always-on behaviors this issue is retrofitting a toggle onto -- also default enabled,\n * so `docuvia hooks list` on an already-set-up repo reports the status quo, not a surprise\n * opt-out. */\nexport const DEFAULT_HOOKS_CONFIG: HooksConfig = {\n [HookNames.CONTEXT_INJECTION]: true,\n [HookNames.COMMIT_L3_WRITE]: true,\n [HookNames.TIER_B_C_PREPUSH]: true,\n};\n","/**\n * Workspace-layout conventions shared across every layer (Presentation resolves paths from\n * these, `lib/ui-core` writes run logs against them, `lib/core`'s command-log-writer — wait,\n * see `lib/ui-core/src/utils/command-log-writer.ts` — reads them too). Zero logic, just the\n * shared naming convention every layer must agree on, per design-spirit.md's \"Centralized\n * Constants\" rule.\n */\n\n/** Name of the hidden directory holding all local Docuvia state (SQLite db, temp files, run logs). */\nexport const DOCUVIA_DIR_NAME = \".docuvia\";\n\n/** Directory (relative to `DOCUVIA_DIR_NAME`) holding persisted, AI-inspectable run logs. */\nexport const DOCUVIA_LOGS_DIR_NAME = \"logs\";\n\nexport const INIT_LOG_FILE_NAME = \"init.log\";\nexport const CLEAN_LOG_FILE_NAME = \"clean.log\";\nexport const STATUS_LOG_FILE_NAME = \"status.log\";\nexport const SYNC_LOG_FILE_NAME = \"sync.log\";\nexport const ANALYZE_LOG_FILE_NAME = \"analyze.log\";\nexport const REVIEW_LOG_FILE_NAME = \"review.log\";\nexport const IMPACT_LOG_FILE_NAME = \"impact.log\";\nexport const QUERY_LOG_FILE_NAME = \"query.log\";\nexport const EXPORT_TOPOLOGY_LOG_FILE_NAME = \"export-topology.log\";\nexport const SNAPSHOT_LOG_FILE_NAME = \"snapshot.log\";\nexport const HYDRATE_LOG_FILE_NAME = \"hydrate.log\";\nexport const SYNC_KNOWLEDGE_LOG_FILE_NAME = \"sync-knowledge.log\";\n\n/** Dependency-install directory name every scan in this package excludes, whether via a glob\n * ignore pattern (`COMMON_GLOB_IGNORE_PATTERNS`) or a plain substring check\n * (`FileDiscoveryService`) — moved here from `lib/core` (issue #93) so upper layers share it\n * without a `lib/core` dependency. */\nexport const NODE_MODULES_DIR_NAME = \"node_modules\";\n\n/** Filename (relative to `DOCUVIA_DIR_NAME`) of the local SQLite database. */\nexport const LOCAL_DB_FILE_NAME = \"local.db\";\n\n/** Filename (relative to `DOCUVIA_DIR_NAME`) of the whole-`init`-command single-flight lockfile (PLAT-006). */\nexport const INIT_COMMAND_LOCK_FILE_NAME = \"init.lock\";\n\n/** Filename (relative to `DOCUVIA_DIR_NAME`) of the Tier C drain's single-flight lockfile\n * (phase1-decision-integration.md §9f, PLAT-006 pattern) — held for the whole dispatch window\n * of `analyze --escalate-to-lsp`'s Tier C queue drain. */\nexport const TIER_C_LOCK_FILE_NAME = \"tierC.lock\";\n\n/** Filename (relative to `DOCUVIA_DIR_NAME/DOCUVIA_LOGS_DIR_NAME`) of the `sync` content-hash dedup cache. */\nexport const SYNC_STATE_FILE_NAME = \"sync-state.json\";\n\n/** Filename (relative to `DOCUVIA_DIR_NAME`) of the `docuvia hooks list/enable/disable`\n * persistence file (issue #42, roadmap items 32-34 §7.1) -- a flat JSON `HooksConfig` map, read\n * by both the compiled CLI and the raw `.claude/hooks/docuvia-hook.js` script (plain\n * `fs.readFileSync` + `JSON.parse`, no subprocess). See `hooks.interfaces.ts`'s doc comment. */\nexport const HOOKS_CONFIG_FILE_NAME = \"hooks-config.json\";\n\n/** Filename (relative to `DOCUVIA_DIR_NAME`) of `commit-l3-write`'s staging file (issue #42,\n * Decision 2's two-stage stage-and-flush design §8.1) -- `analyze <targetPath> --agent-authored\n * --stage` appends here instead of writing straight to `l3_nodes`; the post-commit hook's\n * `analyze --flush-staged-l3` step drains entries whose `filePath` is in the triggering commit's\n * changed-file list. */\nexport const PENDING_L3_DECISIONS_FILE_NAME = \"pending-l3-decisions.json\";\n\n/**\n * Files at/above this size are skipped during discovery rather than fully read and handed to a\n * parse worker (shared by `lib/core`'s `file-discovery.service.ts` and `lib/ui-core`'s delta\n * ingestion oversize check). Matches GitNexus's documented oversized-file threshold, so\n * file-count comparisons between the two tools are apples-to-apples.\n */\nexport const MAX_FILE_SIZE_BYTES = 512_000;\n","/**\n * Plain string constants shared across layers for file I/O encoding — no logic, just the\n * naming convention every layer must agree on (see `paths.ts`'s doc comment). `UTF8_ENCODING`\n * lives here (rather than only in `lib/core`) because the Presentation layer (`artifacts/cli`)\n * reads it directly for its own file writes, and `artifacts/cli` is only allowed to depend on\n * `lib/contracts` (see docs/gitbook/architecture/virtual-contracts-architecture.md) — never on\n * `lib/core` directly.\n */\nexport const UTF8_ENCODING = \"utf8\" as const;\n","/**\n * The set of languages the AST layer can parse, shared across the whole workspace\n * (Virtual Contracts §8 — shared definitions must live in contracts). Moved here from\n * `lib/ast-core` so `lib/core` / `lib/plugins-ast` can reference the supported-language\n * set without importing the tree-sitter tech-provider package for a plain constant.\n *\n * `SupportedLanguage` is the string-literal union over the keys — values are stable\n * identifiers persisted in the graph (`l2_nodes.language`), not display names.\n */\nexport const SUPPORTED_LANGUAGES = {\n TYPESCRIPT: \"typescript\",\n JAVASCRIPT: \"javascript\",\n PYTHON: \"python\",\n RUST: \"rust\",\n GO: \"go\",\n JAVA: \"java\",\n C: \"c\",\n CPP: \"cpp\",\n RUBY: \"ruby\",\n PHP: \"php\",\n CSHARP: \"csharp\",\n} as const;\n\nexport type SupportedLanguage =\n (typeof SUPPORTED_LANGUAGES)[keyof typeof SUPPORTED_LANGUAGES];\n","/**\n * Repo-relative directories where `init`'s platform installers (`artifacts/cli/src/platforms/*`)\n * write each AI-agent platform's hook script. Shared with `DoctorWorkflow`'s agent-hooks presence\n * diagnostic (workflows/doctor-execution-flow.md's Presentation-layer-asymmetry cleanup) so both\n * sides read the same path. Moved here from `lib/core` (issue #69) so the Presentation layer only\n * ever imports `@workspace/contracts` for plain constants.\n */\nexport const CLAUDE_HOOKS_DIR = \".claude/hooks\";\nexport const CURSOR_HOOKS_DIR = \".cursor/hooks\";\n\n/** Filenames of the hook script `init` writes under each platform's hooks dir above. */\nexport const DOCUVIA_HOOK_JS_FILENAME = \"docuvia-hook.js\";\nexport const DOCUVIA_HOOK_CJS_FILENAME = \"docuvia-hook.cjs\";\n","import { LogLevels } from \"./types.js\";\nimport type { ILogger, LogEvent, LogLevel } from \"./types.js\";\n\n/**\n * Pure event-emitting `ILogger` implementation — the base class every layer shares (see\n * \"Exception to Zero-Logic\" in docs/gitbook/architecture/logging-architecture.md: `contracts`\n * is allowed to provide this one concrete class so all layers speak the same event bus).\n *\n * Carries no transport, no formatting, no third-party logging dependency (e.g. pino) — those\n * are exclusively the Presentation layer's concern, wired up by attaching an `onLog` listener.\n */\nexport class Logger implements ILogger {\n private readonly listeners = new Set<(event: LogEvent) => void>();\n\n debug(message: string, context?: Record<string, unknown>): void {\n this.emit(LogLevels.DEBUG, message, context);\n }\n\n info(message: string, context?: Record<string, unknown>): void {\n this.emit(LogLevels.INFO, message, context);\n }\n\n warn(message: string, context?: Record<string, unknown>): void {\n this.emit(LogLevels.WARN, message, context);\n }\n\n error(message: string, context?: Record<string, unknown>): void {\n this.emit(LogLevels.ERROR, message, context);\n }\n\n /** Registers a listener; returns an unsubscribe function. */\n onLog(listener: (event: LogEvent) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private emit(\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n ): void {\n const event: LogEvent = context\n ? { level, message, context }\n : { level, message };\n for (const listener of this.listeners) listener(event);\n }\n}\n\n/** A logger with zero listeners attached — every event is dropped. Safe default for call sites that receive no injected logger (e.g. constructing a service outside of a full workflow, in a test). */\nexport function createNoopLogger(): ILogger {\n return new Logger();\n}\n","import type { ILogger } from \"./types.js\";\nimport { isIpcLogMessage, type IIpcLogMessage } from \"./ipc-log-message.js\";\n\n/**\n * Main-thread counterpart to `IpcLoggerClient` — see\n * docs/gitbook/guidelines/playbook-ipc-logging.md. The component that spawned the worker\n * already holds the real, per-request `ILogger` (received via Factory params), so routing is a\n * direct forward: no `docuviaMemory` lookup, no UUID.\n */\nexport class IpcLogRouter {\n constructor(private readonly logger: ILogger) {}\n\n /**\n * Feed every message received from the worker/child process through this. Non-`ipc-log`\n * messages (e.g. task results) are ignored. Doubles as a type guard, so callers narrow their\n * own union type for free: `if (router.handleMessage(msg)) return; // msg is now the\n * remaining, non-log branch of the union`.\n */\n handleMessage(message: unknown): message is IIpcLogMessage {\n if (!isIpcLogMessage(message)) return false;\n this.logger[message.level](message.message, message.context);\n return true;\n }\n}\n","/**\n * Registry of every known failure mode across the workspace — see\n * docs/gitbook/architecture/error-handling-architecture.md. Append new codes here as new\n * failure modes are discovered; never throw a raw third-party error across a layer boundary.\n */\nexport const ErrorCodes = {\n // Git (lib/git-local / lib/core/git)\n GIT_COMMAND_FAILED: \"GIT_COMMAND_FAILED\",\n GIT_NETWORK_TIMEOUT: \"GIT_NETWORK_TIMEOUT\",\n GIT_NOT_A_REPOSITORY: \"GIT_NOT_A_REPOSITORY\",\n GIT_BRANCH_CREATE_FAILED: \"GIT_BRANCH_CREATE_FAILED\",\n GIT_HOOK_INSTALL_FAILED: \"GIT_HOOK_INSTALL_FAILED\",\n GIT_FAST_IMPORT_FAILED: \"GIT_FAST_IMPORT_FAILED\",\n\n // Database / memory layer (lib/schema)\n DB_OPEN_FAILED: \"DB_OPEN_FAILED\",\n DB_NOT_FOUND: \"DB_NOT_FOUND\",\n DB_MIGRATION_FAILED: \"DB_MIGRATION_FAILED\",\n DB_QUERY_FAILED: \"DB_QUERY_FAILED\",\n DB_LOCKED: \"DB_LOCKED\",\n\n // AST processing (lib/core/ast)\n AST_WORKER_CRASHED: \"AST_WORKER_CRASHED\",\n AST_PARSE_FAILED: \"AST_PARSE_FAILED\",\n\n // File discovery / scanning (lib/core/discovery)\n FS_READ_FAILED: \"FS_READ_FAILED\",\n FS_PATH_TRAVERSAL: \"FS_PATH_TRAVERSAL\",\n\n // Virtual layer (lib/contracts) itself\n FACTORY_TOKEN_NOT_REGISTERED: \"FACTORY_TOKEN_NOT_REGISTERED\",\n FACTORY_LOCKED: \"FACTORY_LOCKED\",\n MEMORY_SCOPE_NOT_FOUND: \"MEMORY_SCOPE_NOT_FOUND\",\n\n // Remote sync client (lib/remote-api)\n SYNC_FETCH_FAILED: \"SYNC_FETCH_FAILED\",\n SYNC_PUSH_FAILED: \"SYNC_PUSH_FAILED\",\n\n // LLM client (lib/llm-api)\n LLM_NOT_INITIALIZED: \"LLM_NOT_INITIALIZED\",\n LLM_CHAT_COMPLETION_FAILED: \"LLM_CHAT_COMPLETION_FAILED\",\n LLM_STREAM_FAILED: \"LLM_STREAM_FAILED\",\n LLM_INVALID_RESPONSE: \"LLM_INVALID_RESPONSE\",\n\n // Orchestration (lib/ui-core)\n INIT_WORKFLOW_FAILED: \"INIT_WORKFLOW_FAILED\",\n CLEAN_WORKFLOW_FAILED: \"CLEAN_WORKFLOW_FAILED\",\n\n // Boundary validation (Presentation layer)\n INVALID_INPUT: \"INVALID_INPUT\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n","import type { ErrorCode } from \"./error-codes.js\";\n\nconst DOCUVIA_ERROR_NAME = \"DocuviaError\" as const;\n\n/**\n * The single base error class used across the entire workspace — see\n * docs/gitbook/architecture/error-handling-architecture.md's \"Catch, Wrap, and Bubble\"\n * pipeline. Implementation libraries catch a native/third-party error, map it to an\n * `ErrorCode`, and throw a `DocuviaError` wrapping the original as `cause`. Only the\n * Presentation layer is allowed to log one.\n */\nexport class DocuviaError extends Error {\n public readonly code: ErrorCode;\n public readonly cause?: unknown;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = DOCUVIA_ERROR_NAME;\n this.code = code;\n this.cause = cause;\n }\n\n static wrap(code: ErrorCode, message: string, cause: unknown): DocuviaError {\n if (cause instanceof DocuviaError) return cause;\n const causeMessage = cause instanceof Error ? cause.message : String(cause);\n return new DocuviaError(code, `${message}: ${causeMessage}`, cause);\n }\n}\n","import { DocuviaError } from \"../errors/docuvia-error.js\";\nimport { ErrorCodes } from \"../errors/error-codes.js\";\n\n/**\n * Valid keys for the UUID-scoped runtime configuration store.\n * Prevents magic-string typing errors across layers.\n */\nexport const MemoryKeys = {\n WORKSPACE_ROOT: \"workspaceRoot\",\n API_URL: \"apiUrl\",\n PAT: \"pat\",\n PROJECT_ID: \"projectId\",\n COMMIT_SHA: \"commitSha\",\n TARGET_PATH: \"targetPath\",\n LLM_BASE_URL: \"llmBaseUrl\",\n LLM_MODEL: \"llmModel\",\n BASE_REF: \"baseRef\",\n TARGET: \"target\",\n ESCALATE_TO_LSP: \"escalateToLsp\",\n /** `analyze --escalate-to-lsp --full` (typescript-cli-benchmark.md §5.3/§5.7 item 1) -- pre-\n * populates `tierBQueue` with every currently-tracked file before the batch drains it. Ignored\n * outside `ESCALATE_TO_LSP`. */\n TIER_B_FULL_RESYNC: \"tierBFullResync\",\n LIMIT: \"limit\",\n COLLAPSE: \"collapse\",\n /** `uninstall --keep-db` — when set, skips local.db deletion, whole-`.docuvia/`-dir removal,\n * and the `docuvia-knowledge` branch delete (all three are the same underlying graph, just in\n * three storage forms — STOR-001/STOR-002 — so \"keep my data\" means keeping all three). */\n KEEP_DB: \"keepDb\",\n /** §8b \"config-overridable\" LSP binary path — absolute path or bare command on PATH. */\n LSP_BINARY_OVERRIDE: \"lspBinaryOverride\",\n /** §8b \"config-overridable\" LSP binary args (space-separated in the env var, split by the CLI layer). */\n LSP_ARGS_OVERRIDE: \"lspArgsOverride\",\n /** §8h \"generous initial timeout\" override, in milliseconds. */\n LSP_TIMEOUT_MS: \"lspTimeoutMs\",\n /** Tier B multi-process sharding override — how many independent LSP server processes to\n * shard the Tier B batch across (see `EdgeResolutionProviderConfig.maxProcesses`). */\n LSP_MAX_PROCESSES: \"lspMaxProcesses\",\n /** §8f commit-cap override (default 20, config-tunable). */\n TIER_B_COMMIT_CAP: \"tierBCommitCap\",\n /** §9f Tier C daily call-budget override. */\n TIER_C_DAILY_CALL_CAP: \"tierCDailyCallCap\",\n /** §9f Tier C daily token-budget override (estimated, per `estimateTokenCount`). */\n TIER_C_DAILY_TOKEN_CAP: \"tierCDailyTokenCap\",\n /** §9d Tier C per-run wall-clock cap override, in milliseconds. */\n TIER_C_WALL_CLOCK_MS: \"tierCWallClockMs\",\n /** §9d Tier C per-run item-count cap override (whichever of the two caps binds first). */\n TIER_C_ITEM_CAP: \"tierCItemCap\",\n /** §9f Tier C system-load-check threshold override (`loadavg[0] / cpus > threshold`). */\n TIER_C_LOAD_THRESHOLD: \"tierCLoadThreshold\",\n /** `sync-knowledge`'s git fetch/push network timeout override, in milliseconds\n * (`DOCUVIA_PUSH_TIMEOUT_MS`). `undefined` (unset) is the default and means \"no timeout — wait\n * for the transfer to finish, however long that takes\" (found via dogfooding: a real\n * `sync-knowledge` push on Docuvia2 routinely exceeds 60s, so a fixed bound cut off healthy\n * pushes, not just hung ones). */\n GIT_NETWORK_TIMEOUT_MS: \"gitNetworkTimeoutMs\",\n /** Manual force override. */\n FORCE: \"force\",\n /** The already-parsed `{title, content, nodeType, confidence}[]` payload for\n * `--agent-authored` mode -- boundary-validated by the CLI layer (zod) before this is set. */\n AGENT_AUTHORED_DECISIONS: \"agentAuthoredDecisions\",\n /** `docuvia hooks enable/disable <hookName>` -- the `HookName` being toggled. */\n HOOK_NAME: \"hookName\",\n /** `docuvia hooks enable/disable <hookName>` -- the boolean to persist for `HOOK_NAME`. */\n HOOK_ENABLED: \"hookEnabled\",\n /** `analyze --flush-staged-l3` (issue #42, Decision 2's two-stage stage-and-flush design §8.2)\n * -- when set, `docuviaApi.analyze()` dispatches `AnalyzeWorkflow`'s flush mode instead of any\n * of `targetPath`/`agentAuthoredDecisions`/`escalateToLsp` (none of which this mode sets). */\n FLUSH_STAGED_L3: \"flushStagedL3\",\n} as const;\n\nexport type MemoryKey = (typeof MemoryKeys)[keyof typeof MemoryKeys];\n\nconst MemoryErrorMessages = {\n SCOPE_NOT_FOUND: (key: MemoryKey, scopeId: string) =>\n `Cannot set \"${key}\": memory scope \"${scopeId}\" was never created`,\n} as const;\n\n/**\n * UUID-scoped runtime configuration store — see\n * docs/gitbook/architecture/application-lifecycle-and-state.md. The *only* source of truth\n * for runtime configuration (workspace paths, log level, feature flags, ...). Implementation\n * libraries read exclusively from here (never `process.env`); the Presentation layer creates a\n * scope per invocation and is responsible for deleting it when the run completes (Garbage\n * Collection), preventing OOM leaks in long-running hosts like an MCP server.\n */\nexport class DocuviaMemory {\n private readonly scopes = new Map<string, Map<string, unknown>>();\n\n /** Creates an empty scope for `scopeId` (no-op if it already exists). */\n createScope(scopeId: string): void {\n if (!this.scopes.has(scopeId)) this.scopes.set(scopeId, new Map());\n }\n\n set<T>(scopeId: string, key: MemoryKey, value: T): void {\n const scope = this.scopes.get(scopeId);\n if (!scope) {\n throw new DocuviaError(\n ErrorCodes.MEMORY_SCOPE_NOT_FOUND,\n MemoryErrorMessages.SCOPE_NOT_FOUND(key, scopeId),\n );\n }\n scope.set(key, value);\n }\n\n get<T>(scopeId: string, key: MemoryKey): T | undefined {\n return this.scopes.get(scopeId)?.get(key) as T | undefined;\n }\n\n /** Deletes the entire scope. Must be called by the Presentation layer once a run is complete. */\n deleteScope(scopeId: string): void {\n this.scopes.delete(scopeId);\n }\n\n hasScope(scopeId: string): boolean {\n return this.scopes.has(scopeId);\n }\n}\n\nexport const docuviaMemory = new DocuviaMemory();\n","import { DocuviaError } from \"../errors/docuvia-error.js\";\nimport { ErrorCodes } from \"../errors/error-codes.js\";\nimport type { Token } from \"./tokens.js\";\n\n/**\n * A provider constructs a `T` given the factory itself (so it can resolve its own nested\n * dependencies by token) and optional per-call params (e.g. a request-scoped logger, which is\n * never resolved from the factory — see docs/gitbook/architecture/logging-architecture.md).\n */\nexport type Provider<T, P = void> = (factory: DocuviaFactory, params: P) => T;\n\nconst FactoryErrorMessages = {\n REGISTER_LOCKED: (description: string) =>\n `Cannot register \"${description}\": factory is locked (test isolation)`,\n PROVIDER_NOT_REGISTERED: (description: string) =>\n `No provider registered for \"${description}\" — the implementation ` +\n `library that owns it was never imported for its registration side effect`,\n} as const;\n\n/**\n * The only globally permitted registration factory — see\n * docs/gitbook/architecture/virtual-contracts-architecture.md#8 (Type-Safe Registry). Stores\n * *constructors*, not active instances: every `resolve()` call returns a brand-new, transient\n * instance (unless a provider deliberately closes over a shared singleton itself).\n * Implementation libraries self-register a provider as a side effect of being imported;\n * orchestration resolves by token and never imports the concrete implementation module\n * directly.\n *\n * Type safety comes entirely from `Token<T, P>` (see `tokens.ts`) being a phantom-typed\n * `symbol` — `register()`/`resolve()` infer `T`/`P` straight from whichever token value is\n * passed in, so requesting one interface through a token typed for another is a compile error,\n * with zero manual generic annotations needed at any call site.\n */\nexport class DocuviaFactory {\n private readonly providers = new Map<symbol, Provider<unknown, unknown>>();\n private locked = false;\n\n register<T, P = void>(token: Token<T, P>, provider: Provider<T, P>): void {\n if (this.locked) {\n throw new DocuviaError(\n ErrorCodes.FACTORY_LOCKED,\n FactoryErrorMessages.REGISTER_LOCKED(String(token.description)),\n );\n }\n this.providers.set(token, provider as Provider<unknown, unknown>);\n }\n\n resolve<T, P = void>(token: Token<T, P>, params?: P): T {\n const provider = this.providers.get(token);\n if (!provider) {\n throw new DocuviaError(\n ErrorCodes.FACTORY_TOKEN_NOT_REGISTERED,\n FactoryErrorMessages.PROVIDER_NOT_REGISTERED(String(token.description)),\n );\n }\n return provider(this, params as P) as T;\n }\n\n has(token: symbol): boolean {\n return this.providers.has(token);\n }\n\n /** Test isolation: freezes registrations so a stray implementation-library import can't silently overwrite a test's mock provider mid-run. */\n lock(): void {\n this.locked = true;\n }\n\n unlock(): void {\n this.locked = false;\n }\n\n /** Test isolation: clears every registration. Callers must re-import registration side effects (or re-register mocks) afterwards. */\n reset(): void {\n this.providers.clear();\n this.locked = false;\n }\n}\n\nexport const docuviaFactory = new DocuviaFactory();\n","import type { ILogger } from \"../logging/types.js\";\nimport type { IGitProvider } from \"../interfaces/git.interfaces.js\";\nimport type { IKnowledgeGitService } from \"../interfaces/knowledge-git.interfaces.js\";\nimport type {\n IConfigScanner,\n IFileDiscovery,\n IVcsScanner,\n} from \"../interfaces/discovery.interfaces.js\";\nimport type { IAstProcessor } from \"../interfaces/ast.interfaces.js\";\nimport type { IGraphPersister } from \"../interfaces/graph-persister.interfaces.js\";\nimport type {\n GraphStoreOpenOptions,\n IGraphStore,\n} from \"../interfaces/graph-store.interfaces.js\";\nimport type { ITempFileManager } from \"../interfaces/temp-file-manager.interfaces.js\";\nimport type { IRemoteSyncClient } from \"../interfaces/remote-sync.interfaces.js\";\nimport type { ILlmClient } from \"../interfaces/llm-client.interfaces.js\";\nimport type { IQueryService } from \"../interfaces/query.interfaces.js\";\nimport type { ITierBCoverageHintProvider } from \"../interfaces/query.interfaces.js\";\nimport type { IImpactService } from \"../interfaces/impact.interfaces.js\";\nimport type { IChangeDetectionService } from \"../interfaces/change-detection.interfaces.js\";\nimport type { ITopologyBuilder } from \"../interfaces/topology.interfaces.js\";\nimport type { ISnapshotRenderer } from \"../interfaces/snapshot.interfaces.js\";\nimport type { IHydrationService } from \"../interfaces/hydration.interfaces.js\";\nimport type { IDiagnosticRunner } from \"../interfaces/diagnostic.interfaces.js\";\nimport type { ISemanticDiffAnalyzer } from \"../interfaces/semantic-diff.interfaces.js\";\nimport type {\n IEdgeResolutionProvider,\n TierBLanguageId,\n} from \"../interfaces/edge-resolution.interfaces.js\";\n\n/**\n * A phantom-typed registration token — see\n * docs/gitbook/architecture/virtual-contracts-architecture.md#8 (Type-Safe Registry). At\n * runtime this is nothing but a `symbol`; `T`/`P` never hold a real value — they exist purely\n * so `docuviaFactory.register()`/`.resolve()` can infer the provider's return type and params\n * type directly from the token argument, with zero manual generic annotations at any call site\n * and zero possibility of requesting one interface through a differently-typed token.\n */\nexport type Token<T, P = void> = symbol & {\n readonly __docuviaToken?: { result: T; params: P };\n};\n\n/** Creates a token carrying its `T`/`P` as a compile-time-only phantom type. `description` is\n * only for debugging (shows up in `Symbol#description` / error messages) — it carries no\n * type information itself. */\nexport function createToken<T, P = void>(description: string): Token<T, P> {\n return Symbol(description) as Token<T, P>;\n}\n\ntype LoggerParams = { logger?: ILogger };\n\n/** `KnowledgeGitService`'s params: the shared `logger` plus §\"sync-knowledge push timeout\"'s\n * config-tunable override for `fetchRef`/`pushRef`'s network bound (`undefined` — the default —\n * means \"no timeout, wait for the transfer to finish\"). */\ntype KnowledgeGitServiceParams = LoggerParams & {\n gitNetworkTimeoutMs?: number;\n};\n\n/**\n * Every registration token in the workspace. This is the single declaration point pairing a\n * token with its interface (and, where relevant, its per-call params shape) — the \"register it\n * in the TokenMap\" step every new capability goes through. Everywhere else in the codebase,\n * `register()`/`resolve()` infer everything from whichever `TOKENS.X` value is passed in.\n */\nexport const TOKENS = {\n GitProvider: createToken<IGitProvider>(\"IGitProvider\"),\n KnowledgeGitService: createToken<\n IKnowledgeGitService,\n KnowledgeGitServiceParams\n >(\"IKnowledgeGitService\"),\n FileDiscovery: createToken<IFileDiscovery, LoggerParams>(\"IFileDiscovery\"),\n ConfigScanner: createToken<IConfigScanner, LoggerParams>(\"IConfigScanner\"),\n VcsScanner: createToken<IVcsScanner, LoggerParams>(\"IVcsScanner\"),\n AstProcessor: createToken<IAstProcessor, LoggerParams>(\"IAstProcessor\"),\n GraphPersister: createToken<IGraphPersister>(\"IGraphPersister\"),\n TempFileManager:\n createToken<(workspaceRoot: string, logger?: ILogger) => ITempFileManager>(\n \"TempFileManager\",\n ),\n GraphStoreOpener:\n createToken<(opts: GraphStoreOpenOptions) => Promise<IGraphStore>>(\n \"GraphStoreOpener\",\n ),\n /** A builder function, not a shared instance — mirrors `TempFileManager`'s token shape since\n * construction needs per-run config (`apiUrl`/`pat`) sourced from `docuviaMemory`, not\n * swappable tech. */\n RemoteSyncClient: createToken<() => IRemoteSyncClient>(\"RemoteSyncClient\"),\n /** A builder function, not a shared instance — mirrors `RemoteSyncClient`'s token shape since\n * construction needs per-run config (`baseUrl`/`apiKey`) sourced from `docuviaMemory`, not\n * swappable tech. */\n LlmClient: createToken<() => ILlmClient>(\"LlmClient\"),\n QueryService: createToken<IQueryService, LoggerParams>(\"IQueryService\"),\n TierBCoverageHintProvider: createToken<ITierBCoverageHintProvider>(\n \"ITierBCoverageHintProvider\",\n ),\n ImpactService: createToken<IImpactService, LoggerParams>(\"IImpactService\"),\n ChangeDetectionService: createToken<IChangeDetectionService, LoggerParams>(\n \"IChangeDetectionService\",\n ),\n TopologyBuilder: createToken<ITopologyBuilder, LoggerParams>(\n \"ITopologyBuilder\",\n ),\n SnapshotRenderer: createToken<ISnapshotRenderer>(\"ISnapshotRenderer\"),\n HydrationService: createToken<IHydrationService, LoggerParams>(\n \"IHydrationService\",\n ),\n DiagnosticRunnerDb: createToken<IDiagnosticRunner>(\"DiagnosticRunnerDb\"),\n DiagnosticRunnerGit: createToken<IDiagnosticRunner, LoggerParams>(\n \"DiagnosticRunnerGit\",\n ),\n SemanticDiffAnalyzer: createToken<ISemanticDiffAnalyzer, LoggerParams>(\n \"ISemanticDiffAnalyzer\",\n ),\n /** A registry, not a single builder function (multi-language-lsp-support plan, Finding A) — the\n * `DocuviaFactory` itself stays single-value-per-token, so the *value* behind this one token is\n * a `Partial<Record<TierBLanguageId, ...>>` map of per-language provider builders. The Tier B\n * batch resolves this once, looks up the builder for each queued language, then calls\n * `.configure()` on the built provider with any overrides before using it — exactly like\n * `ILlmClient.initialize()`, just keyed by language now. Slice 0 registers just `{ typescript }`\n * (unchanged behavior); later language slices each add one more key. */\n EdgeResolutionProviders: createToken<\n Partial<Record<TierBLanguageId, () => IEdgeResolutionProvider>>,\n LoggerParams\n >(\"EdgeResolutionProviders\"),\n} as const;\n","/**\n * Raw Git technology surface — implemented by `lib/git-local`. Contains no Docuvia-specific\n * semantics (no \"knowledge branch\", no \"post-commit hook\" concept); see\n * `knowledge-git.interfaces.ts` for the Domain Core layer built on top of this.\n */\nexport const ChangedFileStatuses = {\n ADDED: \"added\",\n MODIFIED: \"modified\",\n DELETED: \"deleted\",\n RENAMED: \"renamed\",\n} as const;\nexport type ChangedFileStatus =\n (typeof ChangedFileStatuses)[keyof typeof ChangedFileStatuses];\n\nexport interface ChangedFileEntry {\n file: string;\n status: ChangedFileStatus;\n /** Previous path for a `status: \"renamed\"` entry (the `old` half of git's `R###\\told\\tnew` name-status line). Undefined for every other status. */\n oldFile?: string;\n}\n\n/** 0-indexed, tree-sitter-convention `[startRow, endRow]` (inclusive) line range touched by a diff hunk. */\nexport interface DiffLineRange {\n startRow: number;\n endRow: number;\n}\n\n/** One entry of `git worktree list --porcelain` — a live worktree of the repo this command is\n * run from. Used by `doctor`'s worktree-divergence diagnostic (issue #137): each worktree gets\n * its own `.docuvia/local.db`, and nothing reconciles them, so a decision staged in one worktree\n * may never reach the graph another worktree (or the main checkout) queries. */\nexport interface WorktreeEntry {\n /** Absolute path of the worktree's working tree. */\n path: string;\n /** Checked-out branch name (without the `refs/heads/` prefix), undefined for a detached HEAD. */\n branch?: string;\n}\n\nexport interface IGitProvider {\n isGitRepository(cwd: string): Promise<boolean>;\n\n /** Lists local branches matching `namePattern` exactly (used to check existence). */\n branchExists(cwd: string, branchName: string): Promise<boolean>;\n /** `git branch -D <branchName>` — force-deletes a local branch regardless of merge status (the\n * knowledge branch is an orphan, never merged into source history, so a plain `-d` would\n * always fail with \"not fully merged\"). Throws if the branch doesn't exist — callers that want\n * a no-op in that case should check `branchExists()` first (see `IKnowledgeGitService`). */\n deleteBranch(cwd: string, branchName: string): Promise<void>;\n /** `git commit-tree <empty-tree-sha> -m <message>` — creates a rootless commit pointing at\n * git's well-known empty tree, and returns the new commit sha. */\n commitEmptyTree(cwd: string, message: string): Promise<string>;\n /** `git update-ref refs/heads/<branchName> <commitSha>`. */\n updateBranchRef(\n cwd: string,\n branchName: string,\n commitSha: string,\n ): Promise<void>;\n\n hooksDirExists(cwd: string): Promise<boolean>;\n /** The directory hook files actually belong in for this repo — honors `core.hooksPath` (e.g.\n * a husky-managed repo), unlike assuming `<git-dir>/hooks`. Exposed so callers like `doctor`\n * can report exactly where a hook was checked/installed, rather than silently assuming a path\n * that might not be where git (or the repo's hook manager) actually looks. */\n resolveHooksDir(cwd: string): Promise<string>;\n readHookFile(cwd: string, hookName: string): Promise<string | undefined>;\n appendHookFile(cwd: string, hookName: string, content: string): Promise<void>;\n /** Wholesale-overwrites the hook file's content (unlike `appendHookFile`) — used for an\n * in-place legacy-hook upgrade (phase1-decision-integration.md §6c), where the caller has\n * already computed the full replacement content (old Docuvia block removed, new block\n * appended, any other user content preserved) and just needs it written back atomically. */\n writeHookFile(cwd: string, hookName: string, content: string): Promise<void>;\n makeHookExecutable(cwd: string, hookName: string): Promise<void>;\n\n listTrackedFilesWithBlobHash(cwd: string): Promise<Map<string, string>>;\n listUntrackedFiles(cwd: string): Promise<string[]>;\n listModifiedFiles(cwd: string): Promise<string[]>;\n readBlobContent(cwd: string, sha: string): Promise<string>;\n getRemoteUrl(cwd: string): Promise<string | undefined>;\n getRecentChangedFilePaths(\n cwd: string,\n maxCommits?: number,\n ): Promise<string[]>;\n hasUncommittedChanges(cwd: string): Promise<boolean>;\n /**\n * Every live worktree of the repo `cwd` belongs to (`git worktree list --porcelain`), *including*\n * the one `cwd` itself is — callers compare `path` to their own `workspaceRoot` to find siblings.\n * `[]` on a non-repo or a git version without `worktree list`. Used by `doctor`'s\n * worktree-divergence diagnostic (issue #137).\n */\n listWorktrees(cwd: string): Promise<WorktreeEntry[]>;\n /**\n * With no `toRef`: files changed relative to `baseRef`, diffed straight against the working\n * tree (not `<baseRef>...HEAD`) merged with untracked files — the original, commit-to-working-tree\n * semantics every pre-existing caller relies on. With `toRef` also given: a strict two-ref diff\n * (`git diff --name-status <baseRef> <toRef>`, no working-tree/untracked merging) — the\n * commit-to-commit semantics `analyze` auto mode's delta ingestion needs to diff\n * `lastIngestedSourceSha -> HEAD` (phase1-decision-integration.md §6b). Either way, renames\n * (`R###\\told\\tnew`) report the new path in `file`, with `oldFile` carrying the old one.\n */\n getChangedFilesSince(\n cwd: string,\n baseRef?: string,\n toRef?: string,\n ): Promise<ChangedFileEntry[]>;\n /**\n * 0-indexed line ranges (tree-sitter convention) touched by `fromRef -> toRef`'s diff of a\n * single file (`git diff --unified=0 <fromRef> <toRef> -- <filePath>`, parsed from unified-diff\n * hunk headers). Feeds `SemanticDiffDetector`'s classification pass\n * (phase1-decision-integration.md §6b) — approximate by design (context-free hunks, not a full\n * AST diff), and never the sole gate on whether a file gets re-parsed. `[]` if the file has no\n * textual diff between the two refs (or either ref/path doesn't exist).\n */\n getChangedLineRanges(\n cwd: string,\n fromRef: string,\n toRef: string,\n filePath: string,\n ): Promise<DiffLineRange[]>;\n getFilesChangedByCommit(cwd: string, sha: string): Promise<string[]>;\n /** Full 40-char sha of the current source commit (`git rev-parse HEAD`), or `undefined` on an unborn/headless HEAD (e.g. a freshly `git init`-ed repo with no commits yet). */\n getHeadSha(cwd: string): Promise<string | undefined>;\n /** Full 40-char sha of `branchName`'s current tip, or `undefined` if the branch doesn't exist yet. Used to parent the next `packDirectoryToBranch` commit on it (STOR-001 point 2). */\n getBranchTipSha(cwd: string, branchName: string): Promise<string | undefined>;\n /** File content at `ref:filePath` (`git show <ref>:<filePath>`), or `undefined` if the ref or path doesn't exist. Used by hydration (STOR-002) to read `graph/*.jsonl` off the knowledge branch without checking it out. */\n readFileAtRef(\n cwd: string,\n ref: string,\n filePath: string,\n ): Promise<string | undefined>;\n /**\n * Names of the files directly inside `dirPath` at `ref` (`git ls-tree --name-only <ref> --\n * <dirPath>/`, full repo-relative posix paths, not basenames), or `[]` if `ref`/`dirPath`\n * doesn't exist at that commit. Used by L3 card hydration (phase2-l3-distribution.md\n * L3DIST-007) to list `knowledge/_l3/` without checking the knowledge branch out — the\n * directory-listing counterpart to `readFileAtRef`'s single-file read.\n */\n listFilesAtRef(cwd: string, ref: string, dirPath: string): Promise<string[]>;\n /**\n * `ref`'s commit history (newest first), each with its full commit message body — raw, no\n * Docuvia-specific trailer parsing (that's `lib/core`'s job). `[]` if `ref` doesn't exist or has\n * no commits. `maxCount` bounds the walk (default 1000).\n */\n getCommitLog(\n cwd: string,\n ref: string,\n maxCount?: number,\n ): Promise<Array<{ sha: string; message: string }>>;\n /** Shas of `ref`'s ancestry (newest first, `ref` itself included). `[]` if `ref` doesn't exist or has no commits. `maxCount` bounds the walk (default 1000). */\n getCommitAncestry(\n cwd: string,\n ref: string,\n maxCount?: number,\n ): Promise<string[]>;\n /**\n * Packs every file under `sourceDir` onto `branchName` as a full-tree-replace commit\n * (`deleteall` + one `M 100644 inline <path>` per file) via `git fast-import`, parented on the\n * branch's current tip (via `getBranchTipSha`) when it already has one — a root commit only the\n * very first time the branch is created. Every import is therefore a fast-forward; no `--force`\n * is used (STOR-001 point 2 — \"continuous stacking\", never an unreachable, orphaned history).\n */\n packDirectoryToBranch(\n cwd: string,\n sourceDir: string,\n branchName: string,\n commitMessage: string,\n timestamp?: number,\n ): Promise<void>;\n\n /** `git fetch <remote> <ref>` — updates `refs/remotes/<remote>/<ref>` from the remote. Used for cross-clone reconciliation (STOR-001 point 3). Throws on network/remote failure — callers decide whether that's fatal. `timeoutMs` bounds the shell-out (`undefined` — the default — waits for the transfer to finish, however long that takes; config-tunable, see `GitLocalProvider`'s doc comment on why the old hardcoded bound was removed). */\n fetchRef(\n cwd: string,\n remote: string,\n ref: string,\n timeoutMs?: number,\n ): Promise<void>;\n /** `git push <remote> refs/heads/<branchName>:refs/heads/<branchName>` — an explicit refspec, since the knowledge branch is normally never checked out. `timeoutMs` bounds the shell-out (`undefined` — the default — waits for the push to finish, however long that takes; config-tunable, see `GitLocalProvider`'s doc comment). */\n pushRef(\n cwd: string,\n remote: string,\n branchName: string,\n timeoutMs?: number,\n ): Promise<void>;\n /** Full 40-char sha `ref` resolves to (`git rev-parse --verify --quiet <ref>`), or `undefined` if it doesn't exist. Unlike `getBranchTipSha`, `ref` may be any ref form (e.g. `refs/remotes/origin/docuvia-knowledge`), not just `refs/heads/<name>`. */\n getRefSha(cwd: string, ref: string): Promise<string | undefined>;\n /** `true` if `ancestorSha` is an ancestor of (or equal to) `descendantSha` (`git merge-base --is-ancestor`) — used to detect a plain fast-forward before falling back to a full merge. */\n isAncestor(\n cwd: string,\n ancestorSha: string,\n descendantSha: string,\n ): Promise<boolean>;\n /** The tree object sha `commitish` points at (`git rev-parse <commitish>^{tree}`) — the tree wholesale-adopted by a tree-adoption merge commit (STOR-001 point 3). */\n getTreeSha(cwd: string, commitish: string): Promise<string>;\n /** Unix seconds of `sha`'s committer timestamp (`git show -s --format=%ct`) — the wall-clock fallback when neither side of a divergence is a source-topological descendant of the other. */\n getCommitTimestamp(cwd: string, sha: string): Promise<number>;\n /**\n * `git commit-tree <treeSha> -p <parentShas[0]> -p <parentShas[1]> ... -m <message>` — creates a\n * merge commit whose tree is wholesale adopted from one side (a \"tree-adoption merge\", STOR-001\n * point 3), not a content-level merge of both sides. Uses the same synthetic `Docuvia\n * <docuvia@localhost>` committer identity as `packDirectoryToBranch`, not the local git config.\n */\n createMergeCommit(\n cwd: string,\n treeSha: string,\n parentShas: string[],\n message: string,\n ): Promise<string>;\n\n /**\n * Blocks until the advisory `.git/docuvia-knowledge.lock` file is exclusively created (retrying\n * with a timeout), so `snapshot`'s pack and cross-clone reconciliation's fetch/merge/push can\n * never race each other's `update-ref` (STOR-001). A lock file older than a staleness threshold\n * is assumed to belong to a crashed process and is stolen rather than waited out forever. Throws\n * if the wait times out without acquiring it.\n */\n acquireKnowledgeLock(cwd: string): Promise<void>;\n /** Releases the lock acquired by `acquireKnowledgeLock` (best-effort — a missing lock file is not an error). */\n releaseKnowledgeLock(cwd: string): Promise<void>;\n}\n","/**\n * D1 edge-resolution provider seam (phase1-decision-integration.md §8b; PLAT-007 Tier B) —\n * `escalateToLsp`'s real implementation sits behind this interface so a second provider (§8b\n * Provider 2: small-model/LLM compensation, Slice 4+) can be added later without touching the\n * Tier B batch orchestrator. Provider 1 (this slice): spawn-per-batch headless\n * `typescript-language-server`, resolved project-locally, never bundled with docuvia itself.\n *\n * Parallel semantics when more than one provider is enabled (not yet true in this slice, since\n * only Provider 1 exists): providers run in parallel and results merge by provenance — `lsp`\n * edges are authoritative, `llm-inferred` edges must carry a `confidence`, and LSP wins conflicts\n * on the same (sourceNodeKey, targetNodeKey) pair.\n */\nexport const EdgeResolutionSources = {\n LSP: \"lsp\",\n LLM_INFERRED: \"llm-inferred\",\n} as const;\nexport type EdgeResolutionSource =\n (typeof EdgeResolutionSources)[keyof typeof EdgeResolutionSources];\n\n/**\n * A single corrected cross-file `calls` edge, keyed by STOR-005 `node_key` — never a raw\n * `l2_nodes.id` — so the Tier B batch can resolve it against whichever ids are current at insert\n * time (phase1-decision-integration.md §8d's node_key re-attach strategy). The caller (Tier B\n * batch) is responsible for resolving both sides via `IGraphNodesRepo.findNodeIdByNodeKey` and\n * dropping the edge — never inventing a node — when either side doesn't resolve.\n */\nexport interface ResolvedCallEdge {\n sourceNodeKey: string;\n targetNodeKey: string;\n source: EdgeResolutionSource;\n /** Required when `source` is `llm-inferred` (§8b); absent/ignored for `lsp` edges, which are\n * authoritative by construction. */\n confidence?: number;\n}\n\n/** Result of a provider's pre-flight readiness check (phase1-decision-integration.md §8c's\n * environment gate + §8b's honest-degradation reason). */\nexport interface EdgeResolutionAvailability {\n available: boolean;\n /** Human-readable reason when `available` is `false` — surfaced in JSONL logs and (Slice 5)\n * `doctor`. Always present when `available` is `false`. */\n reason?: string;\n}\n\nexport interface EdgeResolutionFileFailure {\n file: string;\n reason: string;\n /** `false` marks a *permanent* per-file failure -- one the LSP server reported as definitively\n * unresolvable (e.g. \"no package metadata\", a file the language server can't load at all), so\n * re-queuing it on the next Tier B batch can only repeat the same empty retry. Absent/`true`\n * means retryable: the file's turn genuinely didn't complete (whole-batch timeout mid-file, or\n * never reached before the deadline), so it deserves another slot in the next batch. The Tier B\n * orchestrator drops `retryable: false` files from the re-queued `failedEntries` entirely\n * (they still get logged per-file and counted in the batch summary). */\n retryable?: boolean;\n}\n\n/**\n * Outcome of a `resolveEdges()` call. `unavailableReason` set means the provider could not run at\n * all (binary unresolvable, spawn/initialize failure, whole-batch timeout) — `edges` is then\n * always `[]` and every requested file effectively stayed at AST precision (honest degradation,\n * §8b: \"AST-level edges stay as they are\"). When `unavailableReason` is unset, the provider ran;\n * `filesProcessed` succeeded, `filesFailed` failed individually (§8g: kept in the Tier B queue for\n * the next batch) while the rest of the batch still proceeded.\n */\nexport interface EdgeResolutionOutcome {\n edges: ResolvedCallEdge[];\n filesProcessed: string[];\n filesFailed: EdgeResolutionFileFailure[];\n unavailableReason?: string;\n}\n\n/**\n * One Tier A AST call-site seed handed to the provider's forward pass (FWD-002). The resolver only\n * needs the call's *position* to issue `textDocument/definition` against the caller file; the callee\n * token text (`targetFunction`) is carried for logging/calibration but is never itself used to\n * resolve the target (that is LSP's job). The source symbol is *derived*, not carried, via\n * `findDeepestContainingSymbol` at the call position — the same containment gate the reverse pass\n * uses. Line/column are 0-based, matching both `LspPosition.line`/`character` and Tier A's\n * `node.startPosition`/`startColumn` seed (Slice 1).\n */\nexport interface EdgeResolutionCallSite {\n targetFunction: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface EdgeResolutionRequest {\n workspaceRoot: string;\n /** Workspace-relative paths, already language-dispatched (§8e) to this provider's supported\n * language(s) -- the provider itself never re-checks language support. */\n files: string[];\n /** Optional per-file Tier A AST call-site seeds that switch the provider onto the *forward*\n * resolution path for the files they cover (FWD-01/002): each call site's callee is resolved\n * directly with `textDocument/definition` instead of a project-wide reverse-`references` scan.\n * Files absent from this map -- and files mapped to an empty array -- keep the reverse path as\n * today (FWD-01: reverse stays as the fallback). As of Slice 2 no orchestrator producer wires\n * this in, so production behavior is unchanged; it is the unit-tested seam the Flip (Slice 3)\n * feeds call sites through. */\n callsByFile?: Record<string, EdgeResolutionCallSite[]>;\n /** PRJ-002: the directory the LSP server should be initialized against (its `cwd`, `rootUri`\n * and workspace folder). When set, it must be a project root (or the workspace root) -- the\n * sharding driver points each shard at its owning project's root so the server loads only that\n * project plus its path deps, instead of the whole workspace. `files` stay relative to\n * `workspaceRoot` regardless (they're always read from `workspaceRoot`); this field only\n * controls where the server is spawned and what it loads. Absent/unset means\n * `workspaceRoot` (today's whole-workspace behavior). */\n serverRoot?: string;\n}\n\n/** Construction-time overrides (phase1-decision-integration.md §8b: \"config-overridable; never\n * bundled\"). All optional — a provider with no overrides resolves its binary via its own default\n * strategy (`node_modules/.bin` -> `npx --no-install`, per §8b). */\nexport interface EdgeResolutionProviderConfig {\n /** Absolute path (or bare command resolvable on PATH) to the LSP server binary. Overrides the\n * provider's default resolution strategy entirely — used both for real user overrides and for\n * pointing tests at a fixture server. */\n binaryOverride?: string;\n /** Args passed to `binaryOverride`/the resolved binary (e.g. `[\"--stdio\"]`). */\n argsOverride?: string[];\n /** Whole-batch timeout in milliseconds (spawn through shutdown), and — `BaseLspEdgeProvider`\n * specifically — the per-request timeout too (same knob, not two independent ones; see\n * `BaseLspEdgeProvider.requestTimeoutMs`). Generous by default per §8h's \"tentative, function\n * first\" ruling. `0` means \"never time out\": some servers (csharp-ls on a large Roslyn/MSBuild\n * solution) have no known upper bound on how long a first response can take. */\n timeoutMs?: number;\n /** Hard cap on how many files' documents `BaseLspEdgeProvider` will hold open in the LSP\n * server at once during a batch (LRU-evicted beyond this). Untuned — a round number chosen to\n * comfortably cover normal reference fan-out without ever handing a huge multi-project-\n * reference workspace (e.g. vscode-scale) an unbounded number of simultaneously-open\n * documents; re-tune if a real workload shows it's off. See `BaseLspEdgeProvider`'s\n * `openAndGetSymbols` for the eviction mechanics. */\n maxOpenFiles?: number;\n /** Bounded cross-file worker in BaseLspEdgeProvider's batch pipeline (Tier B K-way\n * concurrency plan): how many files' own processing turns run in flight at once. `1`\n * (default) reproduces today's strictly-serial behavior exactly -- this field is a pure\n * throughput knob, never a correctness one (every K must produce identical edges/node_keys;\n * see BaseLspEdgeProvider's K-invariance test). Clamped at runtime to\n * `min(configured, files.length, maxOpenFiles - 1)` -- see BaseLspEdgeProvider's\n * effectiveConcurrency(). */\n maxConcurrentFiles?: number;\n /** How many *independent LSP server processes* to shard the batch across (Tier B multi-process\n * sharding plan). Each shard spawns its own `LspJsonRpcClient`/server process and resolves a\n * disjoint slice of `request.files`; outcomes are merged back into one `EdgeResolutionOutcome`.\n * This is the throughput lever that actually sidesteps a single server process's internal\n * serial compute (e.g. tsserver), which client-side K-way concurrency (`maxConcurrentFiles`)\n * only overlaps IPC latency for. `1` (default) reproduces today's single-process batch exactly.\n * A pure throughput knob, never a correctness one -- sharding must yield identical\n * edges/filesProcessed/filesFailed as a single process (see BaseLspEdgeProvider's\n * process-invariance test). Memory scales linearly with `maxProcesses` (each server holds its\n * own process program), so it must be bounded by repo size, not just cores. Clamped at runtime\n * to `min(configured, files.length, floor(maxProcessesMemoryMb / processMemoryEstimateMb))`. */\n maxProcesses?: number;\n /** Total memory budget (in MiB) the entire batch's LSP server processes may occupy at once,\n * across all `maxProcesses` shards. Used to bound `maxProcesses` against memory, not just cores\n * and file count -- the crash the issue #11 multi-process sharding addressed was oversized\n * shard counts (each server holds its own full process program up to the language's heap cap,\n * `DEFAULT_TS_MAX_OLD_SPACE_SIZE_MB`), so \"more processes\" is not free: it compounds as the\n * configured heap ceiling per process. When unset, the provider falls back to the current\n * machine's free memory. Effective process count is clamped additionally to\n * `floor(maxProcessesMemoryMb / processMemoryEstimateMb)`. */\n maxProcessMemoryMb?: number;\n /** Estimate of one shard process's steady-state memory footprint (MiB), used only to derive\n * `maxProcesses`'s memory upper bound (`floor(maxProcessesMemoryMb / this)` when the caller\n * sets a memory budget; otherwise a sane default). A pure throughput-guard heuristic, never a\n * correctness input. */\n processMemoryEstimateMb?: number;\n /** Cold-start settle for the LSP server process, in milliseconds (0 = none): after the\n * `initialize`/`initialized` handshake completes and *before* the first semantic request\n * (`textDocument/documentSymbol` is syntactic and answers correctly immediately, but\n * `textDocument/references`/`textDocument/definition` issued before the server's own async\n * project/crate-graph load finishes can come back empty even though the same request succeeds\n * moments later). Verified live against rust-analyzer 1.97.1 on ripgrep: `Searcher::new`\n * returned 0 references in 0 ms cold, then 2 references after an ~8s settle — the root cause\n * of Tier B's 0-corrected-edges on both ripgrep and tauri even after the GRPH-006 key fix.\n * A batch-scoped, per-spawned-server cost (paid once per shard), never per file. When set, this\n * value wins over the provider's per-language default (`LspLanguageConfig.coldStartSettleMs`);\n * callers set `0` to force-disable the wait. */\n coldStartSettleMs?: number;\n /** [PRJ-007] How often (ms) the provider re-probes server readiness after the fixed\n * `coldStartSettleMs` expires, when a sharded batch's LSP server has not yet reported a\n * non-empty `textDocument/references` (parallel cold servers load big workspaces slower than\n * the fixed settle alone can cover). Default 5000. */\n coldStartPollMs?: number;\n /** [PRJ-007] Hard cap (ms) on the total readiness-poll wait (fixed settle + polls). A broken\n * server that never returns references must not wedge the batch; once this trips the batch\n * proceeds (and will degrade honestly via its normal per-file paths). Default 120000. */\n coldStartMaxWaitMs?: number;\n}\n\nexport interface IEdgeResolutionProvider {\n readonly name: string;\n /** Mirrors `ILlmClient.initialize()`'s shape — per-run config injected by the Orchestration\n * layer, never read from `process.env` inside the provider itself. Optional: a provider used\n * with no overrides may skip this call entirely. */\n configure(config: EdgeResolutionProviderConfig): void;\n /** Environment/pre-flight readiness (§8c's mandatory gate for `init`/manual invocations; also\n * the first honest-degradation check §8b requires before ever attempting to spawn). Never\n * throws — a check that itself fails is reported as `available: false`. */\n checkAvailability(workspaceRoot: string): Promise<EdgeResolutionAvailability>;\n /** Resolves cross-file edges over `request.files`. Never throws for an ordinary\n * unavailable/timeout/per-file-failure outcome (those are reported in the returned\n * `EdgeResolutionOutcome`, per §8b's honest-degradation rule) — only an unexpected\n * programming error should reject. */\n resolveEdges(request: EdgeResolutionRequest): Promise<EdgeResolutionOutcome>;\n}\n\n/**\n * Tier B's per-language vocabulary (multi-language-lsp-support plan, Finding A) — the registry\n * key type shared by `TOKENS.EdgeResolutionProviders` (`tokens.ts`) and the Tier B dispatch table\n * (`lib/ui-core/src/workflows/analyze/tier-b-language-dispatch.ts`), so both sides speak the same\n * vocabulary. Only `TYPESCRIPT` has a shipped provider as of this slice; the rest are reserved for\n * their own later slices (see the plan's §3 language-by-language table) and are not yet wired to\n * any dispatch entry or registry key.\n */\nexport const TIER_B_LANGUAGE_IDS = {\n TYPESCRIPT: \"typescript\",\n PYTHON: \"python\",\n GO: \"go\",\n RUST: \"rust\",\n JAVA: \"java\",\n CPP: \"cpp\",\n CSHARP: \"csharp\",\n PHP: \"php\",\n RUBY: \"ruby\",\n} as const;\nexport type TierBLanguageId =\n (typeof TIER_B_LANGUAGE_IDS)[keyof typeof TIER_B_LANGUAGE_IDS];\n","export const KnowledgeBranchSyncStatuses = {\n NO_REMOTE: \"no-remote\",\n UP_TO_DATE: \"up-to-date\",\n FAST_FORWARDED_LOCAL: \"fast-forwarded-local\",\n PUSHED_LOCAL: \"pushed-local\",\n MERGED: \"merged\",\n} as const;\nexport type KnowledgeBranchSyncStatus =\n (typeof KnowledgeBranchSyncStatuses)[keyof typeof KnowledgeBranchSyncStatuses];\n\n/** Outcome of `IKnowledgeGitService.syncKnowledgeBranch()` (STOR-001 point 3). */\nexport interface KnowledgeBranchSyncResult {\n /**\n * `no-remote`: no `origin` configured, or the fetch failed (offline) — purely local, not an\n * error. `up-to-date`: local and remote already match. `fast-forwarded-local`: local moved to\n * match a strictly-ahead remote (or adopted the remote wholesale when no local copy existed).\n * `pushed-local`: local was strictly ahead (or the remote had no copy yet) and was pushed.\n * `merged`: local and remote had genuinely diverged; a tree-adoption merge commit was created\n * and pushed.\n */\n status: KnowledgeBranchSyncStatus;\n /** The branch's resulting tip sha. Undefined only for `no-remote`. */\n branchTipSha?: string;\n}\n\n/**\n * Docuvia-specific git behavior — implemented by `lib/core/git` on top of `IGitProvider`.\n * This is the \"generating knowledge branches\" example named directly in\n * docs/gitbook/architecture/virtual-contracts-architecture.md's Domain Core section.\n */\nexport interface IKnowledgeGitService {\n ensureKnowledgeBranch(\n cwd: string,\n branchName?: string,\n ): Promise<{ created: boolean }>;\n installPostCommitHook(cwd: string): Promise<{ installed: boolean }>;\n /**\n * Installs the pre-push hook that fires the Tier B batch (`docuvia analyze --escalate-to-lsp\n * && docuvia snapshot`, phase1-decision-integration.md §8h) — the \"share code, share knowledge\"\n * trigger PLAT-007 settles on for Phase 1 (no idle timer). Mirrors\n * `installPostCommitHook`'s marker + lock + legacy-upgrade shape exactly (no legacy hook exists\n * for `pre-push` yet, so there is no upgrade branch — only fresh-install and\n * already-installed). Non-fatal by design, same reasoning as `installPostCommitHook`.\n */\n installPrePushHook(cwd: string): Promise<{ installed: boolean }>;\n /**\n * Removes the Docuvia post-commit hook block (both the current and, if also present, the\n * legacy pre-Slice-2b content) from `.git/hooks/post-commit`, preserving any non-Docuvia\n * content in the same file byte-for-byte (phase1-decision-integration.md §10a) — `uninstall`'s\n * symmetric counterpart to `installPostCommitHook`. A hook file that's absent or doesn't carry\n * either marker is a clean no-op (`{ removed: false }`). Never throws — non-fatal by the same\n * convention as the install methods.\n */\n removePostCommitHook(cwd: string): Promise<{ removed: boolean }>;\n /**\n * Removes the Docuvia pre-push hook block from `.git/hooks/pre-push`, preserving any\n * non-Docuvia content in the same file byte-for-byte (phase1-decision-integration.md §10a) —\n * `uninstall`'s symmetric counterpart to `installPrePushHook`. A hook file that's absent or\n * doesn't carry the marker is a clean no-op (`{ removed: false }`). Never throws.\n */\n removePrePushHook(cwd: string): Promise<{ removed: boolean }>;\n /**\n * `uninstall`'s teardown of the hidden knowledge branch itself — the orphan branch\n * `ensureKnowledgeBranch` creates. `{ deleted: false }` (never throws) when the branch doesn't\n * exist, matching every other uninstall step's non-fatal, idempotent shape.\n */\n deleteKnowledgeBranch(\n cwd: string,\n branchName?: string,\n ): Promise<{ deleted: boolean }>;\n /**\n * `doctor --fix`'s explicit, opt-in repair of the legacy-hook duplicate-block case\n * (phase1-decision-integration.md §10d): re-checks (TOCTOU-safe) that `.git/hooks/post-commit`\n * still carries both the current and legacy Docuvia blocks, strips every Docuvia-authored block\n * via a marker-bounded extraction (robust to minor hand-edits, unlike `installPostCommitHook`'s\n * exact-content-match upgrade path), and appends exactly one canonical\n * `POST_COMMIT_HOOK_CONTENT` block. Never silently mutates a healthy hook — `{ repaired: false }`\n * when the re-check finds nothing to repair. Never throws.\n */\n repairDuplicatePostCommitHook(cwd: string): Promise<{ repaired: boolean }>;\n /** Packs a rendered snapshot directory (see `ISnapshotRenderer`) onto the hidden knowledge branch, wholesale replacing its tree. */\n packSnapshotToKnowledgeBranch(\n cwd: string,\n sourceDir: string,\n branchName?: string,\n ): Promise<void>;\n /**\n * Cross-clone reconciliation (STOR-001 point 3): fetches `origin`'s copy of the knowledge\n * branch and reconciles it with the local one — a plain fast-forward in either direction when\n * possible, or a tree-adoption merge (winner = the side whose stamped source commit is a\n * descendant of the other's, falling back to committer timestamp) when they've genuinely\n * diverged. A no-op (`status: \"no-remote\"`) when there's no `origin`, or the fetch fails\n * (offline) — this never fails a caller's `snapshot`/`hydrate` over a network hiccup.\n */\n syncKnowledgeBranch(\n cwd: string,\n branchName?: string,\n remote?: string,\n ): Promise<KnowledgeBranchSyncResult>;\n /**\n * The `Docuvia-Source` trailer sha stamped on the knowledge branch's most recent commit that\n * carries one (STOR-001 point 4) — `analyze` auto mode's delta-baseline fallback for pre-Slice-2\n * workspaces where `docuvia_meta`'s `lastIngestedSourceSha` key hasn't been written yet\n * (phase1-decision-integration.md §6a's fallback order). Undefined if the branch doesn't exist\n * or none of its commits carry the trailer.\n */\n resolveNewestSourceTrailerSha(\n cwd: string,\n branchName?: string,\n ): Promise<string | undefined>;\n /**\n * Scans the knowledge branch's history to check if a specific source commit sha has already\n * been snapshotted (has a corresponding commit with a matching `Docuvia-Source` trailer).\n */\n hasSourceCommitInHistory?(\n cwd: string,\n sourceSha: string,\n branchName?: string,\n ): Promise<boolean>;\n /**\n * Runs `fn` while holding the knowledge-branch lock (the same advisory\n * `.git/docuvia-knowledge.lock` `packSnapshotToKnowledgeBranch`/`syncKnowledgeBranch` use,\n * STOR-001/PLAT-006) — `analyze` auto mode's delta persist step takes this lock for its\n * local.db writes (phase1-decision-integration.md §6b's locking requirement; PLAT-007's\n * reliability section), even though it doesn't itself mutate the knowledge branch ref, so it\n * can't race a concurrent `snapshot`'s read-then-pack of the same local.db. Exposed as a\n * generic method (rather than requiring `lib/ui-core` to import `lib/core`'s\n * `withKnowledgeBranchLock` helper directly) to keep the Orchestration layer resolving\n * everything by token, per the Virtual Contracts architecture.\n */\n runUnderKnowledgeLock<T>(cwd: string, fn: () => Promise<T>): Promise<T>;\n}\n","/**\n * Row shapes for the local SQLite schema (see `lib/schema`'s migrations). Defined here, not in\n * `lib/schema`, per the Virtual Contracts \"Mandatory Mapping\" rule — `lib/schema` must map its\n * raw driver output onto these before returning from any repo method; nothing above this layer\n * may depend on `lib/schema` types directly. Numeric booleans (SQLite has no native boolean\n * type) are typed as `0 | 1`, matching what `better-sqlite3` hands back without an explicit\n * conversion layer.\n */\nexport const ProjectStatuses = {\n ACTIVE: \"active\",\n ARCHIVED: \"archived\",\n} as const;\nexport type ProjectStatus =\n (typeof ProjectStatuses)[keyof typeof ProjectStatuses];\n\nexport interface ProjectRow {\n id: number;\n name: string;\n repo_url: string;\n description: string | null;\n status: ProjectStatus;\n vcs_type: string;\n svn_url: string | null;\n last_git_ingested_at: string | null;\n last_svn_revision: number | null;\n last_ast_ingested_at: string | null;\n owner_id: number;\n created_at: string;\n updated_at: string;\n}\n\nexport interface ProjectFileRow {\n id: number;\n project_id: number;\n file_path: string;\n content_hash: string | null;\n last_parsed_at: string | null;\n created_at: string;\n /** Tier B analogue of `last_parsed_at` (Tier A) — null means this file has never had its\n * outgoing `calls` edges (re)computed by a Tier B batch. See `0006_tier_b_file_status.sql`. */\n last_tier_b_processed_at: string | null;\n /** HEAD sha at the time this file was last Tier B-processed — null when\n * `last_tier_b_processed_at` is also null, or when the batch ran on an unborn/headless HEAD. */\n last_tier_b_commit_sha: string | null;\n}\n\nexport interface L1TagRow {\n id: number;\n name: string;\n slug: string;\n category: string;\n is_anchored: 0 | 1;\n usage_count: number;\n description: string | null;\n created_at: string;\n}\n\nexport const L2NodeTypes = {\n MODULE: \"module\",\n PACKAGE: \"package\",\n PCD: \"pcd\",\n} as const;\nexport type L2NodeType = (typeof L2NodeTypes)[keyof typeof L2NodeTypes];\n\nexport interface L2NodeRow {\n id: number;\n project_id: number;\n name: string;\n type: L2NodeType;\n is_system: 0 | 1;\n description: string | null;\n ai_generated: 0 | 1;\n needs_review: 0 | 1;\n created_at: string;\n last_verified_at: string | null;\n path_patterns: string | null;\n reindex_required: 0 | 1;\n is_bootstrap_confirmed: 0 | 1;\n content_hash: string | null;\n updated_at: string;\n /** Deterministic `<file_path>` / `<file_path>#<symbolName>` identity (STOR-005). Null on rows inserted before this column existed. */\n node_key: string | null;\n}\n\nexport const LinkTypes = {\n CONTAINS: \"contains\",\n CALLS: \"calls\",\n IMPLEMENTS: \"implements\",\n EXTENDS: \"extends\",\n IMPORTS: \"imports\",\n DEPENDS_ON: \"depends_on\",\n DECISION: \"decision\",\n} as const;\nexport type LinkType = (typeof LinkTypes)[keyof typeof LinkTypes];\n\nexport interface NodeLinkRow {\n id: number;\n source_node_id: number;\n target_node_id: number;\n link_type: LinkType;\n commit_sha: string | null;\n diff_summary: string | null;\n created_at: string;\n}\n\nexport interface L2NodeL1TagRow {\n l2_node_id: number;\n l1_tag_id: number;\n created_at: string;\n}\n\nexport const L3NodeTypes = {\n CHANGE: \"change\",\n RULE: \"rule\",\n DECISION: \"decision\",\n CONTEXT: \"context\",\n} as const;\nexport type L3NodeType = (typeof L3NodeTypes)[keyof typeof L3NodeTypes];\n\nexport const ValidityStatuses = {\n PENDING: \"pending\",\n ACTIVE: \"active\",\n DRAFT: \"draft\",\n GARBAGE: \"garbage\",\n} as const;\nexport type ValidityStatus =\n (typeof ValidityStatuses)[keyof typeof ValidityStatuses];\n\n/** `l3_nodes.source` values. `ANALYZE` = the existing LLM decision-extraction pipeline\n * (`analyze <targetPath>`, no --agent-authored). `AGENT_AUTHORED` = an AI coding agent's own\n * structured decision, written verbatim (no LLM call) via `analyze <targetPath> --agent-authored`\n * (roadmap items 32-34, issue #42). `IMPORT` mirrors L3NodesRepo's private `L3_IMPORT_SOURCE`\n * ('git-import') -- included here so every source value used in application code has one home;\n * `L3NodesRepo.importCard` continues to hardcode its own literal for now (out of scope -- see\n * the note below), but new code should reference this const, not a fresh string literal. */\nexport const L3DecisionSources = {\n ANALYZE: \"analyze\",\n AGENT_AUTHORED: \"agent-authored\",\n} as const;\nexport type L3DecisionSource =\n (typeof L3DecisionSources)[keyof typeof L3DecisionSources];\n\nexport interface L3NodeRow {\n id: number;\n l2_node_id: number;\n title: string;\n content: string | null;\n node_type: L3NodeType;\n source_commits: string;\n commit_hash: string | null;\n ai_generated: 0 | 1;\n confidence: number | null;\n noise_score: number | null;\n created_at: string;\n last_verified_at: string | null;\n occurrence_count: number;\n introduced_in_commit: string | null;\n verified_until_commit: string | null;\n validity_status: ValidityStatus;\n source: string;\n content_hash: string | null;\n /** LLM model id used for extraction (e.g. `gpt-4o-mini`) — null on rows inserted before this column existed, or when the extraction path never set it. */\n extraction_model: string | null;\n /** JSON array of workspace-relative source file paths the decision was extracted from — null on rows inserted before this column existed. */\n source_files: string | null;\n /**\n * JSON array snapshot of `source_commits` as it stood the moment this row was first inserted —\n * frozen forever afterwards, never touched by `upsertDecision`'s later occurrence-bump path\n * (L3DIST-002/003, phase2-l3-distribution). This is what `snapshot`'s L3 card renderer packs as\n * the card's `source_commits` field, so a card's content stays byte-identical run-over-run even\n * as the local row's own (mutable) `source_commits` keeps growing with every re-analysis —\n * true git-object idempotency (L3DIST edge case 5b). Null on rows inserted before this column\n * existed; callers fall back to `source_commits` in that case.\n */\n initial_source_commits: string | null;\n}\n\n/**\n * Small key/value store (`docuvia_meta` table) — currently used to remember the knowledge-branch\n * tip sha `local.db` was last hydrated from (STOR-002), so read commands can cheaply detect\n * staleness without re-parsing JSONL on every call.\n */\nexport interface IMetaRepo {\n get(key: string): string | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface IProjectsRepo {\n getFirst(): ProjectRow | undefined;\n insert(input: { name: string; repoUrl: string }): ProjectRow;\n /**\n * Atomic get-or-insert: returns the existing project row if one exists, otherwise inserts\n * `input` and returns the new row — all inside one write-locked transaction, so two processes\n * racing `docuvia init` on a fresh workspace can't both observe \"no project yet\" and both\n * insert (see seed-project-row.ts).\n */\n getOrInsert(input: { name: string; repoUrl: string }): ProjectRow;\n /** Row count of the `projects` table — used by `status`. */\n count(): number;\n}\n\nexport interface IProjectFilesRepo {\n getAllHashes(): Array<{ filePath: string; contentHash: string | null }>;\n upsertFile(input: {\n projectId: number;\n filePath: string;\n contentHash: string | null;\n }): void;\n /**\n * Stamps `last_tier_b_processed_at`/`last_tier_b_commit_sha` for a file whose calls-edges Tier\n * B just (re)computed — called once per file in `outcome.filesProcessed` right after\n * `applyResolvedEdges` durably inserts that batch's edges (not staged/gated on a later\n * `snapshot`, since the edges themselves already aren't staged either). Upserts defensively (a\n * matching `project_files` row should already exist from Tier A parsing every file Tier B ever\n * queues, but this must not silently no-op if one is somehow missing).\n */\n markTierBProcessed(input: {\n projectId: number;\n filePath: string;\n commitSha: string | null;\n }): void;\n /**\n * Tier B coverage for a single file — `query`/`impact`'s \"does this node's own file's\n * outgoing-edge set look complete\" check. `undefined` when the file has no `project_files` row\n * at all (never parsed by Tier A, so Tier B could never have queued it either).\n */\n getTierBFileStatus(\n filePath: string,\n ):\n | { lastProcessedAt: string | null; lastProcessedCommitSha: string | null }\n | undefined;\n /**\n * Workspace-wide Tier B coverage — `query`/`impact`'s \"could an unqueued file still turn out to\n * be a caller\" check. Cheap aggregate (two `COUNT(*)`-shaped reads), no row materialization —\n * safe to call on every `query`/`impact` invocation, even against a 100k+-file graph.\n */\n getTierBCoverage(): { totalFiles: number; processedFiles: number };\n}\n\nexport interface ITagsRepo {\n upsertTag(name: string): void;\n getIdByName(name: string): number | undefined;\n linkNodeToTag(l2NodeId: number, l1TagId: number): void;\n /**\n * Every (l2NodeId, tagName) pairing across the whole project — used by `export-topology` to\n * attach tag metadata onto file nodes (mirrors old Docuvia's `l2_node_l1_tags`/`l1_tags` join).\n */\n getAllTagLinks(): Array<{ l2NodeId: number; name: string }>;\n}\n\n/** One `l2_nodes` row plus its child `l3_nodes` rows — the shape `sync` needs to decide what to push. */\nexport interface L2NodeWithL3Children {\n l2Node: L2NodeRow;\n l3Nodes: L3NodeRow[];\n}\n\nexport interface IGraphNodesRepo {\n deleteNodesForPath(filePath: string): number[];\n insertNode(input: {\n projectId: number;\n name: string;\n type?: string;\n description?: string;\n pathPatterns: string[];\n /**\n * Deterministic export identity (STOR-005) — `<file_path>` for file nodes,\n * `<file_path>#<symbolName>` for function/class nodes. Optional: when omitted, `GraphNodesRepo`\n * derives it from `pathPatterns[0]`/`name` using the same convention, so callers that don't\n * care about the exported id (most tests) don't need to compute it themselves.\n */\n nodeKey?: string;\n /** Feature hash of the node's own content (STOR-005) — the file's own hash for file nodes, a hash of the symbol's exact source span for function/class nodes. */\n contentHash?: string;\n }): number;\n insertLink(input: {\n sourceNodeId: number;\n targetNodeId: number;\n linkType: string;\n }): void;\n findNodeIdByName(filePath: string, name: string): number | undefined;\n /** Row counts of `l2_nodes`/`l3_nodes` — used by `status`. */\n count(): { l2Nodes: number; l3Nodes: number };\n /**\n * l2_nodes whose `path_patterns` intersects `changedFiles`, each paired with its l3_nodes —\n * used by `sync` to find locally-generated decisions to push for a changed-file set (mirrors\n * old Docuvia's `SyncService.readLocalCandidates`).\n */\n findNodesForChangedFiles(changedFiles: string[]): L2NodeWithL3Children[];\n /**\n * Resolves a node by name for `query`/`impact`/`review`'s blast-radius lookups: exact match\n * first, falling back to a `LIKE %target%` match (mirrors old Docuvia's\n * `QueryService.findNodeByName`). Undefined when nothing matches either way. `filePath` is the\n * first `path_patterns` entry (undefined for a row with none) — `query`'s only consumer of it,\n * since an empty `<l2_module>` block with no file/kind context was otherwise indistinguishable\n * from a genuinely-empty result.\n */\n findNodeByName(\n target: string,\n ): { id: number; name: string; type: string; filePath?: string } | undefined;\n /**\n * Resolves an l2_node's id by its exact STOR-005 `node_key` (deterministic `<file_path>` /\n * `<file_path>#<symbolName>` identity) — used by `analyze <targetPath>`'s decision-extraction\n * anchor resolution (phase1-decision-integration.md §3b). Undefined if no row has that\n * `node_key` (e.g. a pre-STOR-005 row, or the path/symbol was never ingested).\n */\n findNodeIdByNodeKey(nodeKey: string): number | undefined;\n /**\n * Nodes with an outgoing `node_links` edge INTO `nodeId` — i.e. things that depend on/call it\n * (the 1-hop \"blast radius\"). Mirrors old Docuvia's `QueryService.queryIncomingEdges`. `type` is\n * the neighbor node's own kind (currently always `\"module\"` — every symbol/file row shares one\n * `L2NodeType`, see `persist-ast-graph.ts`); `linkType` is the actual relationship\n * (`calls`/`implements`/`extends`/`contains`/...) — the two are easy to conflate but distinct.\n * `impact`'s blast radius intentionally includes every link type here, `contains` included\n * (IMPT-001's documented single-hop heuristic); `query`'s `getContext()` is the one place that\n * filters `contains` out, since a symbol's own containing file isn't a \"caller\".\n */\n /**\n * Issue #135: L2 semantic coverage — how many `l2_nodes` rows carry a non-empty `description`.\n * One cheap aggregate (two `COUNT(*)`-shaped reads, no row materialization), safe to call on\n * every `doctor` invocation even against a 100k+-node graph. Tier C (the LLM enrichment pass)\n * is the writer of these descriptions; a graph where nothing is ever described is structurally\n * correct but semantically empty — exactly the \"query returns empty context\" failure mode\n * issue #135 documents.\n */\n getSemanticCoverage(): { totalNodes: number; describedNodes: number };\n getIncomingEdges(\n nodeId: number,\n ): Array<{ id: number; name: string; type: string }>;\n /** Nodes `nodeId` links out to. See `getIncomingEdges()`'s doc comment on the `DISTINCT`. */\n getOutgoingEdges(\n nodeId: number,\n ): Array<{ id: number; name: string; type: string }>;\n /**\n * `query`'s own incoming-edges lookup — same join as `getIncomingEdges()`, but per-relationship\n * rather than per-neighbor: `linkType` (`calls`/`implements`/`extends`/`contains`/...) is\n * included, and a neighbor connected by two different relationship types produces two rows\n * instead of being collapsed into one (`getIncomingEdges()`'s `DISTINCT` behavior — relied on by\n * `impact`'s blast-radius *count*, IMPT-001 — must not change). Still deduped on the full\n * (neighbor, linkType) tuple, so the exact same edge row twice is one row, not two.\n */\n getIncomingRelations(\n nodeId: number,\n ): Array<{ id: number; name: string; type: string; linkType: string }>;\n /** Nodes `nodeId` links out to, with `linkType`. See `getIncomingRelations()`'s doc comment. */\n getOutgoingRelations(\n nodeId: number,\n ): Array<{ id: number; name: string; type: string; linkType: string }>;\n /** Every `l2_nodes` row — used by `export-topology`. */\n getAllNodes(): L2NodeRow[];\n /** Every `node_links` row — used by `export-topology`. */\n getAllLinks(): NodeLinkRow[];\n /**\n * Rebuild-not-upsert bulk load (STOR-002 hydration): wipes `l2_nodes`/`node_links`/\n * `l2_node_l1_tags` and re-inserts `nodes`/`edges` inside a single transaction with prepared\n * statements (no ORM, no autocommit loop — the exact failure mode STOR-002 exists to prevent).\n * `nodes[].nodeKey` is the git-exported identity (STOR-005); `edges[].source`/`target`\n * reference it, not a rowid. An edge whose source/target key isn't among `nodes` is dropped\n * rather than inserted with a dangling reference (referential-integrity repair — STOR-002).\n */\n bulkLoadGraph(input: {\n projectId: number;\n nodes: Array<{ nodeKey: string; name: string; filePath?: string }>;\n edges: Array<{ source: string; target: string; type: string }>;\n }): { nodesLoaded: number; edgesLoaded: number; edgesDropped: number };\n /**\n * Deletes `node_links` rows whose `source_node_id` or `target_node_id` no longer references an\n * existing `l2_nodes` row — hygiene for the dangling rows `deleteNodesForPath` leaves behind\n * (it only deletes a deleted node's *outgoing* links; a still-live node's *incoming* link into\n * the now-gone id is left pointing nowhere). This is the \"repair\" half of the Tier B batch's\n * incoming-edge fix (phase1-decision-integration.md §8d, PLAT-007 Tier B): stale rows are\n * pruned here, and correct replacements are re-derived by the LSP reference pass, keyed fresh\n * by `node_key` (`findNodeIdByNodeKey`) rather than recovered from the pruned rows themselves.\n * Returns the number of rows removed.\n */\n pruneOrphanedLinks(): number;\n /**\n * Drops `l2_nodes_fts`'s sync triggers, runs `fn`, rebuilds the FTS5 index once, and recreates\n * the triggers — see `GraphNodesRepo`'s implementation doc comment. Callers that call\n * `insertNode`/`deleteNodesForPath` many times in a loop (rather than as one bulk array, which\n * `bulkLoadGraph` already handles internally) MUST wrap that loop in this, or per-row FTS5\n * tokenization dominates cost at 100k+ nodes.\n */\n withFtsSyncSuspended<T>(fn: () => T): T;\n}\n\nexport interface IL3NodesRepo {\n getById(id: number): L3NodeRow | undefined;\n /**\n * Every `l3_nodes` row excluding stale/superseded decisions (`validity_status = 'garbage'`) —\n * used by `export-topology` (mirrors old Docuvia's `TopologyExportService.isExportableStatus`).\n */\n getAllExportable(): L3NodeRow[];\n /**\n * `l3_nodes` rows for a single `l2_node_id` — the \"why\" data behind one blast-radius/changed-\n * file node, used by `review`/`impact` to surface L3 decisions/context alongside the \"what\n * changed\" node list (roadmap item \"Surface L3 'why' data in review/impact output\").\n */\n getByL2NodeId(l2NodeId: number): L3NodeRow[];\n /**\n * Content-hash upsert for `analyze <targetPath>`'s LLM decision-extraction pipeline\n * (phase1-decision-integration.md §3c; PLAT-007 Tier C point 1), also used by the\n * `--agent-authored` write path (issue #42). `content_hash` = sha256 over\n * `nodeType + \"\\n\" + title + \"\\n\" + content`. When a row with the same `content_hash` already\n * exists for `projectId` (joined via `l2_nodes.project_id` — `l3_nodes` has no `project_id`\n * column of its own): bumps `occurrence_count`, refreshes `last_verified_at`, and appends\n * `commitSha` to `source_commits` if not already present — no duplicate row is inserted, and\n * `source` is left untouched (the first writer's `source` always wins, even across a later\n * call with a different `source`). Otherwise inserts a new row with `commit_hash` =\n * `commitSha`, `source_commits` = `[commitSha]`, `source` = `input.source ??\n * L3DecisionSources.ANALYZE`, `ai_generated` = 1, `validity_status` left at its column default\n * (`'pending'`).\n */\n upsertDecision(input: {\n projectId: number;\n l2NodeId: number;\n title: string;\n content: string;\n nodeType: string;\n confidence: number;\n /** HEAD sha at extraction time, or `null` on an unborn/headless HEAD (no commits yet). */\n commitSha: string | null;\n extractionModel: string | null;\n /** Workspace-relative source file paths the decision was extracted from. */\n sourceFiles: string[];\n /** `l3_nodes.source` to stamp on a fresh insert (ignored on the dedup/occurrence-bump path --\n * an existing row keeps its original `source`, never overwritten by a later call with a\n * different one). Defaults to `L3DecisionSources.ANALYZE` when omitted, preserving every\n * existing caller's behavior unchanged. */\n source?: L3DecisionSource;\n }): { id: number; deduped: boolean };\n /**\n * L3DIST-007's git-to-local.db import half of the union (phase2-l3-distribution.md): upserts a\n * card read off `knowledge/_l3/<content_hash>.md` on the knowledge branch, for a developer who\n * never authored it locally. Dedups by `content_hash` exactly like `upsertDecision` (joined\n * through `l2NodeId`'s project) — a `content_hash` already present locally is left untouched\n * (`imported: false`; that developer's own row is the source of truth, e.g. a richer\n * `occurrence_count`), never overwritten by the card's necessarily-thinner git-portable fields.\n * Otherwise inserts a new row seeded from the card's immutable fields, with both\n * `source_commits` and `initial_source_commits` set to the card's (already-frozen)\n * `sourceCommits`, and `created_at` preserved from the card rather than stamped \"now\" — the\n * imported row's history should read as the original decision's, not this machine's import\n * time. Fields the card deliberately never carries (L3DIST-003: `occurrence_count`,\n * `last_verified_at`, `confidence`, `noise_score`, `validity_status`, `commit_hash`,\n * `introduced_in_commit`, `verified_until_commit`) are left at their column defaults.\n */\n importCard(input: {\n l2NodeId: number;\n contentHash: string;\n title: string;\n content: string;\n nodeType: string;\n sourceCommits: string[];\n extractionModel: string | null;\n sourceFiles: string[];\n createdAt: string;\n }): { id: number; imported: boolean };\n}\n\nexport interface IFtsRepo {\n /**\n * FTS5 keyword search over `l2_nodes` (name/description/path_patterns), ranked by `rank`.\n * Returns full mapped rows, not the fts5 virtual table's own shape.\n */\n searchL2Nodes(keywords: string[], limit: number): L2NodeRow[];\n /** FTS5 keyword search over `l3_nodes` (title/content), ranked by `rank`. */\n searchL3Nodes(keywords: string[], limit: number): L3NodeRow[];\n}\n\nexport interface AstCallSiteRow {\n id: number;\n project_id: number;\n file_path: string;\n target_function: string;\n start_line: number;\n start_column: number;\n created_at: string;\n}\n\nexport interface ICallSitesRepo {\n /** Deletes all call-site rows for one file (mirrors IGraphNodesRepo.deleteNodesForPath's\n * delete-then-reinsert-on-reparse pattern) -- called by GraphPersisterService before\n * re-inserting a re-parsed file's fresh call sites. */\n deleteForFile(projectId: number, filePath: string): void;\n /** Bulk-inserts one file's call sites in one prepared-statement loop (same rationale as\n * GraphPersisterService's insertFunctionNodes/insertClassNodes -- avoid per-row overhead\n * at vscode/nest scale). No-op on an empty array. */\n insertMany(\n projectId: number,\n filePath: string,\n callSites: Array<{\n targetFunction: string;\n startLine: number;\n startColumn: number;\n }>,\n ): void;\n /** Tier B's read-back (issue #11 plan A, Slice 3): every persisted call site for the given\n * files, keyed by the *exact* relativePath string passed in (D4) -- callers must not expect\n * path normalization here. Files with no rows (never parsed, or parsed with zero calls) are\n * simply absent from the returned map, not present with an empty array, so callers can use\n * Map.has()/`in` to distinguish \"no data\" from \"confirmed zero calls\" if that distinction\n * ever matters later. */\n getForFiles(\n projectId: number,\n filePaths: string[],\n ): Map<\n string,\n Array<{ targetFunction: string; startLine: number; startColumn: number }>\n >;\n}\n\n/**\n * The shared memory/state layer surface — implemented by `lib/schema`'s `GraphStore`. One\n * instance per `dbPath` per process, opened and closed exclusively by the Orchestration layer\n * (`lib/ui-core`); no other layer manages its lifecycle.\n */\nexport interface IGraphStore {\n readonly projects: IProjectsRepo;\n readonly files: IProjectFilesRepo;\n readonly tags: ITagsRepo;\n readonly graph: IGraphNodesRepo;\n readonly l3: IL3NodesRepo;\n readonly fts: IFtsRepo;\n readonly meta: IMetaRepo;\n readonly callSites: ICallSitesRepo;\n withWriteLock<T>(fn: () => Promise<T> | T): Promise<T>;\n withReadLock<T>(fn: () => Promise<T> | T): Promise<T>;\n /**\n * Runs `fn` inside a single `better-sqlite3` transaction (one BEGIN/COMMIT, rolled back on\n * throw) instead of each repo call inside it auto-committing on its own. `fn` must be fully\n * synchronous — SQLite transactions can't span an event-loop turn — matching every existing\n * `IGraphNodesRepo`/`IProjectFilesRepo`/etc. method, which are already sync. Callers writing\n * many rows in one logical operation (e.g. `GraphPersisterService.persistLocked`) MUST use this\n * instead of relying on default autocommit: at vscode-repo scale (12k+ files, hundreds of\n * thousands of `calls`/`extends`/`implements` edges once `ScopeResolver` actually resolves\n * them), one fsync per row turned a multi-minute persist into a practically-infinite one — see\n * docs/cli-test-analysis/typescript-cli-benchmark.md's Tier B re-verification session. Does not\n * replace `withWriteLock` — callers still need that for cross-process/cross-call serialization;\n * this only removes the per-statement autocommit cost inside one already-locked call.\n */\n withTransaction<T>(fn: () => T): T;\n close(): Promise<void>;\n /**\n * Surgically removes `project_files`/`l2_nodes` (and their `node_links`/`l2_node_l1_tags`) for\n * files no longer present in `activeFiles`, in a single transaction — without wiping the whole\n * database. A node is stale when none of its `path_patterns` entries are in `activeFiles`\n * (mirrors old Docuvia's `CleanService.prune`, adapted to this schema's `path_patterns` column\n * instead of the old `source_paths` column). Not currently wired to any workflow/CLI command —\n * old Docuvia never called it from a command either (see `docs/gitbook/analysis/data-pipeline-sync.md`);\n * it is exposed here so a future incremental-sync workflow can use it.\n */\n pruneMissingFiles(activeFiles: string[]): {\n prunedFiles: number;\n prunedNodes: number;\n };\n}\n\nexport interface GraphStoreOpenOptions {\n dbPath: string;\n readonly?: boolean;\n}\n","import type { L3NodeType } from \"./graph-store.interfaces.js\";\n\n/**\n * Remote backend sync surface — implemented by `lib/remote-api`'s fetch-backed HTTP client. Pure\n * network I/O with no Docuvia-specific dedup logic (the content-hash cache lives in\n * `lib/ui-core`'s sync workflow); see docs/gitbook/architecture/virtual-contracts-architecture.md's\n * Technology Provider section.\n */\nexport interface RemoteL2NodeSummary {\n id: number;\n name: string;\n [key: string]: unknown;\n}\n\nexport interface CreateL3EventPayload {\n l2NodeId: number;\n title: string;\n content?: string | null;\n nodeType?: L3NodeType;\n confidence?: number | null;\n sourceCommits?: string[];\n contentHash?: string | null;\n}\n\nexport const SyncPushEventTypes = {\n CREATE_L3: \"CREATE_L3\",\n} as const;\nexport type SyncPushEventType =\n (typeof SyncPushEventTypes)[keyof typeof SyncPushEventTypes];\n\nexport interface SyncPushEvent {\n type: SyncPushEventType;\n payload: CreateL3EventPayload;\n}\n\nexport interface SyncPushResult {\n success: boolean;\n processed: number;\n}\n\n/** Per-run config — never read from `process.env` inside the implementation (see\n * docs/gitbook/architecture/application-lifecycle-and-state.md); the Presentation layer reads\n * `DOCUVIA_API_URL`/`MCP_PAT` and injects them via `docuviaMemory`, and the orchestration layer\n * passes them into this `initialize()` call. */\nexport interface RemoteSyncClientConfig {\n apiUrl: string;\n pat: string;\n}\n\nexport interface IRemoteSyncClient {\n initialize(config: RemoteSyncClientConfig): void;\n fetchRemoteL2Nodes(projectId: string): Promise<RemoteL2NodeSummary[]>;\n pushSyncEvents(\n projectId: string,\n events: SyncPushEvent[],\n ): Promise<SyncPushResult>;\n}\n","/**\n * CLIProxyAPI bridge surface — implemented by lib/llm-api's fetch-backed HTTP client. Pure\n * network I/O speaking CLIProxyAPI's OpenAI-compatible /v1/chat/completions endpoint, with no\n * provider routing, OAuth, or vendor-SDK logic (CLIProxyAPI, a separately, user-run process,\n * handles all of that); see docs/gitbook/adr/llm/LLM-002-cliproxyapi-bridge.md and\n * docs/gitbook/adr/platform/PLAT-003-remote-sync-technology-provider.md for the Technology\n * Provider pattern this mirrors.\n */\n/** Per-run config — never read from `process.env` inside the implementation (see\n * docs/gitbook/architecture/application-lifecycle-and-state.md); the Presentation layer reads\n * `AI_DOCUVIA_INTEGRATIONS_OPENAI_BASE_URL`/`AI_DOCUVIA_INTEGRATIONS_OPENAI_API_KEY` (naming\n * carried forward from old Docuvia's `AI_INTEGRATIONS_OPENAI_BASE_URL`/`_API_KEY`) and injects\n * them via `docuviaMemory`, and the orchestration layer passes them into this `initialize()`\n * call. `apiKey` is optional — CLIProxyAPI's own gate, only set if the user enabled one; it is\n * never the underlying provider's (OpenAI/Anthropic/Gemini) native API key. */\nexport interface LlmClientConfig {\n baseUrl: string;\n apiKey?: string;\n}\n\nexport const ChatMessageRoles = {\n SYSTEM: \"system\",\n USER: \"user\",\n ASSISTANT: \"assistant\",\n TOOL: \"tool\",\n} as const;\nexport type ChatMessageRole =\n (typeof ChatMessageRoles)[keyof typeof ChatMessageRoles];\n\n/** The only tool kind CLIProxyAPI's OpenAI-compatible surface supports today. */\nexport const CHAT_TOOL_TYPE = \"function\" as const;\n\n/** Values for `ChatCompletionRequest.toolChoice` short-hand modes (\"auto\" lets the model decide, \"none\" forbids tool calls). */\nexport const ChatToolChoiceModes = {\n AUTO: \"auto\",\n NONE: \"none\",\n} as const;\n\nexport interface ChatToolCall {\n id: string;\n type: typeof CHAT_TOOL_TYPE;\n function: { name: string; arguments: string };\n}\n\nexport interface ChatMessage {\n role: ChatMessageRole;\n content: string | null;\n name?: string;\n toolCallId?: string;\n toolCalls?: ChatToolCall[];\n}\n\nexport interface ChatToolDefinition {\n type: typeof CHAT_TOOL_TYPE;\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n };\n}\n\nexport interface ChatCompletionRequest {\n model: string;\n messages: ChatMessage[];\n tools?: ChatToolDefinition[];\n toolChoice?:\n | typeof ChatToolChoiceModes.AUTO\n | typeof ChatToolChoiceModes.NONE\n | { type: typeof CHAT_TOOL_TYPE; function: { name: string } };\n temperature?: number;\n maxTokens?: number;\n}\n\nexport interface ChatCompletionChoice {\n index: number;\n message: ChatMessage;\n finishReason: string | null;\n}\n\nexport interface ChatCompletionResult {\n id: string;\n model: string;\n choices: ChatCompletionChoice[];\n}\n\nexport interface ChatCompletionChunkDelta {\n role?: ChatMessageRole;\n content?: string;\n toolCalls?: ChatToolCall[];\n}\n\nexport interface ChatCompletionChunkChoice {\n index: number;\n delta: ChatCompletionChunkDelta;\n finishReason: string | null;\n}\n\nexport interface ChatCompletionChunk {\n id: string;\n model: string;\n choices: ChatCompletionChunkChoice[];\n}\n\n/** Result of `ILlmClient.checkAvailability()` (phase1-decision-integration.md §10e bullet 3;\n * decision 1e) -- mirrors `EdgeResolutionAvailability`'s exact shape. */\nexport interface LlmClientAvailability {\n available: boolean;\n /** Human-readable reason when `available` is `false`. Always present when `available` is\n * `false`. */\n reason?: string;\n}\n\nexport interface ILlmClient {\n initialize(config: LlmClientConfig): void;\n chatCompletion(request: ChatCompletionRequest): Promise<ChatCompletionResult>;\n streamChatCompletion(\n request: ChatCompletionRequest,\n ): AsyncIterable<ChatCompletionChunk>;\n /**\n * Lightweight reachability probe against `config.baseUrl` (decision 1e) -- a check that a\n * server is there and responding, never *does this exact route exist* (any received HTTP\n * response, even a 4xx/5xx, counts as `available: true`; only a network-level failure --\n * connection refused, DNS failure, timeout -- is `available: false`). Never throws -- a check\n * that itself fails is reported as `available: false`, mirroring\n * `IEdgeResolutionProvider.checkAvailability()`'s contract exactly. Unlike that method, this\n * takes no argument: `ILlmClient` is configured via `initialize()` before use, not per-call.\n */\n checkAvailability(): Promise<LlmClientAvailability>;\n /**\n * Issue #134: a reachability probe that exercises the *actual* Tier C bridge path `analyze`'s\n * drain dials — a POST to `<baseUrl>/v1/chat/completions` with the same auth headers as\n * `chatCompletion()` and a minimal `max_tokens: 1` body. `checkAvailability()`'s GET on the\n * bare `baseUrl` can PASS while the completions route itself is broken (bridge proxy up, but\n * `/v1/chat/completions` 404s or rejects the API key — issue #134's live repro: `doctor`\n * reporting `llm_reachability ✓ PASS` while every Tier C drain item failed with\n * `bridge-unreachable`), so `doctor` probes *this* instead of the liveness ping. Any 2xx is\n * `available: true`; a non-2xx response (wrong route, bad auth) or a network-level failure is\n * `available: false` with a reason. Never throws — a probe that itself fails is reported as\n * `available: false`, mirroring `checkAvailability()`'s contract.\n */\n checkBridgeReachability(model: string): Promise<LlmClientAvailability>;\n}\n","import type { IGraphStore } from \"./graph-store.interfaces.js\";\n\n/**\n * Blast-radius risk scoring (Domain Core logic — see\n * docs/gitbook/architecture/virtual-contracts-architecture.md's Domain Core section, which\n * names blast-radius calculation and risk scoring directly as `lib/core` responsibilities).\n * Shared by the standalone `docuvia impact <target>` command and `review`'s per-file\n * aggregation, so the two never drift apart on what counts as \"risky\".\n */\nexport const RiskLevels = {\n LOW: \"LOW\",\n MEDIUM: \"MEDIUM\",\n HIGH: \"HIGH\",\n CRITICAL: \"CRITICAL\",\n} as const;\n\nexport type RiskLevel = (typeof RiskLevels)[keyof typeof RiskLevels];\n\nexport interface BlastRadiusEntry {\n name: string;\n type: string;\n /**\n * L3 \"why\" data (decisions/context) attached to this node, when any exists — populated by\n * `ImpactService.getBlastRadius` from `IL3NodesRepo.getByL2NodeId`. Omitted (not an empty\n * array) when the node has no L3 rows, so existing `toEqual`-style assertions on a plain\n * `{ name, type }` entry keep passing.\n */\n why?: Array<{ title: string; content: string | null }>;\n}\n\nexport interface IImpactService {\n /**\n * LOW/MEDIUM/HIGH/CRITICAL derivation from a raw impacted-node count, scaled against `store`'s\n * current total `l2_nodes` count (typescript-cli-benchmark.md's\n * impact-risk-thresholds-not-scaled-to-repo-size fix) -- `store` is required, not optional,\n * matching `getBlastRadius`'s own store-first convention on this interface: a caller silently\n * omitting the denominator would just as silently reintroduce the unscaled-absolute-count bug\n * this fixes.\n */\n computeRiskLevel(store: IGraphStore, impactedCount: number): RiskLevel;\n /**\n * 1-hop blast radius (direct callers/dependents) for `target`, resolved exact-then-LIKE.\n * Undefined when `target` doesn't resolve to any node.\n */\n getBlastRadius(\n store: IGraphStore,\n target: string,\n ): BlastRadiusEntry[] | undefined;\n}\n","import type { IGraphStore } from \"./graph-store.interfaces.js\";\n\n/**\n * Local-first (no-LLM) natural-language + structural query surface (Domain Core logic) —\n * mirrors old Docuvia's `QueryService`, minus the LLM-based intent-extraction hop (deferred,\n * tracked separately): keyword extraction always uses the deterministic stop-word-stripping\n * fallback old Docuvia only used when its LLM was unreachable.\n */\nexport interface GraphEdgeRef {\n name: string;\n /** The relationship itself (`calls`/`implements`/`extends`/...) — previously this field held the\n * *neighbor node's* own kind instead, which is always `\"module\"` today (every symbol/file row\n * shares one `L2NodeType`, see `persist-ast-graph.ts`) and so never actually told a caller\n * anything. `getContext()` also excludes `contains` edges from both `incoming`/`outgoing`: a\n * symbol's own containing file isn't a \"caller\"/\"callee\", and leaving it in crowded out (or\n * masqueraded as) genuine relationships for foundational symbols with few resolved\n * calls/implements/extends edges. */\n linkType: string;\n}\n\nexport interface GraphContext {\n incoming: GraphEdgeRef[];\n outgoing: GraphEdgeRef[];\n /** Additive, omit-when-confident \"not yet Tier B-processed\" signal -- see `TierBCoverageHint`'s\n * own doc comment below. Attached by `QueryService.getContext()` only when it would actually\n * change how a caller should read an empty `incoming`/`outgoing` list. */\n tierBCoverage?: TierBCoverageHint;\n}\n\n/**\n * Additive, omit-when-confident \"not yet Tier B-processed\" signal (typescript-cli-benchmark.md\n * §5.3/§5.7 item 2) — attached to a query/impact result only when an empty edge list might mean\n * \"never looked at\" rather than \"confirmed zero\". Shared by `query`'s `GraphContext` and\n * `impact`'s `ImpactResult`, both in `lib/core`/`lib/ui-core` — computed via\n * `resolveTierBCoverageHint()` (`lib/core`), never present-with-nulls: consumers must treat a\n * missing field as \"nothing ambiguous to report,\" not as an empty object.\n */\nexport interface TierBCoverageHint {\n /** Governs how much to trust an empty `outgoing` list — this node's own file's last Tier B\n * pass. `null` = never processed. */\n ownFileLastProcessedAt: string | null;\n /** Governs how much to trust an empty `incoming`/blast-radius list — workspace-wide, has every\n * currently-tracked file been Tier B processed at least once? */\n workspaceFilesProcessed: number;\n workspaceFilesTotal: number;\n}\n\n/**\n * Computes the additive \"not yet Tier B-processed\" signal (`TierBCoverageHint`) for a\n * query/impact result. DI-registered behind a token (implemented by `lib/core`'s\n * `resolveTierBCoverageHint`) so the Orchestration layer resolves it from `docuviaFactory`\n * instead of importing `@workspace/core` directly — same pattern as `IImpactService`.\n */\nexport interface ITierBCoverageHintProvider {\n resolve(\n store: IGraphStore,\n ownFilePath: string | undefined,\n incomingEmpty: boolean,\n outgoingEmpty: boolean,\n ): TierBCoverageHint | undefined;\n}\n\nexport const QueryResultLayers = {\n L2: \"l2\",\n L3: \"l3\",\n} as const;\nexport type QueryResultLayer =\n (typeof QueryResultLayers)[keyof typeof QueryResultLayers];\n\n/** How a `search()`/`query()` result was found -- `\"exact\"` came from an exact `findNodeByName`\n * match, `\"keyword\"` from an FTS keyword hit, `\"neighbor\"` from a resolved node's own edge (not\n * a direct hit on the query itself). Surfaced to callers so a non-exact match can be treated as\n * lower-confidence rather than indistinguishable from an exact one. */\nexport type QueryMatchType = \"exact\" | \"keyword\" | \"neighbor\";\n\nexport interface LocalSearchResult {\n layer: QueryResultLayer;\n id: number;\n title: string;\n content: string | null;\n matchType: QueryMatchType;\n}\n\nexport interface LocalQueryResult {\n l2: {\n name: string;\n type: string;\n filePath?: string;\n matchType: QueryMatchType;\n } | null;\n l3: Array<{ title: string; content: string | null }>;\n context: GraphContext | null;\n}\n\nexport interface IQueryService {\n /** Deterministic stop-word-stripping tokenizer (old Docuvia's LLM-unreachable fallback, used unconditionally here). */\n extractKeywords(query: string): string[];\n /** Structural context (incoming/outgoing edges) for a resolved node, or null if `target` doesn't resolve. */\n getContext(store: IGraphStore, target: string): GraphContext | null;\n /** FTS keyword search + node-ref exact/LIKE lookup + 1-hop neighbor traversal, deduped and ranked. */\n search(\n store: IGraphStore,\n target: string,\n limit?: number,\n ): LocalSearchResult[];\n /** End-to-end query: `search()` bucketed into {l2, l3} plus `getContext()`. */\n query(store: IGraphStore, target: string, limit?: number): LocalQueryResult;\n}\n","import type {\n L2NodeRow,\n L2NodeType,\n L3NodeRow,\n L3NodeType,\n NodeLinkRow,\n LinkType,\n ValidityStatus,\n} from \"./graph-store.interfaces.js\";\n\n/**\n * Topology export schema (machine-readable knowledge-graph projection), ported near-verbatim\n * from old Docuvia's `lib/core/src/types/topology.types.ts` — a pure, storage-agnostic data\n * shape (zero logic), so it belongs here rather than in `lib/schema` or `lib/core`. Bump\n * `TOPOLOGY_VERSION` on any breaking change to this shape.\n */\nexport const TOPOLOGY_VERSION = 2;\n\nexport const TopologyNodeKinds = {\n FILE: \"file\",\n SYMBOL: \"symbol\",\n DECISION: \"decision\",\n} as const;\nexport type TopologyNodeKind =\n (typeof TopologyNodeKinds)[keyof typeof TopologyNodeKinds];\n\nexport const TopologyCollapseModes = {\n AUTO: \"auto\",\n FILE: \"file\",\n SYMBOL: \"symbol\",\n} as const;\nexport type TopologyCollapseMode =\n (typeof TopologyCollapseModes)[keyof typeof TopologyCollapseModes];\n\nexport interface TopologyNode {\n /** Stable id: \"l2:<l2_nodes.id>\" or \"l3:<l3_nodes.id>\" */\n id: string;\n label: string;\n kind: TopologyNodeKind;\n /** Group id referencing TopologyGroup.id */\n group: number;\n /** Workspace-relative source file (absent for ungrouped/unknown nodes) */\n filePath?: string;\n /** For decision nodes: the l2 node id (\"l2:<id>\") the decision documents */\n parent?: string;\n /** Link count touching this node — renderers use it for node sizing */\n degree: number;\n /** L1 tag names attached to this node (file nodes only) */\n tags?: string[];\n /** L2 node type (\"module\" | \"package\" | \"pcd\") — file/symbol nodes only. */\n l2Type?: L2NodeType;\n /** For decision nodes: the l3 row's content classification (\"change\" | \"rule\" | \"decision\" | \"context\") */\n decisionType?: L3NodeType;\n /** For decision nodes: the l3 row's body text */\n content?: string;\n /** For decision nodes: extraction confidence (0-1), absent when never scored */\n confidence?: number;\n /** For decision nodes: current lifecycle status */\n validityStatus?: ValidityStatus;\n /** For decision nodes: commit shas the decision was derived from */\n sourceCommits?: string[];\n}\n\nexport interface TopologyLink {\n source: string;\n target: string;\n linkType: LinkType;\n /** 0-1; reserved for LSP-enriched / inferred edges. Static AST edges are 1. */\n confidence: number;\n /** Commit sha the underlying edge was observed/updated in, when known */\n commitSha?: string;\n /** Short human-readable summary of the diff that produced/changed this edge, when known */\n diffSummary?: string;\n}\n\nexport const TopologyGroupSources = {\n L1_TAG: \"l1_tag\",\n DIRECTORY: \"directory\",\n} as const;\nexport type TopologyGroupSource =\n (typeof TopologyGroupSources)[keyof typeof TopologyGroupSources];\n\nexport interface TopologyGroup {\n id: number;\n label: string;\n /** How the group was derived. Directory clustering is the v1 default. */\n source: TopologyGroupSource;\n /** Number of member nodes */\n count: number;\n}\n\nexport interface TopologyStats {\n nodeCount: number;\n linkCount: number;\n groupCount: number;\n /** Non-CONTAINS edges collapsed into a same-file self-loop (and dropped) when folding to file\n * granularity — the \"auto\"/\"file\" collapse's default view otherwise reports a link count that\n * looks sparse even on a densely-connected repo, since most calls resolve within their own\n * file (see `TopologyBuilderService.buildCollapsed`). Always 0 at symbol granularity. */\n foldedLinkCount: number;\n}\n\nexport interface TopologyGraph {\n topologyVersion: number;\n generatedAt: string;\n workspaceRoot: string;\n /** True when symbol nodes were folded into their file nodes (node-cap or explicit mode) */\n collapsed: boolean;\n nodes: TopologyNode[];\n links: TopologyLink[];\n groups: TopologyGroup[];\n stats: TopologyStats;\n}\n\nexport interface TopologyExportOptions {\n /**\n * \"symbol\": full symbol-level graph. \"file\": fold symbols into their files.\n * \"auto\" (default): symbol-level unless node count exceeds maxNodes.\n */\n collapse?: TopologyCollapseMode;\n /** Node cap for \"auto\" collapse (default 2000) */\n maxNodes?: number;\n}\n\n/**\n * Raw storage-shaped input for the topology builder (Domain Core logic in `lib/core`) — the\n * builder itself does the `path_patterns` JSON-parsing/grouping/collapsing projection, so this\n * layer just hands it the already-mapped repo row types directly (no separate \"storage-agnostic\n * row\" indirection needed now that there is only one storage backend).\n */\nexport interface TopologyBuildInput {\n workspaceRoot: string;\n l2Rows: L2NodeRow[];\n linkRows: NodeLinkRow[];\n /** Pass only exportable decisions — status filtering is `IL3NodesRepo.getAllExportable()`'s job. */\n l3Rows: L3NodeRow[];\n tagRows: Array<{ l2NodeId: number; name: string }>;\n}\n\nexport interface ITopologyBuilder {\n build(\n input: TopologyBuildInput,\n options?: TopologyExportOptions,\n ): TopologyGraph;\n}\n","/**\n * Tag/project-type sentinel values that `ConfigScannerService` both writes (via\n * `CONFIG_DETECTION_RULES`) and reads back (in its post-scan project-type inference fallback),\n * so a typo in either place can't silently desync detection from inference. Shared vocabulary —\n * lives in contracts (issue #94) so upper layers (ui-core/cli) can reference it without a\n * `lib/core` dependency.\n */\nexport const ConfigTags = {\n TYPESCRIPT: \"typescript\",\n REACT: \"react\",\n EXPRESS: \"express\",\n VUE: \"vue\",\n} as const;\n\nexport const ProjectTypes = {\n UNKNOWN: \"unknown\",\n JAVASCRIPT: \"javascript\",\n GENERIC: \"generic\",\n RUST: \"rust\",\n PYTHON: \"python\",\n GO: \"go\",\n} as const;\n\n/** Fallback tag added when config-file scanning surfaces nothing more specific. */\nexport const GENERAL_TAG = \"general\";\n\n/**\n * Non-sentinel tags `CONFIG_DETECTION_RULES` attaches to a matched config file. Unlike\n * `ConfigTags`, none of these are read back/compared elsewhere in `ConfigScannerService` — they're\n * pure output — but several are reused verbatim across multiple detection rules in the same table,\n * so they're still centralized here to keep those repeats in sync.\n */\nexport const ConfigDetectionTags = {\n FRONTEND: \"frontend\",\n BACKEND: \"backend\",\n NEXTJS: \"nextjs\",\n SSR: \"ssr\",\n DRIZZLE: \"drizzle\",\n DATABASE: \"database\",\n TAILWINDCSS: \"tailwindcss\",\n CSS: \"css\",\n JEST: \"jest\",\n TESTING: \"testing\",\n VITEST: \"vitest\",\n POSTGRES: \"postgres\",\n MONOREPO: \"monorepo\",\n TOKIO: \"tokio\",\n ASYNC: \"async\",\n ACTIX: \"actix\",\n SERDE: \"serde\",\n TAURI: \"tauri\",\n DESKTOP: \"desktop\",\n DJANGO: \"django\",\n FASTAPI: \"fastapi\",\n PANDAS: \"pandas\",\n DATA: \"data\",\n GIN: \"gin\",\n STRICT_TS: \"strict-ts\",\n VITE: \"vite\",\n BUILD_TOOL: \"build-tool\",\n} as const;\n","/**\n * Plain git-configuration conventions shared across `lib/core/git`, `lib/git-local`,\n * `lib/ui-core` and `artifacts/cli`. Per\n * docs/gitbook/architecture/virtual-contracts-architecture.md, Domain Core (`lib/core`), Tech\n * Providers (`lib/git-local`), Orchestration (`lib/ui-core`) and Presentation (`artifacts/cli`)\n * sit at different layers and never import each other directly — \"all shared definitions must\n * live in contracts\" — so a value more than one layer needs lives here rather than being\n * duplicated per-package.\n */\n\n/** Git's conventional name for the default/primary remote. */\nexport const GIT_DEFAULT_REMOTE_NAME = \"origin\" as const;\n\n/** Docuvia-specific git conventions — the domain semantics layered on top of `IGitProvider`'s raw primitives. */\nexport const GitConstants = {\n KNOWLEDGE_ROOT: \"docuvia-knowledge\",\n KNOWLEDGE_DIR_NAME: \"knowledge\",\n GRAPH_DIR_NAME: \"graph\",\n /** Subdirectory of `KNOWLEDGE_DIR_NAME` holding L3 decision cards (phase2-l3-distribution.md\n * L3DIST-001): one file per `content_hash`, `knowledge/_l3/<content_hash>.md`. */\n L3_DIR_NAME: \"_l3\",\n NODES_JSONL_NAME: \"nodes.jsonl\",\n EDGES_JSONL_NAME: \"edges.jsonl\",\n /** Commit-message trailer key (STOR-001 point 4) carrying the full 40-char source-commit sha, read back by Phase 2's nearest-ancestor hydration lookup. */\n SOURCE_COMMIT_TRAILER_KEY: \"Docuvia-Source\",\n POST_COMMIT_HOOK_NAME: \"post-commit\",\n /**\n * `analyze` auto mode (PLAT-007 Tier A) is the hook's command as of Slice 2 dispatch 2b\n * (phase1-decision-integration.md §6c) — gated by the `analyze`+`snapshot` and `doctor`+\n * `hydrate` concurrency tests, which must exist and pass before this flip. See\n * `LEGACY_POST_COMMIT_HOOK_MARKER`/`LEGACY_POST_COMMIT_HOOK_CONTENT` for the pre-2b hook this\n * replaces in-place on an existing installation.\n */\n POST_COMMIT_HOOK_MARKER: \"docuvia analyze\",\n /**\n * `docuvia hooks disable commit-l3-write`'s enforcement (issue #42 §8.3) -- present only in\n * the current `POST_COMMIT_HOOK_CONTENT`, absent from `PRE_FLUSH_L3_POST_COMMIT_HOOK_CONTENT`/\n * `LEGACY_POST_COMMIT_HOOK_CONTENT` -- the marker `installPostCommitHook` uses to tell a hook\n * that already runs the `analyze --flush-staged-l3` step from one still missing it.\n */\n POST_COMMIT_FLUSH_L3_MARKER: \"docuvia analyze --flush-staged-l3\",\n /**\n * Issue #58: the `nohup`-backgrounded form, present only in the current `POST_COMMIT_HOOK_CONTENT`,\n * absent from `PRE_NOHUP_POST_COMMIT_HOOK_CONTENT`/`PRE_FLUSH_L3_POST_COMMIT_HOOK_CONTENT`/\n * `LEGACY_POST_COMMIT_HOOK_CONTENT` -- the marker `installPostCommitHook` uses to tell a hook\n * that already backgrounds with `nohup` + a log-file redirect (survives the hook shell's exit;\n * npx-resolution/startup failures visible in `.docuvia/logs/post-commit-hook.log`) from one\n * still using the old fire-and-forget `> /dev/null 2>&1 &` form, whose backgrounded process\n * could die with the hook's shell and silently skip delta ingestion (issue #58's root cause 1).\n */\n POST_COMMIT_NOHUP_MARKER: \"nohup npx --no-install docuvia analyze\",\n /** Byte-identical header line shared by `POST_COMMIT_HOOK_CONTENT`/\n * `LEGACY_POST_COMMIT_HOOK_CONTENT` — the anchor `doctor --fix`'s marker-bounded repair (§10d,\n * decision 1f) uses to strip every Docuvia-authored block regardless of minor hand-edits that\n * would break exact-content matching. */\n DOCUVIA_HOOK_HEADER_COMMENT: \"# Docuvia Knowledge Graph Evolver Hook\",\n /**\n * `> /dev/null 2>&1` (not the bash-only `&>`) throughout this and `PRE_PUSH_HOOK_CONTENT` —\n * husky's shim (`.husky/_/h`) invokes a hook file via `sh -e \"$s\"`, ignoring the file's own\n * `#!/bin/bash` shebang entirely, so any bash-specific syntax silently breaks (or behaves\n * differently) once a repo's `core.hooksPath` redirects Docuvia's hook there (found via\n * dogfooding, 2026-07-21). The portable form works identically under bash and POSIX `sh`.\n *\n * Issue #58 (nohup + log-file redirect): both backgrounded lines now run under `nohup` (a\n * POSIX external command -- unlike `disown`, which is a bash builtin husky's `sh -e` shim\n * would break on, and `setsid`, which macOS doesn't ship) so the process survives the hook's\n * own shell exiting, and their output lands in `.docuvia/logs/post-commit-hook.log` instead of\n * `/dev/null` -- an `npx --no-install` resolution failure or a process that dies before it can\n * write its own JSONL is now visible there (and via doctor's `post_commit_ingestion`\n * diagnostic), where before it was swallowed entirely and `lastIngestedSourceSha` silently\n * stopped advancing.\n *\n * Second backgrounded line (issue #42 §8.3): flushes any staged agent-authored L3 decisions for\n * this commit, self-gated internally on the `commit-l3-write` toggle (see\n * `run-flush-staged-l3.ts`) -- no shell-level `docuvia hooks check` composition here, unlike\n * `PRE_PUSH_HOOK_CONTENT`'s synchronous `&&` chain, since this line is itself backgrounded\n * (`&`) and has no exit code for a `&&` composition to react to.\n *\n * Two background `npx` processes now run per commit (auto `analyze` + `--flush-staged-l3`),\n * both touching the local SQLite DB. That's an accepted, documented tradeoff (issue #53 finding\n * 7): SQLite runs in WAL mode with `busy_timeout` 10000ms (`lib/schema`), so the two processes'\n * write contention serializes rather than erroring, and the flush is a no-op fast path whenever\n * nothing is staged. Keeping them as separate backgrounded lines (rather than one process doing\n * both) preserves each line's simple fire-and-forget contract.\n */\n POST_COMMIT_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Knowledge Graph Evolver Hook\\n` +\n `# Non-intrusively extracts AST deltas in the background\\n` +\n `if command -v npx > /dev/null 2>&1; then\\n` +\n ` mkdir -p .docuvia/logs\\n` +\n ` # Fire and forget (do not block commit) -- nohup keeps the process alive after this hook's\\n` +\n ` # shell exits; output goes to a log file (not /dev/null) so failures are visible (issue #58).\\n` +\n ` nohup npx --no-install docuvia analyze >> .docuvia/logs/post-commit-hook.log 2>&1 &\\n` +\n ` # Flush any staged agent-authored L3 decisions for this commit (roadmap items 32-34, issue #42).\\n` +\n ` # Self-gated internally on the commit-l3-write toggle -- see run-flush-staged-l3.ts.\\n` +\n ` nohup npx --no-install docuvia analyze --flush-staged-l3 >> .docuvia/logs/post-commit-hook.log 2>&1 &\\n` +\n `fi\\n`,\n /**\n * The pre-issue-#42 hook's exact content (single `docuvia analyze &` line, before the\n * `--flush-staged-l3` line was added), retained verbatim so `installPostCommitHook` can\n * recognize a hook installed before that step was composed in and replace it in place -- same\n * technique as the `LEGACY_POST_COMMIT_HOOK_CONTENT` upgrade below.\n */\n PRE_FLUSH_L3_POST_COMMIT_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Knowledge Graph Evolver Hook\\n` +\n `# Non-intrusively extracts AST deltas in the background\\n` +\n `if command -v npx > /dev/null 2>&1; then\\n` +\n ` # Fire and forget (do not block commit)\\n` +\n ` npx --no-install docuvia analyze > /dev/null 2>&1 &\\n` +\n `fi\\n`,\n /**\n * The pre-issue-#58 hook's exact content (the two-line `--flush-staged-l3` form, still using\n * the bare fire-and-forget `> /dev/null 2>&1 &` backgrounding), retained verbatim so\n * `installPostCommitHook` can recognize a hook installed before the `nohup` + log-redirect\n * change (issue #58) and replace it in place -- same technique as the\n * `PRE_FLUSH_L3_POST_COMMIT_HOOK_CONTENT` upgrade below.\n */\n PRE_NOHUP_POST_COMMIT_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Knowledge Graph Evolver Hook\\n` +\n `# Non-intrusively extracts AST deltas in the background\\n` +\n `if command -v npx > /dev/null 2>&1; then\\n` +\n ` # Fire and forget (do not block commit)\\n` +\n ` npx --no-install docuvia analyze > /dev/null 2>&1 &\\n` +\n ` # Flush any staged agent-authored L3 decisions for this commit (roadmap items 32-34, issue #42).\\n` +\n ` # Self-gated internally on the commit-l3-write toggle -- see run-flush-staged-l3.ts.\\n` +\n ` npx --no-install docuvia analyze --flush-staged-l3 > /dev/null 2>&1 &\\n` +\n `fi\\n`,\n /**\n * The pre-Slice-2b hook's marker/content, retained verbatim so `installPostCommitHook` can\n * recognize a hook installed before the `snapshot` -> `analyze` flip and replace it in place\n * (phase1-decision-integration.md §6c) rather than appending a second, duplicate Docuvia block\n * alongside the old one.\n */\n LEGACY_POST_COMMIT_HOOK_MARKER: \"docuvia snapshot\",\n LEGACY_POST_COMMIT_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Knowledge Graph Evolver Hook\\n` +\n `# Non-intrusively extracts AST deltas in the background\\n` +\n `if command -v npx &> /dev/null; then\\n` +\n ` # Fire and forget (do not block commit)\\n` +\n ` npx --no-install docuvia snapshot > /dev/null 2>&1 &\\n` +\n `fi\\n`,\n LOCAL_REMOTE_URL_SCHEME: \"file://\",\n /** Git's conventional name for the default/primary remote — shared with `lib/git-local` via\n * `GIT_DEFAULT_REMOTE_NAME` above per the Virtual Contracts rule that values needed by both a\n * Domain Core and a Tech Provider package live in contracts. */\n DEFAULT_REMOTE_NAME: GIT_DEFAULT_REMOTE_NAME,\n /** Prefix for a remote-tracking ref path (`refs/remotes/<remote>/<branch>`), used when reading\n * the remote's copy of the knowledge branch tip during reconciliation. */\n REMOTE_REF_PREFIX: \"refs/remotes/\",\n /** The special ref name for the currently checked-out commit, used when walking source HEAD's\n * ancestry during hydration's nearest-ancestor lookup. */\n HEAD_REF: \"HEAD\",\n /** One project per local.db (first row created by the `init` workflow). */\n DEFAULT_LOCAL_PROJECT_ID: 1,\n /** `docuvia_meta` key storing the knowledge-branch commit sha `local.db` was last hydrated from (STOR-002). */\n META_KEY_KNOWLEDGE_TIP_SHA: \"hydratedKnowledgeSha\",\n /**\n * `docuvia_meta` key storing the source commit sha `local.db`'s graph was last *ingested*\n * from — distinct from `META_KEY_KNOWLEDGE_TIP_SHA`, which tracks the last git *hydration*\n * (phase1-decision-integration.md §6a; PLAT-007 Tier A). Written after every successful full or\n * delta `analyze` auto-mode ingestion; read back as the idempotency fast-path (`HEAD ===` this\n * value → no-op) and as the delta baseline (`this value -> HEAD`).\n */\n META_KEY_LAST_INGESTED_SOURCE_SHA: \"lastIngestedSourceSha\",\n /** `docuvia_meta` key storing the `node_key` format version (GRPH-006) the graph was last fully\n * ingested with -- `\"2\"` once qualified/structural keys are in use, absent/older on a\n * pre-qualified-key graph. Written only on a full ingestion (`stampFullIngestionForTierB`,\n * shared by `init` and `analyze`'s empty-graph branch); read by `runDeltaIngestion`'s guard to\n * refuse an incremental re-parse that would otherwise silently mix old-flat and new-qualified\n * keys in the same graph. */\n META_KEY_NODE_KEY_FORMAT_VERSION: \"nodeKeyFormatVersion\",\n /**\n * `docuvia_meta` key holding a JSON array of `{file, commitSha}` entries, deduped by `file` —\n * the Tier B queue `analyze` auto mode's delta ingestion enqueues `CONTRACT_CHANGED` files into\n * (phase1-decision-integration.md §6b; PLAT-007 Tier B). Not consumed until Slice 3.\n */\n META_KEY_TIER_B_QUEUE: \"tierBQueue\",\n /** `os.tmpdir()` prefix for `ensureKnowledgeBranch`'s scratch dir used to pack the empty initial snapshot. */\n EMPTY_KNOWLEDGE_TEMP_DIR_PREFIX: \"docuvia-empty-knowledge-\",\n\n /**\n * `docuvia_meta` key storing the source commit sha the last *fully successful* Tier B batch\n * (LSP escalation + snapshot) ran against — written only by `snapshot`'s post-pack finalize\n * step, never by `analyze --escalate-to-lsp` itself (phase1-decision-integration.md §8f, D5).\n * Absent on a pre-Slice-3 workspace: the commit-cap trigger stays inactive until the first\n * batch seeds it.\n */\n META_KEY_LAST_TIER_B_BATCH_SHA: \"lastTierBBatchSha\",\n /**\n * `docuvia_meta` key holding a JSON `{ headSha, remainingQueue }` record staged by a successful\n * `analyze --escalate-to-lsp` run, consumed by the next successful `snapshot` (§8g, D6: \"the\n * queue is cleared only after a successful snapshot\"). An empty-string value is the sentinel\n * for \"no pending batch\" (`IMetaRepo` has no delete — `set(key, \"\")` is the clear).\n */\n META_KEY_TIER_B_BATCH_PENDING: \"tierBBatchPending\",\n /** `docuvia_meta` key marking \"a knowledge-branch pack attempt from *this* local.db is in\n * flight or last failed to land\" — set immediately before `packSnapshotToKnowledgeBranch` is\n * attempted (`pack-current-graph.ts`) and cleared only once it succeeds. Read by\n * `HydrationService.hydrate()`'s safety guard so a same-workspace pack failure can never be\n * mistaken for \"git has newer data I should pull\" (2026-08 vscode-scale data-loss finding).\n * Empty-string sentinel for \"not pending\" — mirrors `META_KEY_TIER_B_BATCH_PENDING`'s\n * existing convention (`IMetaRepo` has no delete()). */\n META_KEY_KNOWLEDGE_PACK_PENDING: \"knowledgePackPending\",\n /**\n * `docuvia_meta` key holding the running total of changed-file bytes (blob size at `HEAD` of\n * every file `analyze`'s delta ingestion re-parsed, summed across delta runs) since the last\n * Tier B batch — the commit-cap trigger's metric as of §9m item 1\n * (phase1-decision-integration.md), replacing the original raw-commit-count comparison. A\n * single large refactor commit inflates this even though it's only one commit, which raw commit\n * count structurally could never detect. Incremented by `run-delta-ingestion.ts`'s\n * `persistDelta` (reusing `filesToParse`'s already-computed content-length data — no new git\n * call); reset to `\"0\"` by `finalize-pending-tier-b-batch.ts` whenever a Tier B batch is\n * finalized. Only counts files `isDiscoverableSourceFile` already lets through delta ingestion's\n * `toReparse` filter, so docs/binaries are excluded for free.\n */\n META_KEY_TIER_B_CHANGED_BYTES: \"tierBChangedBytes\",\n /**\n * Default Tier B commit-cap trigger threshold, in cumulative changed-file bytes (§9m item 1) —\n * config-tunable via `DOCUVIA_TIER_B_COMMIT_CAP` (read by the Presentation layer only, per the\n * `process.env` rule; the env var name is unchanged even though its unit changed from a commit\n * count to a byte count — phase1-decision-integration.md §9m frames this as \"the commit-cap\"\n * throughout, just with a different metric). Unvalidated: no measured drift-vs-batch-value\n * correlation exists yet. Picked to fire once a multi-file refactor's worth of source has\n * changed (dozens of files at a few KB each) without tripping on a typical single/few-file\n * commit; tune if real usage shows it's off.\n */\n DEFAULT_TIER_B_COMMIT_CAP_BYTES: 512_000,\n /**\n * Default Tier B workspace-wide coverage-fail threshold (dogfooding-findings-fixes.md Phase 2,\n * roadmap item 23) -- a fraction (not a percentage): `doctor`'s `tier_b_coverage` diagnostic\n * FAILs when `processedFiles / totalFiles` (`IFilesRepo.getTierBCoverage()`) falls below this.\n * Placeholder value, not derived from any prior measurement -- tune if real usage shows it's off.\n */\n DEFAULT_TIER_B_COVERAGE_FAIL_THRESHOLD: 0.5,\n\n /**\n * Default L2 semantic-coverage fail threshold (issue #135) -- a fraction (not a percentage):\n * `doctor`'s `l2_semantic_coverage` diagnostic FAILs when `describedNodes / totalNodes`\n * (`IGraphNodesRepo.getSemanticCoverage()`) falls below this. Deliberately low (0.1): Tier C\n * enrichment is expected to be sparse on a healthy graph (not every node needs a description),\n * but 0/6285 (issue #135's live state) must read as FAIL -- that's \"semantically dead\", not\n * \"normal\". Placeholder value, not derived from any prior measurement -- tune if real usage\n * shows it's off.\n */\n DEFAULT_L2_SEMANTIC_COVERAGE_FAIL_THRESHOLD: 0.1,\n\n /**\n * `docuvia_meta` key holding the count of *consecutive* Tier B batches that drained an\n * attemptable set and produced zero progress (0 files processed AND 0 edges applied) -- the\n * zero-progress watchdog's cross-batch accumulator (2026-08 moby benchmark follow-up, issue #22\n * split item 2 \"batch-deadline safety net\"). Written by `run-tier-b-batch` after every batch\n * that had something to process: any progress resets it to `\"0\"`, another zero-progress batch\n * increments it, and once it reaches `DEFAULT_TIER_B_ZERO_PROGRESS_MAX_BATCHES` the batch's\n * still-retryable `failedEntries` are treated as permanently-failed and dropped from the\n * re-queue -- the safety net for the \"same files hit their per-file deadline on every batch,\n * zero edges forever\" case the per-file `retryable: false` classification can't reach (those\n * files report `retryable: true` because the whole-batch deadline, not the file, cut them\n * short). Absent -> treated as `0`.\n */\n META_KEY_TIER_B_ZERO_PROGRESS_BATCHES: \"tierBZeroProgressBatches\",\n /**\n * Default Tier B zero-progress watchdog threshold (see `META_KEY_TIER_B_ZERO_PROGRESS_BATCHES`\n * above) -- consecutive zero-progress batches before the still-retryable remainder is declared\n * permanently-failed and dropped from the re-queue. Mirrors Tier C's own poison-pill cap\n * (`DEFAULT_TIER_C_MAX_ITEM_FAILURES: 3`) -- N matches the project's established \"three\n * consecutive tries then give up\" convention for queue entries that never progress. Not\n * config-tunable yet; a placeholder value like the commit-cap / coverage-threshold defaults --\n * tune if real usage shows it's off.\n */\n DEFAULT_TIER_B_ZERO_PROGRESS_MAX_BATCHES: 3,\n /**\n * Doctor's `post_commit_ingestion` grace window (issue #58), in milliseconds -- how recent an\n * `analyze.log` event must be for a `lastIngestedSourceSha !== HEAD` mismatch to be treated as\n * \"ingestion in flight / just ran\" (PASS-with-note) rather than \"the post-commit hook never\n * fires\" (FAIL). Tier A delta ingestion is designed to be sub-second, so ten minutes is a\n * deliberately generous bound against a just-committed-but-still-backgrounded run, not a\n * correctness requirement.\n */\n DEFAULT_POST_COMMIT_INGESTION_GRACE_MS: 600_000,\n\n PRE_PUSH_HOOK_NAME: \"pre-push\",\n /**\n * Fires the Tier B batch on push (phase1-decision-integration.md §8h, D7) — synchronous, with\n * a generous initial timeout (measure via JSONL logs before tightening, per the owner's\n * \"function first\" ruling). `docuvia snapshot` only runs when `analyze --escalate-to-lsp`\n * exits 0 (honest degradation exits 0 too, so a missing/unready LSP still lets the batch's\n * snapshot land). Present in both `PRE_PUSH_HOOK_CONTENT` and `LEGACY_PRE_PUSH_HOOK_CONTENT`\n * (below) — \"installed at all\" detection, not \"which version\" detection; use\n * `PRE_PUSH_SYNC_KNOWLEDGE_MARKER` to tell the two apart.\n */\n PRE_PUSH_HOOK_MARKER: \"docuvia analyze --escalate-to-lsp\",\n /**\n * Phase 2 sync-knowledge-scheduling.md SKSCHED-001/003: present only in the current\n * `PRE_PUSH_HOOK_CONTENT`, absent from `LEGACY_PRE_PUSH_HOOK_CONTENT` — the marker\n * `installPrePushHook` uses to tell an up-to-date hook from a pre-Phase-2 one that still needs\n * the in-place upgrade.\n */\n PRE_PUSH_SYNC_KNOWLEDGE_MARKER: \"docuvia sync-knowledge\",\n /**\n * Present only in the current `PRE_PUSH_HOOK_CONTENT`, absent from\n * `SYNC_KNOWLEDGE_PRE_PUSH_HOOK_CONTENT`/`LEGACY_PRE_PUSH_HOOK_CONTENT` — the marker\n * `installPrePushHook` uses to tell a hook that already opts `analyze --escalate-to-lsp` out of\n * D2's non-interactive hard-fail gate (`--fallback-ast` — phase1-decision-integration.md §8c,\n * the 2026-07 C#/TS benchmark environment-detection follow-up). Without this flag the pre-push\n * hook's own Tier B step would start failing the moment the LSP environment isn't ready, which\n * would skip `snapshot`/`sync-knowledge` for that push (the hook's own trailing `exit 0` still\n * keeps `git push` itself from being blocked either way).\n */\n PRE_PUSH_ENV_GATE_MARKER: \"--fallback-ast\",\n /**\n * `docuvia hooks disable tier-b-c-prepush`'s enforcement (issue #42 §7.5) -- present only in\n * the current `PRE_PUSH_HOOK_CONTENT`, absent from `ENV_GATE_PRE_PUSH_HOOK_CONTENT`/\n * `SYNC_KNOWLEDGE_PRE_PUSH_HOOK_CONTENT`/`LEGACY_PRE_PUSH_HOOK_CONTENT` -- the marker\n * `installPrePushHook` uses to tell a hook that already gates the batch on the `tier-b-c-prepush`\n * toggle from one still missing it.\n */\n PRE_PUSH_HOOKS_CHECK_MARKER: \"docuvia hooks check\",\n /**\n * Phase 2 sync-knowledge-scheduling.md SKSCHED-001: composes `sync-knowledge` onto the same\n * pre-push batch Tier B already occupies, after `snapshot` — reconciliation only makes sense\n * once a fresh local snapshot commit exists to reconcile. Wired here (not post-commit) so the\n * knowledge branch is fetched once per push, never once per commit (SKSCHED-001's whole reason\n * for picking this composition point over a second hook or a separate scheduler).\n */\n /** `> /dev/null 2>&1` (portable), not `&>` (bash-only) — see `POST_COMMIT_HOOK_CONTENT`'s doc\n * comment on why: husky's shim runs a redirected hook via `sh -e`, not bash.\n *\n * `docuvia hooks check tier-b-c-prepush &&` composed onto the front of the existing `&&` chain\n * (issue #42 §7.5): a genuine no-op gate, not a filter -- exiting `1` when disabled simply\n * short-circuits the rest of the chain (`analyze --escalate-to-lsp`/`snapshot`/`sync-knowledge`\n * never run that push), while the hook's own trailing `exit 0` (unchanged) still means `git\n * push` itself is never blocked either way. The toggle gates the automatic trigger, never the\n * underlying CLI capability -- a developer can still run `docuvia analyze --escalate-to-lsp`\n * manually at any time regardless of whether `tier-b-c-prepush` is disabled. */\n PRE_PUSH_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Tier B Batch Hook (LSP escalation + snapshot + knowledge sync)\\n` +\n `# Runs synchronously (generous timeout) so pushed code carries corrected knowledge -- see\\n` +\n `# docs/gitbook/analysis/phase1-decision-integration.md §8h and\\n` +\n `# docs/gitbook/analysis/phase2-sync-knowledge-scheduling.md.\\n` +\n `if command -v npx > /dev/null 2>&1; then\\n` +\n ` npx --no-install docuvia hooks check tier-b-c-prepush && npx --no-install docuvia analyze --escalate-to-lsp --fallback-ast && npx --no-install docuvia snapshot && npx --no-install docuvia sync-knowledge\\n` +\n `fi\\n` +\n `# Never blocks the push on a Tier B/sync-knowledge failure -- PLAT-007's reliability\\n` +\n `# requirement (failures only ever surface via JSONL logs / doctor, never to the pushing\\n` +\n `# developer).\\n` +\n `exit 0\\n`,\n /**\n * The pre-issue-#42 hook's exact content (with `--fallback-ast`, before `docuvia hooks check\n * tier-b-c-prepush` was composed onto the front of its `&&` chain) -- retained verbatim so\n * `installPrePushHook` can recognize a hook installed before that gate was added and replace it\n * in place, same technique as the `SYNC_KNOWLEDGE_PRE_PUSH_HOOK_CONTENT`/\n * `LEGACY_PRE_PUSH_HOOK_CONTENT` upgrades below.\n */\n ENV_GATE_PRE_PUSH_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Tier B Batch Hook (LSP escalation + snapshot + knowledge sync)\\n` +\n `# Runs synchronously (generous timeout) so pushed code carries corrected knowledge -- see\\n` +\n `# docs/gitbook/analysis/phase1-decision-integration.md §8h and\\n` +\n `# docs/gitbook/analysis/phase2-sync-knowledge-scheduling.md.\\n` +\n `if command -v npx > /dev/null 2>&1; then\\n` +\n ` npx --no-install docuvia analyze --escalate-to-lsp --fallback-ast && npx --no-install docuvia snapshot && npx --no-install docuvia sync-knowledge\\n` +\n `fi\\n` +\n `# Never blocks the push on a Tier B/sync-knowledge failure -- PLAT-007's reliability\\n` +\n `# requirement (failures only ever surface via JSONL logs / doctor, never to the pushing\\n` +\n `# developer).\\n` +\n `exit 0\\n`,\n /**\n * The sync-knowledge-era hook's exact content (SKSCHED-003), before `--fallback-ast` was added\n * to its `analyze --escalate-to-lsp` invocation -- retained verbatim so `installPrePushHook` can\n * recognize a hook installed before that flag was composed in and replace it in place, same\n * technique as the `LEGACY_PRE_PUSH_HOOK_CONTENT` upgrade below.\n */\n SYNC_KNOWLEDGE_PRE_PUSH_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Tier B Batch Hook (LSP escalation + snapshot + knowledge sync)\\n` +\n `# Runs synchronously (generous timeout) so pushed code carries corrected knowledge -- see\\n` +\n `# docs/gitbook/analysis/phase1-decision-integration.md §8h and\\n` +\n `# docs/gitbook/analysis/phase2-sync-knowledge-scheduling.md.\\n` +\n `if command -v npx > /dev/null 2>&1; then\\n` +\n ` npx --no-install docuvia analyze --escalate-to-lsp && npx --no-install docuvia snapshot && npx --no-install docuvia sync-knowledge\\n` +\n `fi\\n` +\n `# Never blocks the push on a Tier B/sync-knowledge failure -- PLAT-007's reliability\\n` +\n `# requirement (failures only ever surface via JSONL logs / doctor, never to the pushing\\n` +\n `# developer).\\n` +\n `exit 0\\n`,\n /**\n * The pre-Phase-2 hook's exact content, retained verbatim so `installPrePushHook` can recognize\n * a hook installed before the `sync-knowledge` step was composed in and replace it in place\n * (phase2-sync-knowledge-scheduling.md SKSCHED-003) rather than appending a second, duplicate\n * Docuvia block alongside the old one — mirrors `LEGACY_POST_COMMIT_HOOK_CONTENT`'s precedent.\n */\n LEGACY_PRE_PUSH_HOOK_CONTENT:\n `#!/bin/bash\\n# Docuvia Tier B Batch Hook (LSP escalation + snapshot)\\n` +\n `# Runs synchronously (generous timeout) so pushed code carries corrected knowledge -- see\\n` +\n `# docs/gitbook/analysis/phase1-decision-integration.md §8h.\\n` +\n `if command -v npx &> /dev/null; then\\n` +\n ` npx --no-install docuvia analyze --escalate-to-lsp && npx --no-install docuvia snapshot\\n` +\n ` # Phase 2: a sync-knowledge step composes here -- must not double-fetch (see §7a-5).\\n` +\n `fi\\n` +\n `# Never blocks the push on a Tier B failure -- PLAT-007's reliability requirement (failures\\n` +\n `# only ever surface via JSONL logs / doctor, never to the pushing developer).\\n` +\n `exit 0\\n`,\n\n /**\n * `docuvia_meta` key holding a JSON array of Tier C candidates (phase1-decision-integration.md\n * §9c, E2) -- deduped by `target` (a commit sha for commit-message candidates, a `node_key` for\n * `CONTRACT_CHANGED` symbol candidates). Enqueued by Tier A's delta ingestion (the same\n * `runDeltaIngestion` step that populates `tierBQueue`); drained by `analyze --escalate-to-lsp`\n * (the same pre-push composition Tier B drains from), item-by-item, each item dequeued only\n * once its extraction is durably persisted to `l3_nodes` (§9c's \"same stage-then-finalize\n * discipline as Tier B\" -- adapted to Tier C's own unit of durability, since L3 rows are\n * written immediately and don't wait on a snapshot the way Tier B's edges do).\n */\n META_KEY_TIER_C_QUEUE: \"tierCQueue\",\n /**\n * `docuvia_meta` key holding a JSON `{ date, calls, tokens }` record (phase1-decision-integration.md\n * §9c, E2) -- `date` is a UTC `YYYY-MM-DD` stamp. Reset is lazy: on every read, if `date` is not\n * today (UTC), the counters are treated as zero before any budget check, rather than a\n * scheduled/timer-driven reset (this project has no resident process to run one).\n */\n META_KEY_TIER_C_BUDGET: \"tierCBudget\",\n /** Default Tier C daily LLM call-budget cap (§9c/§9f) -- config-tunable via\n * `DOCUVIA_TIER_C_DAILY_CALL_CAP` (read by the Presentation layer only). */\n DEFAULT_TIER_C_DAILY_CALL_CAP: 50,\n /** Default Tier C daily estimated-token-budget cap (§9c/§9f) -- config-tunable via\n * `DOCUVIA_TIER_C_DAILY_TOKEN_CAP`. Tokens are estimated (the CLIProxyAPI bridge's wire format\n * carries no `usage` field today), not read back from the provider -- see\n * `tier-c-token-estimate.ts`'s doc comment. */\n DEFAULT_TIER_C_DAILY_TOKEN_CAP: 100_000,\n /** Default Tier C per-run wall-clock cap, in milliseconds (§9d) -- config-tunable via\n * `DOCUVIA_TIER_C_WALL_CLOCK_MS`. Whichever of this or `DEFAULT_TIER_C_ITEM_CAP` binds first\n * stops the drain; leftovers stay queued for the next run. */\n DEFAULT_TIER_C_WALL_CLOCK_MS: 12_000,\n /** Default Tier C per-run item-count cap (§9d) -- config-tunable via `DOCUVIA_TIER_C_ITEM_CAP`. */\n DEFAULT_TIER_C_ITEM_CAP: 20,\n /** Default Tier C system-load-check threshold (§9f) -- `os.loadavg()[0] / os.cpus().length`\n * above this skips the drain. Config-tunable via `DOCUVIA_TIER_C_LOAD_THRESHOLD`. A documented\n * no-op on Windows (`os.loadavg()` always returns zeros there) -- see `tier-c-throttle.ts`. */\n DEFAULT_TIER_C_LOAD_THRESHOLD: 0.8,\n /** Default retry budget (consecutive per-item extraction failures, across `analyze` runs)\n * before a permanently-failing Tier C queue entry is evicted instead of blocking every item\n * behind it forever -- `recordTierCQueueFailure`'s poison-pill cap. */\n DEFAULT_TIER_C_MAX_ITEM_FAILURES: 3,\n} as const;\n","import path from \"path\";\nimport { SUPPORTED_LANGUAGES } from \"./languages.js\";\nimport type { SupportedLanguage } from \"./languages.js\";\n\n// ---------------------------------------------------------------------------\n// Per-language extension arrays — the canonical definitions. These were\n// previously scattered across lib/ast-core/src/languages/*.ts; moving them\n// here means there is ONE list to maintain and every layer derives from it.\n// ---------------------------------------------------------------------------\n\nexport const TYPESCRIPT_EXTENSIONS = [\".ts\", \".tsx\", \".mts\", \".cts\"];\nexport const JAVASCRIPT_EXTENSIONS = [\".js\", \".jsx\", \".mjs\", \".cjs\"];\nexport const PYTHON_EXTENSIONS = [\".py\"];\nexport const RUST_EXTENSIONS = [\".rs\"];\nexport const GO_EXTENSIONS = [\".go\"];\nexport const JAVA_EXTENSIONS = [\".java\"];\nexport const C_EXTENSIONS = [\".c\", \".h\"];\nexport const CPP_EXTENSIONS = [\n \".cpp\",\n \".cxx\",\n \".cc\",\n \".hpp\",\n \".hxx\",\n \".hh\",\n \".cu\",\n \".cuh\",\n];\nexport const RUBY_EXTENSIONS = [\".rb\", \".rake\", \".gemspec\"];\nexport const PHP_EXTENSIONS = [\n \".php\",\n \".phtml\",\n \".php3\",\n \".php4\",\n \".php5\",\n \".phps\",\n];\nexport const CSHARP_EXTENSIONS = [\".cs\"];\n\n/**\n * Ruby project files that conventionally carry no extension. `path.extname()`\n * can never match these, so they need an explicit basename allowlist.\n */\nexport const RUBY_EXTENSIONLESS_BASENAMES = new Set([\n \"Rakefile\",\n \"Gemfile\",\n \"Guardfile\",\n \"Vagrantfile\",\n \"Brewfile\",\n]);\n\n// ---------------------------------------------------------------------------\n// Derived extension → language map (single source of truth, not hand-copied)\n// ---------------------------------------------------------------------------\n\nconst EXT_TO_LANGUAGE: Map<string, SupportedLanguage> = (() => {\n const map = new Map<string, SupportedLanguage>();\n const entries: [SupportedLanguage, string[]][] = [\n [SUPPORTED_LANGUAGES.TYPESCRIPT, TYPESCRIPT_EXTENSIONS],\n [SUPPORTED_LANGUAGES.JAVASCRIPT, JAVASCRIPT_EXTENSIONS],\n [SUPPORTED_LANGUAGES.PYTHON, PYTHON_EXTENSIONS],\n [SUPPORTED_LANGUAGES.RUST, RUST_EXTENSIONS],\n [SUPPORTED_LANGUAGES.GO, GO_EXTENSIONS],\n [SUPPORTED_LANGUAGES.JAVA, JAVA_EXTENSIONS],\n [SUPPORTED_LANGUAGES.C, C_EXTENSIONS],\n [SUPPORTED_LANGUAGES.CPP, CPP_EXTENSIONS],\n [SUPPORTED_LANGUAGES.RUBY, RUBY_EXTENSIONS],\n [SUPPORTED_LANGUAGES.PHP, PHP_EXTENSIONS],\n [SUPPORTED_LANGUAGES.CSHARP, CSHARP_EXTENSIONS],\n ];\n for (const [languageName, extensions] of entries) {\n for (const ext of extensions) map.set(ext, languageName);\n }\n return map;\n})();\n\n/**\n * Returns the language name for a file (e.g. `\"typescript\"`), or `undefined`\n * if the file's extension (or basename) is not recognised.\n */\nexport function detectLanguageForFile(\n filePath: string,\n): SupportedLanguage | undefined {\n const byExt = EXT_TO_LANGUAGE.get(path.extname(filePath).toLowerCase());\n if (byExt) return byExt;\n return RUBY_EXTENSIONLESS_BASENAMES.has(path.basename(filePath))\n ? SUPPORTED_LANGUAGES.RUBY\n : undefined;\n}\n\n/**\n * `true` when the AST layer recognises this file's extension (or basename) —\n * i.e. the file *can* be parsed. Uses `path.basename()` for cross-platform\n * correctness (the previous implementation in core used `lastIndexOf(\"/\")`\n * which broke on Windows backslash paths).\n */\nexport function isSupportedSourceFile(filePath: string): boolean {\n if (EXT_TO_LANGUAGE.has(path.extname(filePath).toLowerCase())) return true;\n return RUBY_EXTENSIONLESS_BASENAMES.has(path.basename(filePath));\n}\n\n/**\n * Extensions with the leading dot stripped, for building fast-glob brace\n * patterns.\n */\nexport function getSupportedGlobExtensions(): string[] {\n return Array.from(EXT_TO_LANGUAGE.keys()).map((ext) =>\n ext.replace(/^\\./, \"\"),\n );\n}\n","/**\n * Plain string constants shared across layers for file system operations, lockfile flags,\n * Node.js system error codes, and platform strings.\n */\n\n/** Node.js `fs.open` flag: fail (`EEXIST`) instead of overwriting if the path already exists — exclusive create mode. */\nexport const FS_FLAG_EXCLUSIVE_CREATE_WRITE = \"wx\" as const;\n\n/** System error code reported by Node.js `fs.open(path, \"wx\")` when `path` already exists. */\nexport const ERRNO_EEXIST = \"EEXIST\" as const;\n\n/** System error code reported by Node.js operations when permission is denied or a resource is locked. */\nexport const ERRNO_EPERM = \"EPERM\" as const;\n\n/** System error code reported by Node.js operations when file access is forbidden. */\nexport const ERRNO_EACCES = \"EACCES\" as const;\n\n/** System error code reported by Node.js operations when a resource/device is busy. */\nexport const ERRNO_EBUSY = \"EBUSY\" as const;\n\n/** Node.js `process.platform` value for Windows operating systems. */\nexport const PLATFORM_WIN32 = \"win32\" as const;\n","import fs from \"node:fs/promises\";\nimport { UTF8_ENCODING } from \"../constants/encoding.js\";\nimport {\n FS_FLAG_EXCLUSIVE_CREATE_WRITE,\n ERRNO_EEXIST,\n ERRNO_EPERM,\n ERRNO_EACCES,\n ERRNO_EBUSY,\n} from \"../constants/fs.js\";\n\nconst ProcessLockErrorMessages = {\n TIMED_OUT_WAITING: (lockPath: string) =>\n `Timed out waiting for the lock at ${lockPath} — another process may be stuck`,\n} as const;\n\n/** Tunables for {@link acquireProcessLock}; all have defaults, override per call site. */\nexport interface ProcessLockOptions {\n /** How long to wait for the lock before throwing, in ms. */\n maxWaitMs: number;\n /** Poll interval while waiting for the lock to free up, in ms. */\n retryIntervalMs: number;\n /** How often the holder refreshes the lockfile's mtime while it works, in ms. */\n heartbeatIntervalMs: number;\n /**\n * A lock is only reclaimed as abandoned once its mtime has been stale for this long AND its\n * recorded PID is no longer alive (see `isProcessAlive`) — mtime alone can't distinguish a\n * crashed holder from a live one whose heartbeat is merely delayed under load.\n */\n staleAfterMs: number;\n /** Called once, the first time this call finds the lock already held by another process. */\n onWaiting?: () => void;\n}\n\nexport interface ProcessLockHandle {\n /** Idempotent — safe to call more than once. Stops the heartbeat and removes the lockfile. */\n release(): Promise<void>;\n}\n\nconst DEFAULT_OPTIONS: ProcessLockOptions = {\n maxWaitMs: 10_000,\n retryIntervalMs: 100,\n heartbeatIntervalMs: 10_000,\n staleAfterMs: 30_000,\n};\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** `process.kill(pid, 0)` throws ESRCH if the PID is gone, but EPERM if it exists and we just\n * lack permission to signal it — either way EPERM means \"alive\". */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === ERRNO_EPERM;\n }\n}\n\nasync function readLockPid(lockPath: string): Promise<number | undefined> {\n try {\n const pid = Number.parseInt(await fs.readFile(lockPath, UTF8_ENCODING), 10);\n return Number.isFinite(pid) ? pid : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function removeStaleLockIfAbandoned(\n lockPath: string,\n staleAfterMs: number,\n): Promise<boolean> {\n const stat = await fs.stat(lockPath).catch(() => undefined);\n if (!stat || Date.now() - stat.mtimeMs <= staleAfterMs) return false;\n\n const pid = await readLockPid(lockPath);\n if (pid !== undefined && isProcessAlive(pid)) return false;\n\n await fs.rm(lockPath, { force: true }).catch(() => {});\n return true;\n}\n\nfunction isRetryableLockError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException).code;\n return (\n code === ERRNO_EEXIST ||\n code === ERRNO_EPERM ||\n code === ERRNO_EACCES ||\n code === ERRNO_EBUSY\n );\n}\n\nasync function tryCreateLockFile(lockPath: string): Promise<boolean> {\n try {\n const handle = await fs.open(lockPath, FS_FLAG_EXCLUSIVE_CREATE_WRITE);\n await handle.writeFile(String(process.pid));\n await handle.close();\n return true;\n } catch (err) {\n if (!isRetryableLockError(err)) throw err;\n return false;\n }\n}\n\n/**\n * Cross-process mutex backed by an exclusively-created (`wx`) lockfile containing the holder's\n * PID — the same shape as the ad hoc locks in `graph-store.ts`'s `acquireInitLock` and\n * `git-local-provider.ts`'s `acquireKnowledgeLock` (see PLAT-006), generalized with a heartbeat so\n * it's safe to hold across a long-running operation (not just a sub-second DB bootstrap): the\n * holder periodically touches the lockfile's mtime, and waiters only reclaim it as abandoned once\n * both the mtime is stale *and* the recorded PID is confirmed dead.\n */\nexport async function acquireProcessLock(\n lockPath: string,\n options: Partial<ProcessLockOptions> = {},\n): Promise<ProcessLockHandle> {\n const opts: ProcessLockOptions = { ...DEFAULT_OPTIONS, ...options };\n const deadline = Date.now() + opts.maxWaitMs;\n let notifiedWaiting = false;\n\n for (;;) {\n if (await tryCreateLockFile(lockPath)) break;\n\n if (!notifiedWaiting) {\n notifiedWaiting = true;\n options.onWaiting?.();\n }\n\n if (await removeStaleLockIfAbandoned(lockPath, opts.staleAfterMs)) continue;\n\n if (Date.now() > deadline) {\n throw new Error(ProcessLockErrorMessages.TIMED_OUT_WAITING(lockPath));\n }\n await sleep(opts.retryIntervalMs);\n }\n\n const heartbeat = setInterval(() => {\n const now = new Date();\n fs.utimes(lockPath, now, now).catch(() => {});\n }, opts.heartbeatIntervalMs);\n heartbeat.unref?.();\n\n let released = false;\n return {\n async release(): Promise<void> {\n if (released) return;\n released = true;\n clearInterval(heartbeat);\n await fs.rm(lockPath, { force: true }).catch(() => {});\n },\n };\n}\n","import { GitConstants } from \"../constants/git-conventions.js\";\n\nconst SOURCE_TRAILER_PREFIX = `${GitConstants.SOURCE_COMMIT_TRAILER_KEY}: `;\n\n/** Extracts the `Docuvia-Source: <sha>` trailer (STOR-001 point 4) from a commit message body,\n * or undefined if absent (e.g. the `Snapshot [unknown]` fallback message on an unborn source\n * HEAD). Pure helper shared by `lib/core`'s hydration/snapshot services and `lib/ui-core`'s\n * `analyze` workflow — lives in contracts (utils precedent: `process-lock.ts`) so both layers\n * read the same trailer without a `lib/ui-core` -> `lib/core` import (Virtual Contracts §8). */\nexport function parseSourceTrailer(message: string): string | undefined {\n for (const line of message.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.startsWith(SOURCE_TRAILER_PREFIX)) {\n return trimmed.slice(SOURCE_TRAILER_PREFIX.length).trim();\n }\n }\n return undefined;\n}\n","import type { IGraphStore } from \"../interfaces/graph-store.interfaces.js\";\nimport type { TierBCoverageHint } from \"../interfaces/query.interfaces.js\";\n\n/**\n * §5.3/§5.7 item 2's \"not yet processed\" hint. Returns `undefined` when there's nothing\n * ambiguous to report for either direction (see call sites for what counts as \"empty\" per\n * direction) -- callers must treat `undefined` as \"omit the field entirely,\" not \"empty object.\"\n *\n * Pure store-read helper reused by both `lib/core`'s `QueryService.getContext()` and\n * `lib/ui-core`'s `ImpactWorkflow` — lives in contracts (utils precedent: `process-lock.ts`)\n * so the Orchestration layer doesn't import `lib/core` directly (Virtual Contracts §8).\n */\nexport function resolveTierBCoverageHint(\n store: IGraphStore,\n ownFilePath: string | undefined,\n incomingEmpty: boolean,\n outgoingEmpty: boolean,\n): TierBCoverageHint | undefined {\n if (!incomingEmpty && !outgoingEmpty) return undefined;\n\n const ownFileStatus = ownFilePath\n ? store.files.getTierBFileStatus(ownFilePath)\n : undefined;\n const ownFileLastProcessedAt = ownFileStatus?.lastProcessedAt ?? null;\n const needsOutgoingHint = outgoingEmpty && ownFileLastProcessedAt === null;\n\n const coverage = store.files.getTierBCoverage();\n const needsIncomingHint =\n incomingEmpty && coverage.processedFiles < coverage.totalFiles;\n\n if (!needsOutgoingHint && !needsIncomingHint) return undefined;\n\n return {\n ownFileLastProcessedAt,\n workspaceFilesProcessed: coverage.processedFiles,\n workspaceFilesTotal: coverage.totalFiles,\n };\n}\n","export const DiagnosticStatus = {\n PASS: \"pass\",\n FAIL: \"fail\",\n} as const;\n\nexport type DiagnosticStatusType =\n (typeof DiagnosticStatus)[keyof typeof DiagnosticStatus];\n\nexport interface DiagnosticResult {\n status: DiagnosticStatusType;\n message: string;\n details?: string;\n suggestion?: string;\n}\n\nexport interface IDiagnosticRunner {\n checkHealth(cwd: string): Promise<Record<string, DiagnosticResult>>;\n}\n"],"mappings":"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","names":["LogLevels","IpcLogMessageType","isIpcLogMessage","value","IpcLoggerClient","postMessageFn","message","context","LogLevels","level","payload","IpcLogMessageType","HookNames","DEFAULT_HOOKS_CONFIG","DOCUVIA_DIR_NAME","DOCUVIA_LOGS_DIR_NAME","INIT_LOG_FILE_NAME","CLEAN_LOG_FILE_NAME","STATUS_LOG_FILE_NAME","SYNC_LOG_FILE_NAME","ANALYZE_LOG_FILE_NAME","REVIEW_LOG_FILE_NAME","IMPACT_LOG_FILE_NAME","QUERY_LOG_FILE_NAME","EXPORT_TOPOLOGY_LOG_FILE_NAME","SNAPSHOT_LOG_FILE_NAME","HYDRATE_LOG_FILE_NAME","SYNC_KNOWLEDGE_LOG_FILE_NAME","NODE_MODULES_DIR_NAME","LOCAL_DB_FILE_NAME","INIT_COMMAND_LOCK_FILE_NAME","TIER_C_LOCK_FILE_NAME","SYNC_STATE_FILE_NAME","HOOKS_CONFIG_FILE_NAME","PENDING_L3_DECISIONS_FILE_NAME","UTF8_ENCODING","SUPPORTED_LANGUAGES","CLAUDE_HOOKS_DIR","CURSOR_HOOKS_DIR","DOCUVIA_HOOK_JS_FILENAME","DOCUVIA_HOOK_CJS_FILENAME","Logger","message","context","LogLevels","listener","level","event","createNoopLogger","IpcLogRouter","logger","message","isIpcLogMessage","ErrorCodes","DOCUVIA_ERROR_NAME","DocuviaError","_DocuviaError","code","message","cause","causeMessage","MemoryKeys","MemoryErrorMessages","key","scopeId","DocuviaMemory","value","scope","DocuviaError","ErrorCodes","docuviaMemory","FactoryErrorMessages","description","DocuviaFactory","token","provider","DocuviaError","ErrorCodes","params","docuviaFactory","createToken","description","TOKENS","ChangedFileStatuses","EdgeResolutionSources","TIER_B_LANGUAGE_IDS","KnowledgeBranchSyncStatuses","L2NodeTypes","LinkTypes","ValidityStatuses","L3DecisionSources","SyncPushEventTypes","ChatMessageRoles","RiskLevels","QueryResultLayers","TopologyNodeKinds","TopologyCollapseModes","TopologyGroupSources","ConfigTags","ProjectTypes","GENERAL_TAG","ConfigDetectionTags","GIT_DEFAULT_REMOTE_NAME","GitConstants","path","TYPESCRIPT_EXTENSIONS","JAVASCRIPT_EXTENSIONS","PYTHON_EXTENSIONS","RUST_EXTENSIONS","GO_EXTENSIONS","JAVA_EXTENSIONS","C_EXTENSIONS","CPP_EXTENSIONS","RUBY_EXTENSIONS","PHP_EXTENSIONS","CSHARP_EXTENSIONS","RUBY_EXTENSIONLESS_BASENAMES","EXT_TO_LANGUAGE","map","entries","SUPPORTED_LANGUAGES","languageName","extensions","ext","detectLanguageForFile","filePath","byExt","path","isSupportedSourceFile","getSupportedGlobExtensions","ERRNO_EEXIST","ERRNO_EPERM","ERRNO_EACCES","ERRNO_EBUSY","fs","ProcessLockErrorMessages","lockPath","DEFAULT_OPTIONS","sleep","ms","resolve","isProcessAlive","pid","err","ERRNO_EPERM","readLockPid","fs","UTF8_ENCODING","removeStaleLockIfAbandoned","staleAfterMs","stat","isRetryableLockError","code","ERRNO_EEXIST","ERRNO_EACCES","ERRNO_EBUSY","tryCreateLockFile","handle","acquireProcessLock","options","opts","deadline","notifiedWaiting","heartbeat","now","released","SOURCE_TRAILER_PREFIX","GitConstants","parseSourceTrailer","message","line","trimmed","resolveTierBCoverageHint","store","ownFilePath","incomingEmpty","outgoingEmpty","ownFileLastProcessedAt","needsOutgoingHint","coverage","needsIncomingHint","DiagnosticStatus"]}
@@ -0,0 +1,82 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+ import{A as e,Aa as t,C as o,V as n}from"./chunk-QD6NQB77.js";var _=".github",u=".claude",d="hooks.json",O="settings.json",E=".cursor/mcp.json",h="claude_desktop_config.json",x="docuvia-local",A="copilot-instructions.md",N="CLAUDE.md",R=".cursorrules",r="docuvia:start",g="docuvia:end",S=".windsurfrules",I="llms.txt",L="AGENTS.md",C=".hermes.md",m=".continue/rules",f="docuvia.md",T="Cursor",U="Claude",M="GitHub Copilot",D="Codex",y="Continue",F="Hermes Agent",b="cursor",v="claude",P="copilot",G="codex",w="continue",H="hermes",K="${CLAUDE_PLUGIN_ROOT}/hooks",k="${CURSOR_PLUGIN_ROOT}/hooks",J="${CLAUDE_PROJECT_DIR}/"+t,q="npx",V="-y",$="--no-install",j=["docuvia","mcp"],X="DOCUVIA_WORKSPACE_ROOT",B=`#!/usr/bin/env node
3
+ /**
4
+ * Docuvia Agent Hook
5
+ * Intercepts AI searches and augments with high-density AST context from local SQLite.
6
+ */
7
+ const { execFileSync } = require('child_process');
8
+
9
+ function readInput() {
10
+ try {
11
+ const data = require('fs').readFileSync(0, 'utf-8');
12
+ return JSON.parse(data);
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+
18
+ // \`docuvia hooks disable ${e.CONTEXT_INJECTION}\`'s enforcement (issue #42 \xA77.5) -- a
19
+ // plain synchronous read, no subprocess: this hook fires on every Grep/Glob/Bash/Read, so a
20
+ // second \`npx docuvia ...\` spawn per call would be a real, continuous latency cost, not a
21
+ // one-off. Missing/unparseable config -> enabled (fail open, matches the always-on behavior this
22
+ // toggle is retrofitted onto).
23
+ function isEnabled() {
24
+ try {
25
+ const config = JSON.parse(
26
+ require('fs').readFileSync('${o}/${n}', 'utf-8'),
27
+ );
28
+ return config['${e.CONTEXT_INJECTION}'] !== false;
29
+ } catch {
30
+ return true;
31
+ }
32
+ }
33
+
34
+ const input = readInput();
35
+ // Use the query arguments as the target to retrieve context
36
+ const target = input.args ? input.args.query || input.args.pattern : null;
37
+
38
+ if (target && isEnabled()) {
39
+ try {
40
+ // Call the local Docuvia CLI to retrieve exact L2/L3 structural context. target is passed as a
41
+ // literal argv element via execFileSync (no shell) instead of string-interpolated into an
42
+ // execSync shell command (issue #51): target is tool-call input (Grep/Glob/Bash/Read
43
+ // query/pattern) that an agent or, transitively, a prompt can influence, so the old
44
+ // interpolation was a real shell-injection exposure. npx is a .cmd shim on Windows that
45
+ // execFileSync can't spawn as a bare name, so resolve the platform-specific name inline (this
46
+ // standalone script can't import windows-shell-spawn.ts).
47
+ const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
48
+ const context = execFileSync(
49
+ npx,
50
+ ['--no-install', 'docuvia', 'query', target, '--format=prompt'],
51
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
52
+ );
53
+ if (context && context.trim().length > 0) {
54
+ console.log("=== Docuvia Context injected ===");
55
+ console.log(context);
56
+ console.log("================================");
57
+ }
58
+ } catch (e) {
59
+ const errorMsg = e instanceof Error ? e.message : String(e);
60
+ console.error("[Docuvia Pre-Command] Failed to retrieve context: " + errorMsg);
61
+ }
62
+ }
63
+ `,s="PreToolUse",W=JSON.stringify({[s]:[{hooks:[{command:"node ${HOOKS_DIR}/docuvia-hook.js",statusMessage:"Enriching with Docuvia architectural context...",timeout:5,type:"command"}],matcher:"Grep|Glob|Bash|Read"}]},null,2),z=`
64
+ <!-- ${r} -->
65
+ # Docuvia \u2014 Codebase Knowledge Evolver
66
+
67
+ This project uses Docuvia to manage architectural context and prevent blast-radius regressions.
68
+ Grep/Glob/Read are the most expensive tools available to you \u2014 before reaching for them to explore the codebase, query the local knowledge graph instead, and before editing a symbol or file, check its blast radius:
69
+
70
+ Run: \`npx --no-install docuvia query "<concept_or_file>" --format=prompt\`
71
+ Run: \`npx --no-install docuvia impact <symbolOrFile>\`
72
+
73
+ Use the results to understand architectural boundaries, historical decisions, and potential blast radius before modifying code. Only fall back to Grep/Glob/Read when the graph returns nothing, the target is flagged \`tier_b_status="unprocessed"\` (unknown, not zero), you need exact source text/formatting a structural query can't capture, or \`query\` returns a non-\`exact\` \`match_type\` (keyword/neighbor) for what should be a well-known symbol or file.
74
+
75
+ After making a code change that reflects a real architectural decision, rule, or notable rationale, stage it so the graph picks it up without a separate write step:
76
+
77
+ Run: \`npx --no-install docuvia analyze <file> --agent-authored --stage\`
78
+
79
+ Pipe a JSON payload on stdin (default) \u2014 \`{"decisions":[{"title":string,"content":string,"nodeType":"change"|"rule"|"decision"|"context","confidence":number}]}\` \u2014 or pass \`--decisions-file=<path>\` instead. Put \`--agent-authored\`/\`--stage\` after the positional \`<file>\`, not before \u2014 a flag preceding the path silently swallows it as the flag's own value. Staged decisions flush into the knowledge graph automatically the next time you commit a change touching that file \u2014 nothing else to run.
80
+ <!-- docuvia:end -->
81
+ `;export{_ as a,u as b,d as c,O as d,E as e,h as f,x as g,A as h,N as i,R as j,r as k,g as l,S as m,I as n,L as o,C as p,m as q,f as r,T as s,U as t,M as u,D as v,y as w,F as x,b as y,v as z,P as A,G as B,w as C,H as D,K as E,k as F,J as G,q as H,V as I,$ as J,j as K,X as L,B as M,s as N,W as O,z as P};
82
+ //# sourceMappingURL=chunk-W6H4BYG4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants/init-templates.ts"],"sourcesContent":["// Shared with lib/ui-core's DoctorWorkflow agent-hooks diagnostic -- defined in\n// @workspace/contracts so neither side depends on the other; re-exported here so this file\n// stays the single import path\n// every platform installer already uses. Also imported (not just re-exported) below so\n// CLAUDE_PROJECT_HOOKS_DIR can build off the same value instead of re-typing it.\nimport { CLAUDE_HOOKS_DIR } from \"@workspace/contracts\";\nexport {\n CLAUDE_HOOKS_DIR,\n CURSOR_HOOKS_DIR,\n DOCUVIA_HOOK_JS_FILENAME,\n DOCUVIA_HOOK_CJS_FILENAME,\n} from \"@workspace/contracts\";\n// `context-injection`'s enforcement gate (issue #42 §7.5): this file is itself normal\n// TypeScript, so it can import the shared literal values and interpolate them into the\n// generated `.js` template text at compile time -- the *generated* raw hook script on disk just\n// contains the resolved strings, no runtime import (a standalone `.js` hook file has no\n// `@workspace/contracts` to import from).\nimport {\n HookNames,\n HOOKS_CONFIG_FILE_NAME,\n DOCUVIA_DIR_NAME,\n} from \"@workspace/contracts\";\n\nexport const GITHUB_DIR = \".github\";\nexport const CLAUDE_DIR = \".claude\";\n\nexport const HOOKS_CONFIG_FILENAME = \"hooks.json\";\nexport const SETTINGS_JSON_FILENAME = \"settings.json\";\n\nexport const CURSOR_MCP_CONFIG_PATH = \".cursor/mcp.json\";\nexport const CLAUDE_DESKTOP_CONFIG_FILENAME = \"claude_desktop_config.json\";\nexport const MCP_SERVER_ALIAS = \"docuvia-local\";\n\nexport const COPILOT_INSTRUCTIONS_FILENAME = \"copilot-instructions.md\";\nexport const CLAUDE_MD_FILENAME = \"CLAUDE.md\";\nexport const CURSOR_RULES_FILENAME = \".cursorrules\";\nexport const AGENT_INSTRUCTIONS_MARKER = \"docuvia:start\";\nexport const AGENT_INSTRUCTIONS_END_MARKER = \"docuvia:end\";\n\n// PLAT-008 legacy-only: these two files were written by the retired \"Markdown Agents\" catch-all\n// and are never installed to anymore, but `uninstall` still best-effort cleans them up for repos\n// set up under an older Docuvia version.\nexport const WINDSURF_RULES_FILENAME = \".windsurfrules\";\nexport const LLMS_TXT_FILENAME = \"llms.txt\";\n\nexport const AGENTS_MD_FILENAME = \"AGENTS.md\";\nexport const HERMES_MD_FILENAME = \".hermes.md\";\nexport const CONTINUE_RULES_DIR = \".continue/rules\";\nexport const CONTINUE_RULES_FILENAME = \"docuvia.md\";\n\nexport const PLATFORM_NAME_CURSOR = \"Cursor\";\nexport const PLATFORM_NAME_CLAUDE = \"Claude\";\nexport const PLATFORM_NAME_COPILOT = \"GitHub Copilot\";\nexport const PLATFORM_NAME_CODEX = \"Codex\";\nexport const PLATFORM_NAME_CONTINUE = \"Continue\";\nexport const PLATFORM_NAME_HERMES = \"Hermes Agent\";\n\n// Stable, CLI-facing identifiers for --platform= — PLATFORM_NAME_* above is the display name only.\nexport const PLATFORM_SLUG_CURSOR = \"cursor\";\nexport const PLATFORM_SLUG_CLAUDE = \"claude\";\nexport const PLATFORM_SLUG_COPILOT = \"copilot\";\nexport const PLATFORM_SLUG_CODEX = \"codex\";\nexport const PLATFORM_SLUG_CONTINUE = \"continue\";\nexport const PLATFORM_SLUG_HERMES = \"hermes\";\n\n// Literal placeholder text written into each platform's hooks.json `${HOOKS_DIR}` substitution —\n// the platform itself expands these at runtime, so they must stay un-interpolated here.\nexport const CLAUDE_PLUGIN_HOOKS_DIR = \"${CLAUDE_PLUGIN_ROOT}/hooks\";\nexport const CURSOR_PLUGIN_HOOKS_DIR = \"${CURSOR_PLUGIN_ROOT}/hooks\";\n\n// Project-level equivalent of CLAUDE_PLUGIN_HOOKS_DIR above. `${CLAUDE_PLUGIN_ROOT}` only resolves\n// inside a formal Claude Code plugin install (and is currently broken there too --\n// anthropics/claude-code#24529); `${CLAUDE_PROJECT_DIR}` resolves correctly today in a project-level\n// `.claude/settings.json` hook, so ClaudePlatform also writes a hook entry through this path,\n// pointing at the same `.claude/hooks` dir `configureHooks` already writes `docuvia-hook.js` into\n// (roadmap-and-open-items.md item 26).\nexport const CLAUDE_PROJECT_HOOKS_DIR =\n \"${CLAUDE_PROJECT_DIR}/\" + CLAUDE_HOOKS_DIR;\n\nexport const NPX_COMMAND = \"npx\";\nexport const NPX_YES_FLAG = \"-y\";\nexport const NPX_NO_INSTALL_FLAG = \"--no-install\";\nexport const DOCUVIA_MCP_LAUNCH_ARGS = [\"docuvia\", \"mcp\"];\nexport const DOCUVIA_WORKSPACE_ROOT_ENV_VAR = \"DOCUVIA_WORKSPACE_ROOT\";\n\nexport const DOCUVIA_HOOK_JS = `#!/usr/bin/env node\n/**\n * Docuvia Agent Hook\n * Intercepts AI searches and augments with high-density AST context from local SQLite.\n */\nconst { execFileSync } = require('child_process');\n\nfunction readInput() {\n try {\n const data = require('fs').readFileSync(0, 'utf-8');\n return JSON.parse(data);\n } catch {\n return {};\n }\n}\n\n// \\`docuvia hooks disable ${HookNames.CONTEXT_INJECTION}\\`'s enforcement (issue #42 §7.5) -- a\n// plain synchronous read, no subprocess: this hook fires on every Grep/Glob/Bash/Read, so a\n// second \\`npx docuvia ...\\` spawn per call would be a real, continuous latency cost, not a\n// one-off. Missing/unparseable config -> enabled (fail open, matches the always-on behavior this\n// toggle is retrofitted onto).\nfunction isEnabled() {\n try {\n const config = JSON.parse(\n require('fs').readFileSync('${DOCUVIA_DIR_NAME}/${HOOKS_CONFIG_FILE_NAME}', 'utf-8'),\n );\n return config['${HookNames.CONTEXT_INJECTION}'] !== false;\n } catch {\n return true;\n }\n}\n\nconst input = readInput();\n// Use the query arguments as the target to retrieve context\nconst target = input.args ? input.args.query || input.args.pattern : null;\n\nif (target && isEnabled()) {\n try {\n // Call the local Docuvia CLI to retrieve exact L2/L3 structural context. target is passed as a\n // literal argv element via execFileSync (no shell) instead of string-interpolated into an\n // execSync shell command (issue #51): target is tool-call input (Grep/Glob/Bash/Read\n // query/pattern) that an agent or, transitively, a prompt can influence, so the old\n // interpolation was a real shell-injection exposure. npx is a .cmd shim on Windows that\n // execFileSync can't spawn as a bare name, so resolve the platform-specific name inline (this\n // standalone script can't import windows-shell-spawn.ts).\n const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';\n const context = execFileSync(\n npx,\n ['--no-install', 'docuvia', 'query', target, '--format=prompt'],\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },\n );\n if (context && context.trim().length > 0) {\n console.log(\"=== Docuvia Context injected ===\");\n console.log(context);\n console.log(\"================================\");\n }\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n console.error(\"[Docuvia Pre-Command] Failed to retrieve context: \" + errorMsg);\n }\n}\n`;\n\n// Named so the merge/prune logic in claude.platform.ts's project-level `.claude/settings.json`\n// support reuses this exact key instead of re-typing the literal (a typo there would silently\n// desync the two).\nexport const HOOK_EVENT_PRE_TOOL_USE = \"PreToolUse\";\n\nexport const HOOKS_JSON = JSON.stringify(\n {\n [HOOK_EVENT_PRE_TOOL_USE]: [\n {\n hooks: [\n {\n command: \"node ${HOOKS_DIR}/docuvia-hook.js\",\n statusMessage: \"Enriching with Docuvia architectural context...\",\n timeout: 5,\n type: \"command\",\n },\n ],\n matcher: \"Grep|Glob|Bash|Read\",\n },\n ],\n },\n null,\n 2,\n);\n\nexport const AGENT_INSTRUCTIONS = `\n<!-- ${AGENT_INSTRUCTIONS_MARKER} -->\n# Docuvia — Codebase Knowledge Evolver\n\nThis project uses Docuvia to manage architectural context and prevent blast-radius regressions.\nGrep/Glob/Read are the most expensive tools available to you — before reaching for them to explore the codebase, query the local knowledge graph instead, and before editing a symbol or file, check its blast radius:\n\nRun: \\`npx --no-install docuvia query \"<concept_or_file>\" --format=prompt\\`\nRun: \\`npx --no-install docuvia impact <symbolOrFile>\\`\n\nUse the results to understand architectural boundaries, historical decisions, and potential blast radius before modifying code. Only fall back to Grep/Glob/Read when the graph returns nothing, the target is flagged \\`tier_b_status=\"unprocessed\"\\` (unknown, not zero), you need exact source text/formatting a structural query can't capture, or \\`query\\` returns a non-\\`exact\\` \\`match_type\\` (keyword/neighbor) for what should be a well-known symbol or file.\n\nAfter making a code change that reflects a real architectural decision, rule, or notable rationale, stage it so the graph picks it up without a separate write step:\n\nRun: \\`npx --no-install docuvia analyze <file> --agent-authored --stage\\`\n\nPipe a JSON payload on stdin (default) — \\`{\"decisions\":[{\"title\":string,\"content\":string,\"nodeType\":\"change\"|\"rule\"|\"decision\"|\"context\",\"confidence\":number}]}\\` — or pass \\`--decisions-file=<path>\\` instead. Put \\`--agent-authored\\`/\\`--stage\\` after the positional \\`<file>\\`, not before — a flag preceding the path silently swallows it as the flag's own value. Staged decisions flush into the knowledge graph automatically the next time you commit a change touching that file — nothing else to run.\n<!-- docuvia:end -->\n`;\n"],"mappings":";8DAuBO,IAAMA,EAAa,UACbC,EAAa,UAEbC,EAAwB,aACxBC,EAAyB,gBAEzBC,EAAyB,mBACzBC,EAAiC,6BACjCC,EAAmB,gBAEnBC,EAAgC,0BAChCC,EAAqB,YACrBC,EAAwB,eACxBC,EAA4B,gBAC5BC,EAAgC,cAKhCC,EAA0B,iBAC1BC,EAAoB,WAEpBC,EAAqB,YACrBC,EAAqB,aACrBC,EAAqB,kBACrBC,EAA0B,aAE1BC,EAAuB,SACvBC,EAAuB,SACvBC,EAAwB,iBACxBC,EAAsB,QACtBC,EAAyB,WACzBC,EAAuB,eAGvBC,EAAuB,SACvBC,EAAuB,SACvBC,EAAwB,UACxBC,EAAsB,QACtBC,EAAyB,WACzBC,EAAuB,SAIvBC,EAA0B,8BAC1BC,EAA0B,8BAQ1BC,EACX,yBAA2BC,EAEhBC,EAAc,MACdC,EAAe,KACfC,EAAsB,eACtBC,EAA0B,CAAC,UAAW,KAAK,EAC3CC,EAAiC,yBAEjCC,EAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAgBFC,EAAU,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAQpBC,CAAgB,IAAIC,CAAsB;AAAA;AAAA,qBAEzDF,EAAU,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwCnCG,EAA0B,aAE1BC,EAAa,KAAK,UAC7B,CACE,CAACD,CAAuB,EAAG,CACzB,CACE,MAAO,CACL,CACE,QAAS,oCACT,cAAe,kDACf,QAAS,EACT,KAAM,SACR,CACF,EACA,QAAS,qBACX,CACF,CACF,EACA,KACA,CACF,EAEaE,EAAqB;AAAA,OAC3BnC,CAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["GITHUB_DIR","CLAUDE_DIR","HOOKS_CONFIG_FILENAME","SETTINGS_JSON_FILENAME","CURSOR_MCP_CONFIG_PATH","CLAUDE_DESKTOP_CONFIG_FILENAME","MCP_SERVER_ALIAS","COPILOT_INSTRUCTIONS_FILENAME","CLAUDE_MD_FILENAME","CURSOR_RULES_FILENAME","AGENT_INSTRUCTIONS_MARKER","AGENT_INSTRUCTIONS_END_MARKER","WINDSURF_RULES_FILENAME","LLMS_TXT_FILENAME","AGENTS_MD_FILENAME","HERMES_MD_FILENAME","CONTINUE_RULES_DIR","CONTINUE_RULES_FILENAME","PLATFORM_NAME_CURSOR","PLATFORM_NAME_CLAUDE","PLATFORM_NAME_COPILOT","PLATFORM_NAME_CODEX","PLATFORM_NAME_CONTINUE","PLATFORM_NAME_HERMES","PLATFORM_SLUG_CURSOR","PLATFORM_SLUG_CLAUDE","PLATFORM_SLUG_COPILOT","PLATFORM_SLUG_CODEX","PLATFORM_SLUG_CONTINUE","PLATFORM_SLUG_HERMES","CLAUDE_PLUGIN_HOOKS_DIR","CURSOR_PLUGIN_HOOKS_DIR","CLAUDE_PROJECT_HOOKS_DIR","CLAUDE_HOOKS_DIR","NPX_COMMAND","NPX_YES_FLAG","NPX_NO_INSTALL_FLAG","DOCUVIA_MCP_LAUNCH_ARGS","DOCUVIA_WORKSPACE_ROOT_ENV_VAR","DOCUVIA_HOOK_JS","HookNames","DOCUVIA_DIR_NAME","HOOKS_CONFIG_FILE_NAME","HOOK_EVENT_PRE_TOOL_USE","HOOKS_JSON","AGENT_INSTRUCTIONS"]}