/** * Pure link graph walker that operates on ResourceRegistry data. * * Replaces the I/O-heavy `collectLinks()` with a pure function that walks * the link graph using pre-parsed data from a ResourceRegistry. This eliminates * per-file `parseMarkdown()` calls and `existsSync()` checks for markdown files. * * Non-markdown assets (images, JSON, etc.) won't be in the registry and still * require `existsSync()` checks — this is acceptable since the goal is eliminating * redundant I/O for markdown files that are already parsed. */ import type { DeferredArtifacts, ResourceMetadata } from '@vibe-agent-toolkit/resources'; import { type GitTracker } from '@vibe-agent-toolkit/utils'; /** * Resolution result for a single link found in a bundled markdown file. */ export interface LinkResolution { /** Absolute path to the linked file (the link TARGET) */ path: string; /** * Absolute path to the file that CONTAINS the link. * * Distinct from {@link LinkResolution.path} on purpose: for a * `missing-target` exclusion the target does not exist, so an issue * anchored to it names a file the author cannot open. The issue's * `location` must be this, the containing file. */ sourcePath: string; /** 1-based line of the link within {@link LinkResolution.sourcePath}, when known */ sourceLine?: number | undefined; /** * Whether the link TARGET existed on disk when the walker classified it. * * Recorded rather than re-derived downstream. The verdict engine gates * `LINK_TO_GITIGNORED_FILE` on "gitignored AND exists at source"; a * translation front-end that hardcodes existence turns that guard into dead * code, so the one place that actually stat'ed the path carries the answer. */ targetExists: boolean; /** Whether the file will be bundled */ bundled: boolean; /** Reason it was excluded (only set when bundled is false) */ excludeReason?: 'depth-exceeded' | 'pattern-matched' | 'directory-target' | 'outside-project' | 'navigation-file' | 'agent-instruction-file' | 'skill-definition' | 'gitignored' | 'missing-target' | undefined; /** The rule that matched (only set for pattern-matched exclusions) */ matchedRule?: ExcludeRule | undefined; /** Link text from the source markdown */ linkText?: string | undefined; /** Original href from the markdown */ linkHref?: string | undefined; } /** * A rule that excludes files from bundling based on glob patterns. * First matching rule wins (ordered evaluation). */ export interface ExcludeRule { patterns: string[]; template?: string | undefined; } /** * Minimal interface for the registry operations walkLinkGraph needs. * Avoids tight coupling to the full ResourceRegistry class. */ export interface WalkableRegistry { getResourceById(id: string): ResourceMetadata | undefined; getResource(filePath: string): ResourceMetadata | undefined; } /** * Result of walking the link graph from a skill resource. */ export interface LinkGraphResult { /** Markdown resources within depth and not excluded */ bundledResources: ResourceMetadata[]; /** Non-markdown file paths (images, JSON, etc.) — absolute paths */ bundledAssets: string[]; /** References detected but NOT bundled (depth, exclude, etc.) */ excludedReferences: LinkResolution[]; /** Actual max depth of the bundled portion */ maxBundledDepth: number; /** Asset paths that are deferred (declared in files config, may not exist yet) */ deferredAssets: string[]; } /** * Options for walking the link graph. */ export interface WalkLinkGraphOptions { /** Max depth for following markdown links (Infinity for 'full') */ maxDepth: number; /** Ordered exclude rules (first match wins) */ excludeRules: ExcludeRule[]; /** Project root for boundary enforcement and pattern matching */ projectRoot: string; /** * Absolute path to the current skill's SKILL.md. Used to distinguish self-links * (a bundled doc linking back to the current skill's own SKILL.md) from * cross-skill links to other skills' SKILL.md files. Self-links are silently * ignored; cross-skill links become `skill-definition` exclusions. */ skillRootPath: string; /** Whether to exclude navigation files (README.md, index.md, etc.) */ excludeNavigationFiles?: boolean; /** * Declared `files:` deferred-artifact model. A path covered by it is treated * as a deferred build artifact in two cases: * * - The target does not yet exist on disk ({@link checkDeferred}, the FIRST * discriminator in {@link checkExclusions}) — covered via dest OR source. * - The target exists, is gitignored, AND is covered as a DEST (the * gitignore branch in {@link checkExclusions}) — the expected state of a * build artifact once a build has run, not a leak. Source-only coverage * does NOT exempt an existing, gitignored target: a `files:` source is a * real file the author pointed at, and the leak signal is wanted. * * A covered path that exists and is NOT gitignored still falls through to * the normal directory-target / bundling handling. An UNCOVERED path is * never exempted — an existing gitignored file outside `files:` still * surfaces the `gitignored` leak signal. */ deferredArtifacts?: DeferredArtifacts; /** * Optional pre-populated {@link GitTracker} for O(1) gitignore checks. * * When provided, link-target gitignore checks use the tracker's active set, * which avoids spawning `git check-ignore` per file. Supply the same * tracker you already built for the containing scan (e.g. from audit's * ScanContext) so the first call warms the cache and every subsequent * walker call answers in O(1). * * When omitted, the walker falls back to the legacy per-path * `isGitIgnored()` spawn so one-off callers continue to work unchanged. */ gitTracker?: GitTracker; } /** * Walk the link graph starting from a skill resource, using pre-parsed * registry data instead of per-file I/O. * * Semantics match the original `collectLinks()`: * - Non-markdown assets bypass depth limits (always bundled unless pattern-excluded) * - Markdown links are subject to depth limits and exclude rules * - Circular references are handled via a visited set * - Glob matching uses forward-slash paths relative to projectRoot * * @param skillResourceId - The resource ID of the skill's SKILL.md in the registry * @param registry - A walkable registry with pre-parsed resources * @param options - Walk options (depth, excludes, etc.) * @returns Graph walk result with bundled resources, assets, and exclusions */ export declare function walkLinkGraph(skillResourceId: string, registry: WalkableRegistry, options: WalkLinkGraphOptions): LinkGraphResult; //# sourceMappingURL=walk-link-graph.d.ts.map