/** * Skills foundation types. * * SDK 2.0 is a thin typed wire binding to QAR (Qodo Agent Runtime); it does * not execute agents. Skills here are a *consumer-side* concept: the SDK * reads `SKILL.md` files from the configured sources, builds an in-memory * catalog, and injects a slim `` index into * `InlineAgentSpec.instructions` so the QAR-side model loop sees what * skills exist. Runtime body loading lives in the `qodo-skills` MCP * server. * * The frontmatter shape mirrors the Agent Skills open standard * (`name` / `description` / `when_to_use` / `allowed-tools` / ...) plus * Qodo extensions (`vendor`, `version`, `min_sdk_version`, `requires`). * Unknown fields are preserved on `unknown_fields` so forward-compat is * by silence, not error. */ import type { JsonValue } from '../qar/json.js'; /** * Type tag for a source's trust derivation. Set at discovery time from * the source type; the trust layer gates `allowed-tools` / preprocessing * / `disable-model-invocation` waivers off this value. * * Stored on every `ParsedSkill` so downstream consumers (and the future * trust ticket) read it without re-walking the source list. Three tiers: * * - `'trusted'` — `project`, `config`, `bundled`. The consumer's own * code-reviewed surfaces. * - `'operator'` — `user`. Local-machine; trusted by virtue of being on * the operator's box, but not in the project's source tree. * - `'third-party'` — `package`, `cli-fetched`. Authored by someone the * consumer didn't write the code of. */ export type SkillTrustTier = 'trusted' | 'operator' | 'third-party'; /** * One configured discovery source. The default precedence is * project > config > user > package > cli-fetched > bundled. * * Each variant carries its own optional `path` / `packageName` / `ref` * fields per `skills-discovery.md` §1. */ export type SkillSource = { readonly type: 'project'; readonly path?: string; } | { readonly type: 'config'; readonly path: string; } | { readonly type: 'user'; readonly path?: string; } | { readonly type: 'package'; readonly packageName: string; readonly path?: string; } | { readonly type: 'cli-fetched'; readonly ref?: string; } | { readonly type: 'bundled'; readonly path?: string; }; /** The discriminator for `SkillSource`. */ export type SkillSourceType = SkillSource['type']; /** * Parsed `allowed-tools` entry. The standard accepts both a flat tool name * (`Read`) and the tool-with-args form (`Bash(git:*)`); both normalize to * this shape with `args === undefined` for the bare case. */ export interface AllowedToolEntry { readonly tool: string; readonly args?: string; } /** * Named-argument descriptor from `arguments:` frontmatter. Per the standard, * authors can declare positional names so the body can reference `$issue` * instead of `$1`. * * The standard's syntax is informal; we accept either a string list (bare * names) or an object list (`{ name, description? }`). The parser * normalizes to this shape. */ export interface SkillArgument { readonly name: string; readonly description?: string; } /** * Skill frontmatter — the open standard's surface plus the Qodo extensions * documented in `skills.md` §2. Every field is optional at the type level; * the validator enforces shape rules and emits `sdk.skill.error` events for * the violations the SDK considers fatal. * * Unknown fields stay on `unknown_fields` for forward compatibility; the * raw object is also preserved on `raw` so a later wave that needs a field * the SDK doesn't know about yet can read it without re-parsing. */ export interface SkillFrontmatter { readonly name?: string; readonly description?: string; readonly when_to_use?: string; readonly 'allowed-tools'?: readonly AllowedToolEntry[]; readonly 'disable-model-invocation'?: boolean; readonly 'user-invocable'?: boolean; readonly model?: string; readonly effort?: 'low' | 'medium' | 'high' | 'max'; readonly context?: 'fork' | 'inherit'; readonly agent?: string; readonly paths?: readonly string[]; readonly arguments?: readonly SkillArgument[]; readonly 'argument-hint'?: string; readonly shell?: 'bash' | 'powershell'; readonly hooks?: JsonValue; readonly vendor?: string; readonly version?: string; readonly min_sdk_version?: string; readonly requires?: readonly string[]; /** Unknown frontmatter fields preserved verbatim. Emits `sdk.skill.unknown_field` at parse. */ readonly unknown_fields?: Readonly>; } /** * One catalog entry — the result of parsing + validating a `SKILL.md`. * * The `fqn` is the catalog key: `/@`. Bare-name * and `/` lookups resolve to the latest version. * * `body` carries the post-frontmatter markdown verbatim. The slim-index * renderer reads `frontmatter.description` + `frontmatter.when_to_use`; * the body is served to the model via the `get_skill` MCP tool. */ export interface ParsedSkill { /** Fully-qualified identifier — `/@`. Catalog key. */ readonly fqn: string; /** `/` — version-stripped. Used for shadowing / latest-version lookups. */ readonly fqnNoVersion: string; readonly name: string; readonly vendor: string; readonly version: string; /** Absolute path to the skill directory. */ readonly path: string; /** Absolute path to the `SKILL.md` file inside the directory. */ readonly skillFile: string; /** Modification time of `SKILL.md` (epoch ms). Used by the slim-index truncation order. */ readonly mtimeMs: number; /** Parsed frontmatter — fields per the open standard + Qodo extensions. */ readonly frontmatter: SkillFrontmatter; /** Markdown body verbatim, with frontmatter stripped. */ readonly body: string; /** Source this skill was discovered from. */ readonly source: SkillSource; /** Trust tier derived from `source.type` (operator / trusted / third-party). */ readonly trustTier: SkillTrustTier; /** Raw frontmatter object — preserves unknown fields, used by forward-compat readers. */ readonly raw: JsonValue; } /** * Embedding pre-filter interface for catalogs that overflow the * slim-index budget. */ export interface SkillEmbedder { readonly id: string; readonly dim: number; embed(text: string | readonly string[]): Promise; } /** * Caller-pinned skill specifier. Either a bare name (`'code-review'`), * a fully-qualified name without version (`'qodo/code-review'`), or a * fully-qualified-with-version (`'qodo/code-review@1.2.0'`). */ export type SkillSpecifier = string; /** * `!command` preprocessing policy. * * - `disabled` — `!command` blocks left literal; no execution; default. * - `allowlisted` — block executes only if the parsed first token matches * one of `allow`. Non-matches replaced with a disable marker; the * skill still loads. * - `full` — every block executes. Reserved for trusted environments; * third-party skills always bypass this mode (tier gate). * * Defaults: `timeoutMs: 5000`, `maxOutputBytes: 64_000`. */ export type SkillsPreprocessingMode = 'disabled' | 'allowlisted' | 'full'; export interface SkillsPreprocessingConfig { readonly mode: SkillsPreprocessingMode; /** * Left-anchored command-name allowlist used when `mode: 'allowlisted'`. * Matched against the first shell-tokenized argv of the command line * AFTER `${QODO_*}` substitution. Bare command names match (`git`), * absolute / path-bearing forms must match exactly * (`/usr/bin/jq`). On Windows both ignore case and separator style, and * bare names ignore a trailing `.exe`. Wildcards are not supported — list * each command explicitly. */ readonly allow?: readonly string[]; /** Hard kill timeout, default 5000 ms. */ readonly timeoutMs?: number; /** Per-command stdout+stderr cap, default 64 KB. */ readonly maxOutputBytes?: number; /** Optional override cwd. Defaults to the skill's directory (`${QODO_SKILL_DIR}`). */ readonly cwd?: string; } /** Preprocessing config defaults — exported so tests stay in sync with the runtime. */ export declare const PREPROCESS_DEFAULT_TIMEOUT_MS = 5000; export declare const PREPROCESS_DEFAULT_MAX_OUTPUT_BYTES: number; /** * Per-render activation tuning for the optional embedding pre-filter. * The pre-filter only kicks in when the catalog can't fit in the * slim-index budget AND an embedder is configured; otherwise these * fields are ignored. */ export interface SkillsActivationConfig { /** Top-K skills to keep after cosine ranking. Default 8. */ readonly topK?: number; /** Minimum cosine similarity (after `normalize`) to include. Default 0.35. */ readonly minScore?: number; } /** Default top-K when `SkillsConfig.activation.topK` is unset. */ export declare const ACTIVATION_DEFAULT_TOP_K = 8; /** Default minimum cosine score when `SkillsConfig.activation.minScore` is unset. */ export declare const ACTIVATION_DEFAULT_MIN_SCORE = 0.35; /** * Public config object accepted as `ClientOptions.skills`. Each field is * optional and degrades gracefully when omitted: * * - `sources` — discovery list, highest precedence first. Omitted ⇒ the * default order (`project`, `user`, `bundled`). Passing `[]` * disables skills entirely (catalog stays empty). * - `activate` — caller-pinned skills applied as the constructor-level * default. Per-call `tasks.startWithAgent({ ..., skills })` overrides. * - `trustedSources` — opt-in trust elevation for `package` / * `cli-fetched` sources. * - `embedder` — pre-filter for catalogs > slim-index budget. * - `indexCharBudget` — total cap for the rendered `` * block. Default `8000` (the SDK doesn't know the model's context * window without a runtime lookup, so the conservative cap wins). * - `preprocessing` — `!command` policy. * - `watch` — dev-only hot reload. * - `emit` — optional telemetry sink. The SDK calls this once per * `sdk.skill.*` event. Omitting it suppresses telemetry; no events * emit by default. */ export interface SkillsConfig { readonly sources?: readonly SkillSource[]; /** * Caller-pinned skills applied as the constructor-level default. Per-call * `tasks.startWithAgent({ skills })` overrides per the * `undefined` / `[]` / named-array semantics in `skills-activation.md` §2. * * **Trust scoping.** `activate` (and the per-call `TaskOptions.skills`) * is a **trusted-application-code input**, not an end-user input. Caller * pinning bypasses the trust gate that blocks model-driven loading of * third-party skills — that's intentional: the caller is the * human-in-the-loop signal. If a consumer plumbs `activate` from an * end-user-controlled surface (a Slack message, a URL param), the * consumer **must** apply its own allowlist before forwarding to the * SDK. */ readonly activate?: readonly SkillSpecifier[]; /** * Opt-in allowlist of source identities trusted at the same level as * `bundled` / `project` / `user`. Entries key on **immutable source * identity** — npm package name (`'@anthropic-skills/core'`), the * full `:` key (`'package:@anthropic-skills/core'`, * `'config:my-skills.json'`), or the literals `'project'` / `'user'` * / `'bundled'`. Vendor-name strings from frontmatter are **never** * accepted — a malicious package could declare * `vendor: anthropic-skills` and inherit pre-existing opt-ins. * * The validator runs at `SkillsManager` construction; entries that don't * match a configured source identity throw `SkillsTrustConfigError`. * Wildcards (`*`) are rejected — list each source explicitly. */ readonly trustedSources?: readonly string[]; readonly embedder?: SkillEmbedder | undefined; readonly indexCharBudget?: number; readonly preprocessing?: SkillsPreprocessingConfig; /** * Caps applied to the `requires:` resolution graph at activation * time. Defense against malicious or malformed dependency chains. * Any breach causes the parent's activation to fail closed with * `sdk.skill.requires_cap_exceeded`. */ readonly requires?: SkillsRequiresConfig; /** * Tuning for the optional embedding pre-filter. Ignored when * `embedder` is not configured. */ readonly activation?: SkillsActivationConfig; readonly watch?: boolean; readonly emit?: SkillsEventSink; /** * SDK semver used for `min_sdk_version` compatibility checks. The default * reads `package.json`; tests inject a value so they don't have to * round-trip through the package surface. */ readonly sdkVersion?: string; /** * Lockfile policy. When set to `'discover'` (the default), the * manager searches `qodo.lock.yaml` at the configured CWD and beside * `agent.toml`; when set to `{ path }`, that path is loaded directly; * when `'off'`, no lockfile is consulted regardless of on-disk state. * * On drift or unreachable, boot throws `LockfileValidationError` and * emits `sdk.skill.lock_drift` / `sdk.skill.lock_unreachable`. Skills * not in the lockfile are still discovered + included. */ readonly lockfile?: 'discover' | 'off' | { readonly path: string; }; } /** * Caps on `requires:` chain resolution. All three are independent — * the resolver fails closed on the first breach. */ export interface SkillsRequiresConfig { /** Maximum nesting depth of the resolution chain. Default `3`. */ readonly maxDepth?: number; /** Maximum transitive (non-root) dependencies. Default `8`. */ readonly maxTransitive?: number; /** Maximum sum of activated body sizes in bytes. Default `50 * 1024`. */ readonly maxTransitiveTokens?: number; } /** Resolved cap defaults used when `SkillsConfig.requires` fields are absent. */ export declare const REQUIRES_DEFAULT_MAX_DEPTH = 3; export declare const REQUIRES_DEFAULT_MAX_TRANSITIVE = 8; export declare const REQUIRES_DEFAULT_MAX_TRANSITIVE_TOKENS: number; export type SkillEventSeverity = 'error' | 'warning' | 'info'; /** * Closed union of `sdk.skill.*` events emitted across discovery, * rendering, activation, MCP runtime, preprocessing, lockfile, and * hot-reload. `kind`-discriminated so consumers `switch (ev.kind)` * cleanly. */ export type SkillEvent = { readonly kind: 'sdk.skill.source_scanned'; readonly source: SkillSource; readonly durationMs: number; readonly count: number; readonly errors: number; } | { readonly kind: 'sdk.skill.discovered'; readonly fqn: string; readonly source: SkillSource; readonly version: string; readonly trustTier: SkillTrustTier; } | { readonly kind: 'sdk.skill.shadowed'; readonly fqn: string; readonly winner: { readonly source: SkillSource; readonly version: string; }; readonly loser: { readonly source: SkillSource; readonly version: string; }; } | { readonly kind: 'sdk.skill.error'; readonly source?: SkillSource; readonly path?: string; readonly severity: SkillEventSeverity; readonly message: string; } | { readonly kind: 'sdk.skill.unknown_field'; readonly path: string; readonly field: string; } | { readonly kind: 'sdk.skill.indexed'; readonly catalogSize: number; readonly includedCount: number; readonly source: 'all' | 'truncated' | 'embedding'; readonly charsUsed: number; } | { readonly kind: 'sdk.skill.index_truncated'; readonly reason: 'no_embedder' | 'embedder_failed' | 'over_budget'; readonly omittedCount: number; } | { /** * Emitted on a successful embedding-prefilter pass: the * catalog was larger than the slim-index budget, an embedder was * configured, and ranking by cosine similarity reduced the * candidate set to `includedCount`. Caller-pinned skills are * always counted. * * `scoreFloor` is the lowest cosine score that made the cut; * `scoreCeiling` is the highest. When the ranked set is empty * (every survivor is pinned / transitive-pinned and nothing was * scored), both fields are emitted as `0` (NOT `±Infinity`) so * the wire payload serialises cleanly. Use `includedCount` vs * caller-pinned size to tell "no ranking happened" apart from * "ranking happened and everything cleared at score 0". */ readonly kind: 'sdk.skill.index_filtered'; readonly catalogSize: number; readonly includedCount: number; readonly scoreFloor: number; readonly scoreCeiling: number; readonly embedderId: string; } | { readonly kind: 'sdk.skill.index_overflow_pinned'; readonly skill: string; readonly charsRequired: number; readonly budget: number; } | { readonly kind: 'sdk.skill.pin_omitted'; readonly skill: string; readonly reason: 'budget_hard_cap_exceeded'; readonly charsUsed: number; readonly hardCap: number; } | { readonly kind: 'sdk.skill.loaded'; readonly skill: string; readonly bytes: number; readonly cached: boolean; } | { readonly kind: 'sdk.skill.file_loaded'; readonly skill: string; readonly path: string; readonly bytes: number; readonly cached: boolean; } | { readonly kind: 'sdk.skill.file_rejected'; readonly skill: string; readonly path: string; readonly reason: string; } | { readonly kind: 'sdk.skill.invocation_denied'; readonly skill: string; readonly reason: 'not_found' | 'user_invocable_only' | 'third_party_without_trust' | 'min_sdk_version_unmet' | 'requires_cycle' | 'requires_unresolved' | 'requires_trust_violation' | 'requires_cap_exceeded'; } | { readonly kind: 'sdk.skill.activated'; readonly skill: string; readonly source: 'caller' | 'llm'; readonly tier: SkillTrustTier; } | { /** * Caller pin overrode the skill's `disable-model-invocation: true` * gate. Audit-only — caller intent is treated as the human-in-the-loop * waiver per `skills-activation.md` §2.85. */ readonly kind: 'sdk.skill.disable_model_invocation_overridden'; readonly skill: string; readonly source: 'caller'; } | { /** * `allowed-tools` was honored — tools are eligible for auto-approval * while the skill is active. Emitted on trusted-tier activation * (or third-party + `trustedSources` opt-in). * * SDK 2.0 doesn't own the model loop, so the SDK records the intent * via this event for downstream observability + future auto-approval * wire field; effective auto-approval is QAR-side. */ readonly kind: 'sdk.skill.allowed_tools_applied'; readonly skill: string; readonly tools: readonly string[]; } | { /** * Third-party skill's `allowed-tools` parsed but NOT honored. Surfaces * what the skill *would* grant if added to `trustedSources`. */ readonly kind: 'sdk.skill.allowed_tools_advisory'; readonly skill: string; readonly tools: readonly string[]; } | { /** * One or more entries in a skill's `allowed-tools` referenced tools * that don't exist in the configured MCP catalog. The skill still * activates; the missing tools just won't be auto-approved. */ readonly kind: 'sdk.skill.allowed_tools_unmatched'; readonly skill: string; readonly missing: readonly string[]; } | { /** * Third-party skill declared lifecycle `hooks:` that were ignored. * Hooks are too powerful to gate by content-substring; for the * third-party tier the only safe policy is "never honor" * (`skills-trust.md` §6). */ readonly kind: 'sdk.skill.hooks_ignored'; readonly skill: string; } | { /** * Trust tier was assigned at discovery time. Emitted once per skill * during catalog construction for security-audit log replay. * (`skills-trust.md` §7.) */ readonly kind: 'sdk.skill.tier_assigned'; readonly skill: string; readonly tier: SkillTrustTier; } | { /** * A skill's `requires:` chain pulled in a third-party dependency * whose source isn't in `trustedSources`. Parent activation fails * closed; no partial body leak (`skills-trust.md` §3 — "requires * enforces the trust gate transitively"). */ readonly kind: 'sdk.skill.requires_trust_denied'; readonly parent: string; readonly child: string; readonly reason: string; } | { /** * A `requires:` chain breached one of the three caps (depth / * breadth / token budget). Parent activation fails closed. */ readonly kind: 'sdk.skill.requires_cap_exceeded'; readonly parent: string; readonly kind_breach: 'depth' | 'breadth' | 'token_budget'; readonly limit: number; readonly actual: number; } | { readonly kind: 'sdk.skill.substitution_warn'; readonly skill: string; readonly reference: string; readonly reason: 'out_of_bounds' | 'unknown_named' | 'malformed'; } | { readonly kind: 'sdk.skill.lock_drift'; readonly fqn: string; readonly expectedSha256: string; readonly actualSha256: string; } | { readonly kind: 'sdk.skill.lock_unreachable'; readonly fqn: string; readonly source: SkillSource; } | { /** * A `!command` block was rendered but its execution was suppressed. * * Reasons: * - `'tier_third_party'` — skill is third-party; tier gate blocks * all execution regardless of mode. * - `'not_allowlisted'` — `allowlisted` mode and the first * argv token was not in `allow` (or any line in a multi-line * body failed the allowlist gate). * - `'powershell_disabled'` — skill declared `shell: powershell` * but the consumer hasn't set the env-var gate. */ readonly kind: 'sdk.skill.preprocess_blocked'; readonly skill: string; readonly command: string; readonly reason: 'tier_third_party' | 'not_allowlisted' | 'powershell_disabled'; } | { /** * A `!command` block was executed and its output spliced into the * rendered body. Emitted for every execution, including errors. */ readonly kind: 'sdk.skill.preprocess_executed'; readonly skill: string; readonly command: string; readonly mode: SkillsPreprocessingMode; readonly durationMs: number; /** Process exit code; `null` when the process was killed by timeout. */ readonly exitCode: number | null; /** Bytes of stdout+stderr buffered (post-truncation). */ readonly outputBytes: number; /** `true` iff output exceeded `maxOutputBytes` and was cut. */ readonly truncated: boolean; /** `true` iff timeout fired and the process was killed. */ readonly timedOut: boolean; } | { readonly kind: 'sdk.skill.fork_declined'; readonly skill: string; readonly agent?: string; readonly reason: 'no_multi_agent'; } | { /** * A previously-discovered skill no longer satisfies discovery * (file deleted, parse error introduced, source path removed). * Emitted during a watcher-triggered rebuild only — the initial * `discover()` has no prior catalog to diff against. */ readonly kind: 'sdk.skill.removed'; readonly fqn: string; readonly source: SkillSource; } | { /** * The catalog epoch counter advanced. Every successful rebuild * (initial discover OR watcher-triggered rebuild that produced * any delta) increments the epoch by one. * * Body caches that key by `(fqn, epoch)` evict stale entries * once no in-flight run references them; this event surfaces * the bump so observability dashboards can correlate cache-miss * rate with hot-reload activity. */ readonly kind: 'sdk.skill.catalog_epoch_bumped'; readonly from: number; readonly to: number; readonly reason: 'initial_discover' | 'watcher_event'; }; /** The kind discriminator value for `SkillEvent`. */ export type SkillEventKind = SkillEvent['kind']; /** Optional consumer-supplied telemetry sink. Errors thrown inside are caught and ignored. */ export type SkillsEventSink = (event: SkillEvent) => void; /** * Per-entry `description + when_to_use` cap in the slim index. Mirrors * Claude Code's `SLASH_COMMAND_TOOL_CHAR_BUDGET` so OSS skills land * identically. */ export declare const SLIM_INDEX_ENTRY_BUDGET = 1536; /** Default total cap on the `` block. */ export declare const DEFAULT_INDEX_CHAR_BUDGET = 8000; /** Hard cap on `SKILL.md` body size — exclusion threshold. */ export declare const SKILL_BODY_HARD_BYTES: number; /** * Hard cap on total `SKILL.md` file size (frontmatter + body + structure) * enforced *before* `fs.readFile` — files larger than this are rejected * with `sdk.skill.error` severity `'error'` and never read into memory. * Comfortably above `SKILL_BODY_HARD_BYTES` so a generous frontmatter * fits, but tight enough to defend against a third-party `package` / * `cli-fetched` source shipping multi-megabyte SKILL.md files — * defense-in-depth against DoS via unbounded `fs.readFile`. */ export declare const SKILL_FILE_HARD_BYTES: number; /** Soft cap on `SKILL.md` body lines — emits warning, does not exclude. */ export declare const SKILL_BODY_SOFT_LINES = 500; /** Max chars in a skill `name`. Reserved-word + character-class checks live in the validator. */ export declare const SKILL_NAME_MAX_CHARS = 64; /** Reserved words a skill `name` cannot include (case-insensitive substring). */ export declare const SKILL_NAME_RESERVED_WORDS: readonly string[]; /** Default `vendor` per source type. Frontmatter `vendor:` overrides. */ export declare const SOURCE_DEFAULT_VENDORS: Readonly>; /** Trust tier per source type. Mirror of `SkillTrustTier` documentation. */ export declare const SOURCE_TRUST_TIERS: Readonly>; /** Default precedence (highest first). */ export declare const DEFAULT_SOURCE_ORDER: readonly SkillSourceType[]; //# sourceMappingURL=types.d.ts.map