/** * Skill subject module: declared/resolved/actual payload types, scanner * composition, and projections via the shared `projectInstalledExtensions` * helper. * * Per Decision 4 (single service + per-subject modules) and Decision 9 * (read-model rows) of the workspace read-model design, this module owns: * * - the subject-specific declared/resolved/actual payload types * (`DeclaredSkills`, `ResolvedSkills`, `ActualSkills`); * - the subject-specific origin union (`SkillDetectionOrigin`) and * skill-specific facts (`contentRoot`, `sourcePath`, `packageRoot`, * `hasSkillMd`, `hasSkillJson`); * - scanner composition (canonical-extensions + agent-dir × skill-rendering * agents); * - the `installed` / `active` / `unmanaged` projections, wired * through the helper with a skill-specific `SubjectPolicy`. * * The factory `makeSkillExtensionsApi(deps)` returns a `SkillExtensionsApi` * with cells whose public types are dependency-closed. Phase 9 composes the * factory inputs (`loaders`, `scanners`, `installedPacks`, `diagnostics`) * inside `WorkspaceReadModelLive`. */ import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import type { AgentId } from "../../../agents/types.js"; import { type ExtensionName } from "../../../extensions/common.js"; import type { Lockfile, SkillLockEntry } from "../../../lockfile/schema.js"; import type { Settings, SkillEntry } from "../../../settings/schema.js"; import type { Diagnostics } from "../diagnostics.js"; import type { LockfileReadError, SettingsReadError } from "../errors.js"; import type { AgentDirOccurrence, CanonicalExtensionOccurrence } from "../scanners/types.js"; import type { ActivationState, ExtensionKey, InstallationOrigin, InstalledPackRef, Scope } from "../types.js"; /** * Subject-specific origin discriminator for an `ActualSkill`. Mirrors the spec * scenarios that distinguish canonical AXM, external AXM, and agent-rendered * skill directories. */ export type SkillDetectionOrigin = { readonly _tag: "canonical-axm-skill"; } | { readonly _tag: "external-axm-skill"; } | { readonly _tag: "agent-skill-dir"; readonly agentId: AgentId; }; /** * One declared skill entry. Wraps the raw settings entry without re-shaping * fields; `name` is lifted out for ergonomic lookup. */ export interface DeclaredSkill { readonly name: ExtensionName; readonly entry: SkillEntry; } /** Decoded skills declared in `axm.json`. */ export type DeclaredSkills = ReadonlyArray; /** One resolved skill entry from the lockfile, wrapping the raw lock entry. */ export interface ResolvedSkill { readonly name: ExtensionName; readonly lockEntry: SkillLockEntry; } /** Decoded skills resolved in the lockfile. */ export type ResolvedSkills = ReadonlyArray; /** * One observable skill materialization. Carries the subject-specific origin * plus skill-specific facts: * * - `contentRoot` — the directory containing the rendered skill (the same * value as the underlying scanner's `contentLocation`); * - `sourcePath` — absolute path to the canonical content file inside * `contentRoot` (`SKILL.md`); equal to `null` if the file does not exist; * - `packageRoot` — for canonical/external AXM, the registry-publish package * root (parent of `src//`); `null` for agent-rendered skills; * - `hasSkillMd` — convenience boolean derived from `sourcePath`; * - `hasSkillJson` — placeholder for the optional `skill.json` companion file * (left `false` in v1; Phase 9 wiring will flip when `agent-settings` or a * future scanner detects it). */ export interface ActualSkill { readonly key: ExtensionKey<"skill">; readonly origin: SkillDetectionOrigin; readonly contentRoot: string; readonly sourcePath: string | null; readonly packageRoot: string | null; readonly hasSkillMd: boolean; readonly hasSkillJson: boolean; } /** Actual skills payload — array of observed materialization occurrences. */ export type ActualSkills = ReadonlyArray; /** Pack-member entry for a skill: per Decision 9 it is the resolved member. */ export interface SkillPackMember { readonly name: ExtensionName; readonly providingPack: InstalledPackRef; } /** Installed skill row. */ export interface InstalledSkill { readonly key: ExtensionKey<"skill">; readonly installationOrigin: InstallationOrigin; readonly activation: ActivationState; readonly resolved: Option.Option; readonly actual: ReadonlyArray; readonly providingPacks: ReadonlyArray; } /** Unmanaged skill row — one actual occurrence not attached to an installed row. */ export interface UnmanagedSkill { readonly key: ExtensionKey<"skill">; readonly actual: ActualSkill; } /** * Cached source loaders the factory captures. `Effect.cached` is applied at * the loader site (Phase 4); the helper just consumes whatever shape the * caller passes in. */ export interface SkillScopedLoaders { readonly settings: Effect.Effect, SettingsReadError>; readonly lockfile: Effect.Effect, LockfileReadError>; } /** * Cached scanner outputs the factory captures. The factory composes * canonical-extensions + agent-dir occurrences into the actual skill array. */ export interface SkillScanners { readonly canonical: Effect.Effect>; readonly agentDir: Effect.Effect>; } /** * One installed-pack entry consumed by the projection. Pack-resolved member * groups are read from the **installed pack manifest** in Phase 9; for unit * tests the caller passes a synthetic shape. */ export interface InstalledPackForSkills { readonly ref: InstalledPackRef; readonly skills: ReadonlyArray; } /** * Inputs `makeSkillExtensionsApi` captures. */ export interface SkillExtensionsApiDeps { readonly scope: Scope; readonly loaders: SkillScopedLoaders; readonly scanners: SkillScanners; readonly installedPacks: Effect.Effect, SettingsReadError | LockfileReadError>; readonly diagnostics: Diagnostics; } /** * Public skill API exposed by `ctx.scope(scope).skills`. Cells are * dependency-closed and never carry `FileSystem | Path` requirements. */ export interface SkillExtensionsApi { readonly declared: Effect.Effect, SettingsReadError>; readonly resolved: Effect.Effect, LockfileReadError>; readonly actual: Effect.Effect; readonly installed: Effect.Effect, SettingsReadError | LockfileReadError>; readonly byName: (name: string) => Effect.Effect, SettingsReadError | LockfileReadError>; readonly declaredByName: (name: string) => Effect.Effect, SettingsReadError>; readonly active: Effect.Effect, SettingsReadError | LockfileReadError>; readonly unmanaged: Effect.Effect, SettingsReadError | LockfileReadError>; } /** * Build the skill subject API over the captured loaders, scanners, and pack * set. * * Returns an `Effect` because the projection cell is wrapped in * `Effect.cached` so all four derived cells (`installed` / `active` / * `unmanaged`) share a single in-flight execution and the * projection — including its diagnostic side effects — runs at most once per * scope, mirroring the `state.ts` loader pattern. */ export declare const makeSkillExtensionsApi: (deps: SkillExtensionsApiDeps) => Effect.Effect; //# sourceMappingURL=skill.d.ts.map