import { type Context } from '@deepseek-ai/cordis'; import type { GateResult, ValidationResult } from '@mstar-harness/engine'; import type { FsTarget, FsWriteIntent } from '@deepseek-ai/dsh-fs'; import { HarnessResolver } from './_shared.ts'; import type { Config } from './_shared.ts'; /** * Advisory emitted on skill-lint gate decisions (the `mstar/status-gate` * advisory pattern reused for the skill-authoring gate). Emitted * when a `SKILL.md` write-intent under a configured skill root finds lint * violations in the pre-write on-disk document (warn mode), when hard mode * allows an ALREADY-invalid document as a repair escape, and when the gate * degrades to allow after an unexpected internal error. Clean passes stay * silent. * * The gate NEVER throws on the listener path (status-gate repair-escape * semantics): the intent waterfall carries no incoming content, so the only * lint signal is the pre-write on-disk state; the typed hard veto lives on * the incoming-document branch (`lintSkillWrite`, `SkillLintVetoError`). */ export interface SkillLintAdvisory { /** Which intent slot passed the gate (write-intent only — skills have no linted edit slot). */ operation: 'write'; /** `displayPath` of the guarded SKILL.md. */ target: string; /** Canonical skill-root form of the target (`resolveSkillRoot('dsh', …)` form). */ canonical: string; /** The lint verdict (warn-mode: `hardBlocked` false; hard repair escape: `hardBlocked` true). */ result: GateResult; /** Resolved enforcement flag: false for warn-mode advisories, true for hard-mode repair escapes. */ hard: boolean; /** True when hard mode allowed a write to an ALREADY-invalid document (repair escape). */ repair?: boolean; /** True when the gate errored internally and degraded to allow (error-containment envelope). */ degraded?: boolean; } /** * Typed hard-mode veto for the skill lint gate (the dsh fs-policy veto * channel: "veto = throw"; the write tool turns the throw into an isError * tool result carrying `{ name, code }`). Thrown ONLY by * {@link lintSkillWrite} — the entry that lints a KNOWN incoming document * (the brief's "against the incoming doc when available" branch). The * content-blind `fs/write-intent` listener never throws: it cannot * distinguish a repair from a re-violation, so hard mode degrades to the * status-gate repair escape there (see {@link gateSkillIntent}). */ export declare class SkillLintVetoError extends Error { /** Stable code for tool-result serialization (the `{ name, code }` convention). */ readonly code: 'skill-lint.veto'; /** The lint violations that caused the veto. */ readonly violations: readonly ValidationResult[]; constructor(target: string, violations: readonly ValidationResult[]); } /** * Lint one SKILL.md document with the engine skill-authoring lints * (`lintFrontmatter` + `lintFiveQuestion` + `findEphemeralCitations` — the * CLI `mstar skill lint` combination plus the ephemeral-citation gate; * violation codes `lint.frontmatter.*` / `skill-authoring.five-question.*` * / `skill.ephemeral.*`). Pure: no enforcement, no I/O. * * Five-question profile (spec A4): the shared Engine classifier * (`classifySkillLint`) selects the mode from `options.skillId` — the * trusted resolved skill-target basename passed by the fs-intent path and * `lintSkillWrite`. A doc-only call (no `skillId`) stays strict authoring — * the greenfield default is never loosened by an unparented document. The * frontmatter and ephemeral-citation checks run in every profile; `core` * (exact `mstar-harness-core`) skips only the five-question check. * @param doc - the full SKILL.md text. * @param options - `skillId`: the resolved skill-target directory basename * (trusted boundary identity — never the doc's YAML `name` alone). */ export declare function lintSkillDoc(doc: string, options?: { skillId?: string; }): GateResult; /** * Enforce the skill-authoring lints over a KNOWN document (the brief's * "incoming doc when available" branch): `Enforcement: hard` + violations → * throw the typed {@link SkillLintVetoError} (fs-policy veto channel); warn * mode → return the gate for advisory logging. A repairing write carries a * VALID incoming document and passes by construction — no repair escape is * needed on this branch. The content-blind listener path (where the * incoming doc is never visible) routes through {@link gateSkillIntent} * instead, which applies the status-gate repair-escape decision. * * The lint profile is classified from the trusted resolved skill-target * basename (spec A4): `basename(dirname(resolve(options.target)))` — the * write's own target, never the document's YAML `name`. * @param doc - the document about to be written (the write's content). * @param options - target display path (veto message + profile identity) + * resolved hard flag. */ export declare function lintSkillWrite(doc: string, options: { target: string; hard: boolean; }): GateResult; /** * `fs/write-intent` listener for the skill lint gate. Registered with * `prepend` for the same reachability reason as the status gate: the slot * is first-wins by registration order (dsh-fs-policy README), so without * prepend a policy plugin mounted earlier would make this gate unreachable. * Every gate decision (warn advisory, repair escape, degraded allow) calls * `next()` — the skill lint gate never owns the intent decision and must * not terminate the chain (fs-policy's observed-state CAS on skill files * stays live in composed deployments). */ export declare function skillWriteIntentListener(ctx: Context, resolver: HarnessResolver, config: Config, target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise): Promise;