/** * The skills seam as the daemon holds it: ONE store over `/skills` + the one thing the * daemon derives from it — the engine env (`WICKED_SKILLS_SNAPSHOT`). * * `apply()` is called at boot (`createServer`), after the root passed the fence (root-fence.ts): * seed from the live plugin when the root is empty, publish a first snapshot when none exists, * export the resolved snapshot path. There is NO skills setting to re-apply on `PUT /settings` — * the root is `/skills`, full stop (design v3.1 §1 one storage root; v3.2 §1 never a * user CLI directory; codex round 5 on #480 retired `skills_root` and `WICKED_CREW_SKILLS_ROOT`: * a configurable root let seeding write into `~/.codex/skills`). `apply` never throws — but it does * NOT fail open either. The degradation ladder (design v3 §3; codex review of #480) distinguishes * three outcomes: * * published `current` verified → `WICKED_SKILLS_SNAPSHOT=`. * fallback ABSENT configuration — no wicked-garden installed, nothing to seed from. The ONE * case the engine input is restored to the value this process booted with (an * operator export or the test harness's arming survives; unset when there was * none): the engine's own ladder falls back to the live installed cache and logs * `skills.fallback`. Finding `skills.fallback` (warning). The log line says which * of the two happened — "restored to " or "left unset" — never one for the * other (Copilot on #480). * blocked the FIRST publish is blocked (a defective catalog: a reference that ESCAPES the * plugin root, a missing required skill, a missing plugin catalog, a file outside * the bundle closure, a baseline env that could not be provisioned — a reference * whose target is merely MISSING is a warning and publishes, design v3.4 §1). * There is no snapshot to offer and the live cache is NOT a * substitute — the engine input is pointed at a path that does not exist * (`/refused/skills.blocked`), which the engine treats as an invalid explicit * path: every launch fails loudly naming it until the operator fixes the catalog * and publishes. Finding `skills.blocked` (error). * config-error an INVALID configuration — a corrupt manifest, a `current` link that fails * verification, a root that is not the directory the store bound. Same refusal path * (`/refused/skills.config`), same loud launch failure: recorded disablement * is never bypassed by "restoring" the live cache. Finding `skills.config` (error). * * Orthogonal to the ladder, one persistent WARNING (design v3.6, #490): finding `skills.source` is * present while the CURRENT baseline was captured from the installer-managed copy * (`source.kind: 'installer-copy'` — plugin-source.ts tier 3): the daemon works on an installer-only * machine, but that copy receives no marketplace updates until the plugin is registered with Claude * Code. It is judged live from the manifest on every `health()` read, so it appears with the seed * and disappears with the first refresh from the marketplace cache (a byte-identical refresh * re-records the provenance too). That read FAILS CLOSED (codex on #491): a manifest that cannot be * read now is reported as `config-error` with a `skills.manifest` error finding naming the cause — * never the stale outcome the ladder recorded at boot. A read never touches the engine input. * * A second orthogonal WARNING (F-083): finding `skills.stale-rules` is present while the CURRENT * generation was published under other portability rules than this daemon runs (an older * publisher — every pre-0.7.31 snapshot records no rules identity at all). The generation is * accepted and exported unchanged (`state: published`); the finding names the rows that derive * differently today (up to five, the count carries the rest) and the remedy — a re-publish, which * records the running identity, re-lays the copilot view, rewrites the rows and clears it. A rule * change is not tampering: before this, the store's row cross-check refused such a generation * outright, and the 0.7.29 → 0.7.30 upgrade left every seat without skills until an operator * re-published. It is judged where the generation is verified (`afterPublish`: boot and every * publish), never re-judged on a read — staleness changes only with a publish or an upgrade. * * What the engine is handed is EXACTLY ONE variable, `WICKED_SKILLS_SNAPSHOT` = the absolute REAL * path of `snapshots/` (v3.1 §2, v3.4 §2); `WICKED_SKILLS_CURRENT` is withdrawn and never set, * and `WICKED_CREW_STATE_HOME` is RETIRED as an engine input (v3.4 §2): core derives the state home * it fences from the snapshot path's fixed layout (`/skills/snapshots/` — parent * `snapshots`, grandparent `skills`, else a config error naming the path). Because the root IS * `/skills` (asserted at boot, identity-checked on every operation), that layout holds * by construction. The canonical state home is still REPORTED (`health().stateHome`) for humans. * * NOTHING here writes into the user's own CLI directories (design v3.2 §1): the v3 additive * mirror into the user's codex/pi/copilot/opencode skill dirs is withdrawn, and with it the * `skills_mirror` setting. Skills reach non-Claude workers only through the per-launch delivery * core performs from the snapshot (the generated `views/copilot/` for copilot, `--skill` lists for * pi); a CLI without a lever runs without wicked skills — never through a side channel into the * user's home. * * `health()` is the last outcome, surfaced read-only on `GET /diagnostics` (`skills`). */ import type { LaunchNotice } from '../core/adapter.js'; import type { CoreEvent, SkillPublishResult } from '../core/types.js'; import { type BaseSkillPolicy, type BaseSkillPosture } from './base-skill.js'; import type { InstalledPluginState } from './installed.js'; import { REFUSED_DIRNAME } from './root-names.js'; import { type CurrentSnapshot, type SkillsStore } from './store.js'; import { type Pacer } from './tree.js'; /** * crew#753: the blocking `skills.garden` finding — found vs required, and the one install command. * `found` is the version and where it is. */ export declare function gardenTooOldFinding(found: { version: string; path: string; }, what: string): SkillsHealthFinding; /** The refusal sentinel directory name — from the ONE table of root names (design v3.5 §2); re-exported for the tests. */ export { REFUSED_DIRNAME }; /** `publishing` (crew#1145): the daemon serves while the boot ladder (seed, verify or publish, export) * runs in the background; launches wait for it (`CoreAdapter.setLaunchGate`) and the Nerve Center's * skills row says "publishing". It ends in one of the other states. */ export type SkillsHealthState = 'published' | 'fallback' | 'blocked' | 'config-error' | 'disabled' | 'publishing'; /** `skills.stale-rules` (F-083) is emitted ahead of its `wicked-crew-api-types` declaration — the next api-types cut adds it to `DiagnosticsSkillsFinding.kind`. */ /** `skills.base-skill` (crew#554 / DES-L4 PR-⑧): the configured base skill is not in the published generation — an ERROR (the engine refuses launches at intake; `'require'` is the only policy). */ /** `skills.garden` (crew#753): the installed wicked-garden found at seed is older than {@link REQUIRED_GARDEN_VERSION} — an ERROR; launches and onboarding are refused until a newer garden is installed. */ /** `skills.publish-warning` (F-E2E-002): one per warning the publish of the CURRENT generation landed with (a broken reference the skill's author owns) — published as found, reported here so the warnings are not only in the daemon log. */ export type SkillsHealthFindingKind = 'skills.fallback' | 'skills.blocked' | 'skills.config' | 'skills.source' | 'skills.manifest' | 'skills.stale-rules' | 'skills.base-skill' | 'skills.phase-skill' | 'skills.garden' | 'skills.publish-warning' | 'skills.installed-ahead' | 'skills.publishing'; export interface SkillsHealthFinding { kind: SkillsHealthFindingKind; severity: 'info' | 'warning' | 'error'; message: string; } /** The seam's last outcome — what `GET /diagnostics` reports as `skills`. */ export interface SkillsHealth { state: SkillsHealthState; /** The skills root (`/skills`), or `null` when the daemon booted without the seam. */ root: string | null; /** The verified published snapshot, or `null`. */ current: { gen: number; path: string; } | null; /** What `WICKED_SKILLS_SNAPSHOT` is exported as right now (`null` = unset / boot value). */ engineInput: string | null; /** The canonical daemon state home — reported for HUMANS (`GET /diagnostics`); NOT an engine input * (v3.4 §2: core reads only `WICKED_SKILLS_SNAPSHOT` and derives the state home from its layout). * `null` when `disabled`. */ stateHome: string | null; findings: SkillsHealthFinding[]; /** The BASE skill posture (crew#554 / core#468): the role-keyed discipline skill the engine is * handed as `WICKED_BASE_SKILL_REF` for the next launch, judged against `current` — or `null` * when the setting is off (`baseSkillRef: ""`) or the seam is `disabled`. Its finding (when the * generation lacks the skill) also rides `findings`. Additive. */ baseSkill: BaseSkillPosture | null; } /** The `skills` block a daemon booted WITHOUT the seam reports. */ export declare function disabledSkillsHealth(): SkillsHealth; /** * The path the engine input is pointed at when the daemon has NO valid snapshot to offer and must * not fall back: it does not exist, so the engine's "explicit path invalid → launch fails loudly" * rung fires, and the path itself names why (`…/refused/skills.blocked`). */ export declare function refusalPath(root: string, kind: 'skills.blocked' | 'skills.config' | 'skills.garden' | 'skills.publishing'): string; /** Is `path` the `refused/skills.publishing` placeholder the engine input holds while the ladder runs (either separator)? */ export declare function isPublishingPlaceholder(path: string): boolean; export interface SkillsRuntimeOptions { store: SkillsStore; log: (message: string) => void; /** The `WICKED_SKILLS_SNAPSHOT` this process BOOTED with (default: the real one, `BOOT_SKILLS_SNAPSHOT`); tests inject `''` / `undefined`. */ bootSnapshot?: string | undefined; /** crew#874: how long one installed-plugin probe (a bundle read + hash) is reused by `health()` (default 60 s). */ installedTtlMs?: number; /** crew#1021: maximum wait for a fresh probe; a timeout serves the last value (default 5 s). */ installedTimeoutMs?: number; /** Clock for that cache (tests). */ nowMs?: () => number; } /** crew#874: the env switch for the boot auto-refresh — ON unless set to `0` / `false` / `off`. */ export declare const SKILLS_AUTO_REFRESH_ENV = "WICKED_CREW_SKILLS_AUTO_REFRESH"; /** What `autoRefreshOnBoot` did (crew#874), for the boot log and tests. */ export type AutoRefreshOutcome = { action: 'none'; reason: string; } | { action: 'skipped'; reason: string; } | { action: 'published'; gen: number; from: string | null; to: string; } | { action: 'failed'; reason: string; }; export declare class SkillsRuntime { readonly store: SkillsStore; private readonly log; private readonly bootSnapshot; private lastHealth; /** The base skill setting (crew#554) — `null` until `configureBaseSkill` runs (boot reads settings.json first). */ private baseSkillConfig; private lastBaseSkill; /** The last `skills.base-skill` message logged — the seam says a finding once per change, not per re-export. */ private lastBaseSkillLogged; /** * F-E2E-002: the warnings the publish that minted generation `gen` landed with, as diagnostics * findings. Reported while `current` IS that generation (a later publish mints another gen and * answers its own). Held in memory: a restart re-verifies `current` but does not re-scan it, so * after a restart the warnings are back to the log line of the publish that found them. */ private publishWarnings; /** crew#874: the last installed-plugin probe and when it ran (a bundle hash is not free; `health()` runs per diagnostics read). */ private installedProbe; private readonly installedTtlMs; private readonly installedTimeoutMs; private installedInFlight; private readonly nowMs; /** crew#874: why the boot auto-refresh did not run (said in the finding), or `null`. */ private autoRefreshNote; /** crew#874: the published generation's `gardenSource`, keyed by its path (immutable per generation). */ private publishedGardenCache; /** crew#1145: the boot ladder (`apply` then `autoRefreshOnBoot`); settled at once until `startBoot` runs it. */ private bootRun; /** crew#1145: the generation the ladder last verified and exported — read for the seams' arm lines without a re-walk. */ private lastVerified; constructor(opts: SkillsRuntimeOptions); /** crew#1021: request reads serve the last probe and schedule stale work off the request path. */ installed(): InstalledPluginState | null; /** * crew#1021: a fresh caller waits for any pre-call probe, then starts (or joins) a newer one. * One deadline covers both waits; after it expires no follow-up probe is started for that caller. * A timed-out probe may still land later; reads serve the last completed reading until then. */ refreshInstalled(opts?: { fresh?: boolean; }): Promise; /** Background callers share one probe and need no deadline timer. */ private probeInstalled; /** * crew#874: the installed wicked-garden is not the one the published generation was built from. * Every governed run then executes the OLDER garden's skills and scripts while the operator * believes the upgrade landed — and nothing on diagnostics or the launch said so. Compared by * CONTENT HASH (`installed.baseline` vs `manifest.baseline`, the identity `refresh-baseline` * decides on), named by version. `null` when they agree, when nothing is published, or when the * installed bundle cannot be read (that comparison is `GET /skills`' `installed.unreadable`). */ installedAheadFinding(opts: { fresh: true; }): Promise; installedAheadFinding(opts?: { fresh?: false; }): SkillsHealthFinding | null; private findingForInstalled; /** `snapshot.json` `gardenSource` of the generation at `path`, read once per path. Never throws. */ private publishedGarden; /** * crew#1145: run the boot ladder — `apply`, then `autoRefreshOnBoot` — WITHOUT holding the daemon's * listen. A warm boot spent 50 s here on the rig (the walk and hash of the current generation) * before the port opened. From this call until the ladder lands, `health()` reports `publishing`; * the daemon's launch gate and the `/skills` routes wait on {@link bootSettled}, so no launch reads * the engine input before the ladder decided it. Never rejects. `onSettled` gets the outcome and * the ladder's wall time, for the boot log. */ startBoot(onSettled?: (health: SkillsHealth, ms: number) => void): Promise; /** crew#1145: settles once the boot ladder has landed (at once when it ran inline, or never started). */ bootSettled(): Promise; /** * crew#1145: does the generation the ladder last verified and EXPORTED hold skill `name`? Its * metadata only, no re-walk — for the interactive seams' one arm-time log line, which used to walk * the whole generation per seam on the boot path. Launch admission keeps {@link holdsSkill}. */ exportedHolds(name: string): boolean; /** * crew#874: at boot, when the installed garden differs from the baseline AND the catalog holds no * operator change (`store.pristine()`), refresh the baseline and publish — what the operator would * do by hand after every garden upgrade, and the step whose omission ran the rig on garden 12.39. * Anything user-edited, a refresh that does not come back clean, or a blocked publish leaves the * current generation in place and is said in the `skills.installed-ahead` finding. Off with * `WICKED_CREW_SKILLS_AUTO_REFRESH=0`. With auto-refresh off, the installed-ahead finding * appears once the background constructor probe lands (crew#1021). Never throws. */ autoRefreshOnBoot(env?: NodeJS.ProcessEnv): Promise; private autoRefreshInner; /** * Take the base skill setting (crew#554 — `SystemSettings.baseSkillRef` / `baseSkillPolicy`) and * apply it NOW: at boot (before `apply`, so the ladder's outcome re-judges it) and on every * `PUT /settings` that changes it. `baseSkillRef: ""` (or blank) is OFF — the engine variable is * deleted. Answers the posture applied. */ configureBaseSkill(settings: { baseSkillRef?: string | undefined; baseSkillPolicy?: BaseSkillPolicy | undefined; }): BaseSkillPosture | null; /** The base skill posture the engine is handed right now — `null` when off or unconfigured. */ baseSkill(): BaseSkillPosture | null; /** * crew#1149: the base skill posture as REPORTED. While the ladder is still publishing nothing is * published to judge against, so the posture's "not handed" finding would tell the user to install * what they have; it is withheld (launches wait anyway) and {@link publishingFinding} says why. */ reportedBaseSkill(): BaseSkillPosture | null; /** The single info finding reported while the ladder is publishing; `null` once it has landed. */ publishingFinding(): SkillsHealthFinding | null; /** * Re-judge the base skill against the CURRENT published generation and the editor catalog, and * export (or delete) `WICKED_BASE_SKILL_REF` accordingly (base-skill.ts). Called by every ladder * outcome (`record`), after every publish, after a baseline refresh (the catalog moved, so * `inCatalog` did) and on a settings change. Never throws: a generation whose metadata will not * re-read counts as "nothing known to be handed" — under `warn` that unsets the variable and * warns; under `require` the engine refuses at intake, which is the policy's whole point. */ refreshBaseSkill(verified?: CurrentSnapshot): BaseSkillPosture | null; /** * The seam's last outcome (diagnostics) — plus the live `skills.source` warning when the current * baseline is the installer copy (design v3.6). Only a seeded root (`published` / `blocked`) has a * current baseline to judge; the other rungs have no manifest, or one their own finding condemns. */ /** * Does the PUBLISHED snapshot the engine is handed hold skill `name`? The interactive seams gate * their `skill_ref` on this (interactive/draft-skill.ts): the engine refuses a run whose `skill_ref` * the snapshot lacks, so a seam must know before it stamps. Answered from the VERIFIED current * generation's own rows (`store.currentSnapshotSkills()` — only ENABLED skills are ever published), * not from the editor manifest (F-E2E-042 / DES-L6 PR-L6-1 (d)): a skill enabled AFTER the last * publish is in the catalog but not in what the engine is handed, and used to be answered `true` * here and refused by the engine at unit 1. `false` on every non-published state (fallback, * blocked, config-error, disabled) and on an unverifiable or unreadable generation — never a guess. */ holdsSkill(name: string): boolean; health(): SkillsHealth; /** The reported block carries the base skill posture and its finding (crew#554) — the seam's one answer to "is the discipline skill handed". */ private withBaseSkill; /** Boot entry point. Never throws; never fails open (module header). Idempotent: a seeded, published root is only re-verified. */ apply(): Promise; /** Say once, at boot, that the current baseline is the installer copy (design v3.6) — the finding itself persists in `health()`. */ private logSourceWarning; /** * One CoreEvent from the daemon's event listener: pins the generation the engine reports it * handed the live run the event belongs to, releases (and reaps) at the run's terminal frame — * live-generations.ts. */ observe(event: CoreEvent): void; /** * One launch notice from the adapter (`CoreAdapter.onLaunch`): the daemon is handing a run or * campaign to the engine — open its launch pin at the generation the env exports right now, * BEFORE the engine call, so no spawn can read the env ahead of the pin — or the engine refused * it (release the pin; nothing will spawn). The pin is otherwise released only by the engine's * `skillsSnapshotHanded` report or the terminal frame — never by publish count (codex round 4). */ launched(notice: LaunchNotice): void; /** * Remember what a publish that minted a generation landed with (F-E2E-002), so `health()` reports * its warnings beside the generation instead of only logging them. Called by the boot ladder and * by `POST /skills/publish` before the export; an `unchanged` publish minted nothing and keeps * what the generation already reports. */ notePublished(result: Pick): void; /** * Export the VERIFIED current snapshot for the engine — the ONE engine input (v3.4 §2). With * nothing published (or an unverifiable `current`) the engine input is NOT touched here — `apply` * decided it. Answers the health it recorded, or `null` when there was nothing to export. */ afterPublish(): SkillsHealth | null; /** * `afterPublish` on the event loop (crew#852): the generation's verification — a full walk and * hash of what was just published — runs as paced steps, so the publish route's response and * every other request keep moving while it verifies. Same ladder, same record. */ afterPublishPaced(pacer: Pacer): Promise; /** The ladder's `config-error` rung: `current` would not verify — the engine input is pointed at the refusal path. */ private recordConfigError; /** The ladder's upper rungs for a verified (or absent) `current`: export it, pin it, judge the rules identity, record. */ private exportCurrent; /** Store the ladder's outcome; answer it as `health()` reports it (the live `skills.source` warning included). */ private record; } //# sourceMappingURL=runtime.d.ts.map