/** * Paragraph-level citation (RCL-10). * * A search hit already knows WHICH document it came from. A citation has to * know WHERE IN IT: the character span of the chunk against the extracted * text, an anchor that survives being written down, and a way back to the * live page at that passage. * * Three pieces, deliberately separate: * * - `span:-` — a tag written next to every chunk at ingest, so a * search result carries offsets without re-reading the document. * - the ANCHOR `#-` — offsets bound to the content hash * of the exact version they were measured against. Quoting an offset range * without the version it belongs to is how citations rot: the document is * re-captured, the text shifts, and the quote silently points at other * words. A stale anchor here is DETECTED (`stale: true`), never served as * if it were still true. * - a W3C text fragment (`#:~:text=`) — the link a human clicks, which lands * on the sentence even though the site never had an id there. * * Everything above `resolveCitation` is pure and synchronous so the pipeline * can use it while ingesting. `resolveCitation` reaches back into the * document-pipeline through a DYNAMIC import: the pipeline imports this * module statically, and a static import back would close the cycle. * * @module v1/cli/knowledge/citation */ import type { CaptureProvenance } from './capture-envelope.js'; export declare const SPAN_TAG_PREFIX = "span:"; export interface ChunkSpan { chunkIndex: number; startChar: number; endChar: number; } /** The tag written beside a chunk at ingest: `span:-`. */ export declare function spanTag(startChar: number, endChar: number): string; /** Offsets from a chunk's tag list, or null when it predates span tags. */ export declare function parseSpanTag(tags: readonly string[] | undefined): { startChar: number; endChar: number; } | null; /** `#-` — offsets bound to the version they were measured against. */ export declare function citationAnchor(contentHash: string, startChar: number, endChar: number): string; export declare function parseCitationAnchor(anchor: string): { hashPrefix: string; startChar: number; endChar: number; } | null; /** True when the anchor was cut against this document version. */ export declare function anchorMatchesHash(anchor: string, contentHash: string): boolean; /** * The prose of a passage: § context prefixes gone, markdown syntax gone, * whitespace collapsed. * * `skipHeadings` drops heading lines as well, which is what a text fragment * wants — a heading is usually its own element on the page, so starting a * range there matches less reliably than starting at the paragraph. It falls * back to including them when the passage is nothing but headings. */ export declare function passageProse(passage: string, skipHeadings?: boolean): string; /** The head of a passage as a quotable sentence or two — the words a reader * would see on the page, not the markdown we stored. */ export declare function passageQuote(passage: string, maxChars?: number): string; /** * A W3C scroll-to-text fragment for this passage. * * Long passages use the `textStart,textEnd` form rather than the whole * paragraph: the spec matches across the range, and a shorter pair survives * the site re-flowing its markup between capture and click. */ export declare function textFragment(passage: string): string; /** The captured page's URL, pointed at the passage. Returns undefined when * there is no URL — a citation into a local file has no link to give. */ export declare function citationUrl(url: string | undefined, passage: string): string | undefined; export interface Citation { /** `:` — the pipeline's own document id. */ docId: string; filePath: string; scope: string; chunkIndex: number; startChar: number; endChar: number; /** Hash of the text the offsets were cut against, i.e. the file as it is NOW. */ contentHash: string; anchor: string; /** The cited text itself, straight out of the extracted document. */ passage: string; /** The head of the passage, collapsed — what an agent puts in quotes. */ quote: string; /** Present only when the anchor asked for a version this file no longer is. */ stale?: boolean; version?: number; title?: string; url?: string; /** `url` with a text fragment appended: the link that lands on the passage. */ citeUrl?: string; capturedAt?: string; source?: string; provenance?: CaptureProvenance; } export interface ResolveCitationOptions { rootDir?: string; scope?: string; /** Chunk index, as reported on a search hit. */ chunkIndex?: number; /** An anchor string from a previous citation — resolved by offsets, and * flagged `stale` when it names a version this document no longer is. */ anchor?: string; } /** * Resolve a citation back to the passage it came from. * * `target` is whatever the caller has written down: an indexed file path, a * capture's `canonicalUrl`, or the `:` docId. Throws with a * message meant for a human when the document is not indexed, its file is * gone, or the chunk does not exist — a citation that cannot be checked must * fail loudly, not resolve to an empty string. */ export declare function resolveCitation(target: string, opts?: ResolveCitationOptions): Promise; //# sourceMappingURL=citation.d.ts.map