import type { Artifact, RemoteGitRef } from '../types/index.js'; export declare function gitShowFile(ref: string, filePath: string, cwd?: string): string | null; /** * 取 `:` 的原始字节(二进制资产用,不做 utf-8 解码);不存在/非 blob/出错返回 null。 * 同 gitShowFile 用 `cat-file blob`:对目录会非零退出,不会把树清单字节当文件内容物化。 */ export declare function gitShowBytes(ref: string, filePath: string, cwd?: string): Buffer | null; /** * 把一个 ref(branch / tag / HEAD / 缩写或完整 SHA)解析到它指向的 commit SHA(#234/#236 还原坐标)。 * `^{commit}` 会把 annotated tag 也 peel 到 commit;`--verify --quiet` 解析不出时静默非零退出 → 落 null。 * 不存在 / 出错 → null(best-effort,绝不抛、不阻断 eval)。`` 物化内容时用的就是它,故这是被测字节的定点 * (与工作树是否 dirty 无关 —— 内容从 object DB 按 ref 取)。dash-ref(前缀 `-`)直接挡,不得被当 git 选项 * (rev-parse 的 rev 不能放 `--` 之后,故显式前置守卫,与 gitShowBytes 的 `--` 同口径 fail-closed)。 */ export declare function gitResolveCommit(ref: string, cwd?: string): string | null; export interface GitTreeEntry { /** git 文件模式:100644/100755=普通文件,120000=软链,160000=submodule。 */ mode: string; /** 相对所列 tree 的路径。 */ path: string; } /** * 递归列出 `:` 子树下的叶子条目(blob / 软链 / submodule)。tree 不存在返回 []。 * 用 `-z`(NUL 分隔):git 不会对含换行 / 非 ASCII 的路径做 C-quote,路径原样可回喂 git show。 * 用 `--full-tree`:treePath 是仓库根相对路径,不受当前 cwd 前缀限制 —— 否则在仓库子目录执行时 * `ls-tree HEAD:skills/review` 会返回空(而 gitShowFile 探测不受限,导致"探测命中→物化为空"的发散)。 * `treePath` 为空时列整棵根 tree(`:`)。 */ export declare function gitLsTreeBlobs(ref: string, treePath: string, cwd?: string): GitTreeEntry[]; export interface GitRepoContext { /** 仓库根(realpath 归一,避免 macOS /var ↔ /private/var 等价路径算错)。所有 git helper 以此为 cwd。 */ repoRoot: string; /** fromPath 相对仓库根的路径(realpath 归一后计算)。 */ relDir: string; } /** * 解出 `fromPath` 所属 git 仓库的上下文。**关键**:`git rev-parse` 在 `fromPath` 处执行(-C), * 而非进程 cwd —— 否则 eval 从别处调用、skillDir 指向另一个 repo 时,会拿进程 cwd 的 repo root 和 * HEAD 去解析,轻则 not_found、重则评测错 repo 的同名内容。repoRoot / relDir 都 realpath 归一, * 消除 /var ↔ /private/var 这类等价路径导致 relative 算错。不在 git 仓库时 rev-parse 抛错,由上层转 * not_a_git_repo。stderr 吞掉,不双重打印 `fatal:`。 */ export declare function resolveGitRepoContext(fromPath: string): GitRepoContext; /** 拼 git 仓库相对路径:始终用 `/`,空 base 直接返回 b(避免 join 产生 `.` 这类退化 tree-ish)。 */ export declare function gitJoin(base: string, sub: string): string; /** * 解析 `git::` —— **install 与 eval 共用此一处**,保证两侧对 ref/spec 的切分与合法性 * 判定一致。`git:` 缺省 ref=HEAD;spec 可含 `/`(如 skills/review)。空 ref / 空 spec 都非法 * 并返回 null:空 ref(`git::x`)会被 git 当 index/stage-0 解析,量到不可复取、依赖本地暂存区的内容, * 破坏 git variant 的可复现语义;空 spec(`git:HEAD:`)会退化去探 `.md`/`SKILL.md` 误命中。 */ export declare function parseGitInput(input: string): { ref: string; spec: string; } | null; export interface GitSkillRef { isDir: boolean; /** dir-skill 的子树根(仓库相对);file-skill 为空。 */ treePath: string; /** file-skill 的 .md 路径(仓库相对);dir-skill 为空。 */ fileSkillPath: string; name: string; } /** * 把 git spec 解析成 dir / file skill —— **install 与 eval 共用此一处**,保证两条路径对同一 * `git::` 的 file-vs-dir 归类绝不发散(发散会让 eval 量文件、install 注册目录, * 两边对不上)。接受三种写法,与本地路径安装对称: * - 显式 SKILL.md:`skills/dir/SKILL.md` → 目录-skill,name 取父目录名; * - 显式 .md:`skills/foo.md` → 文件-skill; * - 裸 spec:`skills/review` → **文件优先**(先试 `.md`,再试 `/SKILL.md`)。 * 裸 spec 的文件优先必须与 eval 历史顺序一致 —— 否则同名同时存在 .md 与 dir/SKILL.md 时, * eval 量文件、install 注册目录,evidence 读时按 hash 门控被静默剥离、记录永久 stale。 * `gitRelDir` 是各调用方的解析基准(install=cwd 相对仓库根、eval=skillDir 相对仓库根),作为显式 * 参数传入,基准差异留给调用方、归类逻辑单一来源。任一探测命中即返回;都不中返回 null。 */ export declare function classifyGitSkillRef(ref: string, gitRelDir: string, spec: string, cwd?: string): GitSkillRef | null; /** * 源解析的结构化错误 —— 不依赖 CLI:以 `messageKey` 抛出,由调用方(install 走 tCli、eval 走自身 * 中文错误)映射成本地化文案。住在 skill-loader(而非 source-resolver)是为了让共享的 * `materializeGitSkillTree` 能抛它而不致 skill-loader → source-resolver 反向成环;source-resolver * re-export 以保持 install 既有 import 不破。 */ export declare class SourceResolveError extends Error { readonly messageKey: string; readonly params: Record; constructor(messageKey: string, params?: Record); } /** * 校验 git tree 条目路径在物化目标内,越界即 fail closed(抛 SourceResolveError)。 * 双保险:既显式拒 `..` / 空段(git tree 可被手工构造出名为 `..` 的子树),也用 resolve 兜底 * 确认落点仍在 temp 之下(绝对路径 / 符号化逃逸)。绝不静默跳过——跳过会让物化树与真实树发散。 */ export declare function assertContainedRelPath(temp: string, relPath: string): void; export interface MaterializedGitTree { /** 物化后的本地根:目录-skill 为临时目录、文件-skill 为临时 .md;喂 hashArtifactSource + 分发。 */ localRoot: string; isDirectorySkill: boolean; name: string; /** 释放临时目录;调用方务必 try/finally 调用(物化只为算哈/分发,用完即删)。 */ cleanup: () => void; } /** * 把 git 某个 ref 上的 skill(已由 `classifyGitSkillRef` 归类)逐文件物化到临时目录 —— * **install 与 eval 共用此一处物化**,保证两侧拿到完全一致的本地树(此前 install/eval 各写一套 * git 解析正是四轮 bug 的根源,已靠共享 helper 收敛)。eval 仅为算整树指纹(hashArtifactSource)而 * 物化,算完即 cleanup;install 物化后分发并登记。失败(越界路径 / 空树 / 取不到 blob)抛 * SourceResolveError,绝不静默落空壳。 */ export declare function materializeGitSkillTree(ref: string, resolved: GitSkillRef, repoRoot: string): MaterializedGitTree; export interface RemoteGitCheckout { /** 临时 bare 仓库路径,作为 git helper 的 cwd(repoRoot)。 */ repoRoot: string; /** fetch 落地后 rev-parse 出的**实际 SHA**(branch/tag 会漂,记录与重取都钉 SHA)。 */ ref: string; /** 删临时 bare 仓库;调用方务必 try/finally 调用。 */ cleanup: () => void; } /** * 远端 git URL 形态校验:接受 https(s):// / ssh:// / git:// / file:// 协议 URL、scp 形式 * `user@host:path`、绝对本地路径(file 远端 / 测试)。明显非法(空、相对裸串)拒。 */ export declare function isPlausibleGitUrl(url: string): boolean; /** * 远端 git:clone 某 ref 到临时 bare 仓库、pin 实际 SHA —— **install 与 eval 共用此一处**,远端只是 * 把下游 git helper(classifyGitSkillRef / materializeGitSkillTree)的 repoRoot 从「当前仓库」换成 * 「fetch 下来的临时 bare」,其余一字不改。`git init --bare` + `git fetch --depth 1`(省带宽,只取该 * ref 的 tip 树);ref 为 branch/tag/HEAD 通用,裸 SHA 取决于服务端是否允许 reachable-SHA fetch。 * 认证依赖本机 git 凭证(SSH agent / credential helper),omk 不自管 token。失败 fail-closed * (SourceResolveError),由调用方本地化;eval 侧捕获后改抛中文 Error。 */ export declare function fetchRemoteGitRef(url: string, ref: string): RemoteGitCheckout; export declare function discoverVariants(skillDir: string): string[]; export declare function discoverBatchSkills(skillDir: string): Array<{ name: string; skillPath: string; samplesPath: string; }>; export declare function loadSkills(skillDir: string, variants: string[]): Record; /** opts for resolveArtifacts skill-isolation wiring. */ export interface ResolveArtifactsOptions { /** Default true. When true, baseline-kind artifacts get allowedSkills=[] auto-injected. * 显式 per-variant 隔离声明走 spec.allowedSkills(prepareEvaluationRun 按 spec 身份绑定), * 不经此处——resolveArtifacts 只认 strictBaseline 默认,隔离绑定收成单一来源。 */ strictBaseline?: boolean; /** Default true. dir-skill 是否落地隔离副本(写 `~/.oh-my-knowledge/state/trees` 并设 execRoot)。 * 只有 eval(执行隔离)需要;doctor / loadSkills 等纯读路径传 false,只算指纹与正文、不写副本。 */ materialize?: boolean; } /** * Parse variant expression, extracting optional cwd suffix. * Format: "name@/path/to/cwd" or just "name" */ export declare function parseVariantCwd(variant: string): { name: string; cwd?: string; }; /** 把一个 variant 表达式规范化成稳定的物理身份,用于「同一 variant 不能既是 control 又是 * treatment」的判重。同一份 skill 的不同写法必须折叠成同一个 key: * - `./x.md` 与 `x.md`、符号链接、大小写不敏感卷上的等价写法:resolve 后取 `dev:ino` 物理身份。 * - 目录 `dir` 与 `dir/SKILL.md`:先折叠到同一锚点再取物理身份。 * - 裸短名(传了 `skillDir` 时):按 resolveArtifacts 的解析基准(`skillDir/name.md` 或 * `skillDir/name/SKILL.md`)取物理身份,这样裸名 `greeter` 与指向同一文件的 `./greeter.md` * 能判为重复;没传 skillDir(如纯单测)则退回字面名。 * - `git:` / `baseline`:本身就是稳定标识,原样返回。 * 结构化的 `cwd`(第三参数)按物理身份纳入 key —— 同一份 skill 绑不同 cwd 是不同 runtime * context,不算重复。不要用派生短名判重:`v1/greeter.md` 与 `v2/greeter.md` 短名都是 greeter * 却是两个 variant。 */ export declare function variantIdentity(expr: string, skillDir?: string, cwd?: string): string; /** 从已解析的 skill 路径取短名:`SKILL.md` 取其父目录名,否则取去掉 `.md` 后缀的 basename。 * `variantExprToSkillName`(expr → 短名)与 `resolveArtifacts` 的 file-path 命名共用这一处, * 避免两份各写一遍后悄悄发散——report 键就来自 resolveArtifacts 这一支。 */ export declare function skillNameFromPath(filePath: string): string; /** 从 variant 表达式(纯 artifact 身份,可能是路径)取短名。 */ export declare function variantExprToSkillName(expr: string): string; /** variant 输入:纯 artifact 表达式字符串、结构化 `{expr, cwd?}`、或远端 git 结构化 `{git, cwd?, name}` * (url/ref/spec 分字段,绝不拼成单串再经 parseGitInput/parseVariantCwd 切分)。 */ export type VariantInput = string | { expr: string; cwd?: string; } | { git: RemoteGitRef; cwd?: string; name: string; }; export declare function resolveArtifacts(skillDir: string, variants: VariantInput[], opts?: ResolveArtifactsOptions): Artifact[]; /** 消歧:多个 variant resolve 出同名时(如 `v1/greeter.md` vs `v2/greeter.md` 都叫 `greeter`), * 用父目录逐段限定恢复唯一性。否则 `results[sample][variant]` / `summary[variant]` 按 name 键时 * 后写覆盖前写,把对照 / 实验组的结果搅在一起、verdict 拿同名跟自己比 —— 静默破坏对比可信度。 * 没有可用路径(baseline / git)时退化加 `#n` 序号兜底,保证返回的 name 一定两两不同。 */ export declare function ensureUniqueVariantNames(artifacts: Artifact[]): void;