import type { ResolveSkillSourceContext, ResolvedSkillSource, SkillSource } from '@vibe-agent-toolkit/agent-skills'; import type { SkillSourceDescriptor } from '@vibe-agent-toolkit/resources'; import { type StagedManifest } from './manifest.js'; import type { PluginLayout } from './plugin-layout.js'; export interface StageItem { name: string; source: SkillSource; /** * Marks the primary skill under test. Exactly one item should carry * `role: 'subject'`; its staged directory is returned as * {@link StageHarnessResult.subjectStagedDir} so the caller can locate the * subject's own `evals/evals.json`. */ role?: 'subject'; /** * Present when this skill's TRUE source dir lives inside a Claude plugin * (detected via {@link detectPluginLayout}). When set, the item is staged under * its real plugin-root layout — the plugin's `.claude-plugin/` is copied and the * skill is nested at `//` — so that the * harness mirrors a real plugin install and `${CLAUDE_PLUGIN_ROOT}/skills/` * paths in the skill's own code resolve. Absent → flat staging (standalone skill). */ pluginLayout?: PluginLayout; /** * Marks a `--with-optional` companion: when this item's `resolve` (or the * subsequent staging of its resolved contents) throws, `stageHarness` SKIPS it * — with the name recorded in {@link StageHarnessResult.skippedOptional} — rather * than failing the whole run. Absent (required, including the subject and every * `--with` companion) means a throw propagates and fails the run. */ optional?: true; } export interface StageHarnessOptions { harnessRoot: string; items: StageItem[]; resolve: (source: SkillSource, ctx: ResolveSkillSourceContext) => Promise; ctx: ResolveSkillSourceContext; currentUid: number; /** * Suite subpath (e.g. `evals/evals.json`) whose contents are stripped from EVERY * item's resolved copy before it is staged onward or hashed — the eval answer key * must never reach the executor's filesystem. See {@link isolateEvalSuite}. * * Optional: `undefined` means this run declares no eval suite at all, which is a * clean no-op (nothing to strip) rather than a runtime accident — the real * production caller (`run-harness.ts`) always defaults this to * `evals/evals.json` before calling in, but the type must not pretend the value * can never be absent, since nothing upstream is contractually obligated to * default it. */ evalsSubpath?: string; /** * vat-only directory OUTSIDE the harness root. When the SUBJECT's resolved copy * carries the suite (a fetched npm/url/vendored artifact, or a source tree that * holds its own evals), it is relocated here so the harness can still read it * while the executor cannot. Left empty when the subject carried no suite. */ evalSuiteHoldDir: string; } export interface StageHarnessResult { manifest: StagedManifest; pluginDirs: string[]; /** * Absolute staged directory of the item tagged `role: 'subject'` (the primary * skill under test). `null` when no item carried that role. The harness reads * the subject's `evals/evals.json` from inside this directory. For a * plugin-distributed subject this points at the NESTED skill dir * (`/`), not the plugin root. */ subjectStagedDir: string | null; /** * Absolute staged PLUGIN ROOT of the subject when the subject is * plugin-distributed (the dir holding `.claude-plugin/`). The caller exports it * as `CLAUDE_PLUGIN_ROOT` so the harness mirrors a real plugin install. `null` * when the subject is standalone (no plugin layout) or absent. */ subjectPluginRoot: string | null; /** * Names of `--with-optional` items that were SKIPPED because resolving or * staging them threw (unresolvable source, build failure, etc.). Empty when * every optional item staged cleanly, or when there were none. A required item * (subject or `--with`) that throws is never recorded here — it propagates and * fails the whole run instead. */ skippedOptional: string[]; /** * True when the SUBJECT's resolved copy carried an eval suite, which was * therefore relocated into `evalSuiteHoldDir` (and removed from everything the * executor can reach). The harness reads the suite from there ONLY when no * authored source copy exists — an authored suite always wins, so editing it is * what a re-run picks up. */ subjectEvalSuiteHeld: boolean; } /** * Map a config descriptor onto Plan 1's runtime SkillSource union. * * Implemented as a CHECKED assignment (no `as`): the config descriptor union and * the runtime `SkillSource` union are a pinned cross-plan interface that must stay * structurally identical. If either ever drifts, this assignment stops * type-checking instead of silently laundering the mismatch through a cast. */ export declare function descriptorToSource(d: SkillSourceDescriptor): SkillSource; /** * Safe single-segment directory name for a staged item under the harness root. * * `item.name` may be an absolute or relative path — the subject under test is the * positional CLI arg (`vat skill test run `). Using it raw as a path segment * (`join(harnessRoot, name)`) is a bug: on Windows an absolute `C:\…` name lands a * drive letter mid-path (`…\harness\C:\Users\…`), an invalid path that makes cpSync * throw; on POSIX the same join silently produces a wrongly-nested directory. * Reduce to the sanitized basename plus a short hash of the full name so the * destination is always one valid, collision-free segment (the hash also covers the * empty-after-sanitize fallback and disambiguates equal basenames). */ export declare function stagedDirName(name: string): string; /** Stable content hash of a staged directory tree (sorted relative paths + bytes). */ export declare function computeDirContentHash(dir: string): string; export declare function stageHarness(opts: StageHarnessOptions): Promise; //# sourceMappingURL=staging.d.ts.map