package vip.mate.skill.manifest;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* RFC-090 Phase 2 — parsed SKILL.md manifest (source of truth, §14.6).
*
*
Maps the YAML frontmatter shape from §5.1 onto a typed model.
* Persisted to {@code mate_skill.manifest_json}; legacy columns
* (skill_type / icon / version / author) are projected from this
* after each resolve.
*
*
All collection fields default to empty so consumers don't need
* null guards. Unknown YAML keys are preserved in
* {@link #extras} for forward compatibility.
*/
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class SkillManifest {
// ==================== Identity ====================
private String id;
private String name;
private String description;
private String icon;
private String version;
private String author;
/** prompt | code | mcp | acp | knowledge */
private String type;
/** file | web | data | content | comm | system | ... (free-form tag) */
private String category;
// ==================== Tools / dependencies ====================
/** Anthropic-compatible {@code allowed-tools} list. */
@Builder.Default
private List allowedTools = List.of();
/** Top-level {@code requires} entries. */
@Builder.Default
private List requires = List.of();
/** Top-level {@code platforms} list (overall package compatibility). */
@Builder.Default
private List platforms = List.of();
/**
* v3.1 {@code features[]} matrix. Empty list means
* "no explicit feature partitioning" — the resolver synthesizes a
* single default feature carrying the top-level {@link #requires}
* and {@link #platforms} so legacy skills behave unchanged.
*/
@Builder.Default
private List features = List.of();
// ==================== User-facing settings ====================
@Builder.Default
private List settings = List.of();
// ==================== Provider routing ====================
@Builder.Default
private List requiresModel = List.of();
// ==================== Dashboard ====================
@Builder.Default
private List dashboardMetrics = List.of();
// ==================== v3 self-evolution ====================
@Builder.Default
private SelfEvolution selfEvolution = SelfEvolution.defaults();
// ==================== v3.1 knowledge type ====================
private KnowledgeBinding knowledge;
// ==================== Phase 7b — type=acp binding ====================
/** Set when {@code type=acp}. Resolves to a {@code mate_acp_endpoint} row. */
private AcpBinding acp;
// ==================== Attention-anchoring constraints ====================
/**
* Short, high-priority constraints extracted from SKILL.md that the
* agent must obey throughout the task — e.g. "never delete user
* files", "always confirm before writing", "use server B's fetch
* tool, not server A's".
*
* Unlike the full SKILL.md (which is a free-form document loaded
* via {@code load_skill} and subject to context-window trimming),
* these structured constraints are pinned into the ProgressLedger's
* pinned-entries section by ActionNode on {@code load_skill}, so they
* survive context compression and stay visible on every turn.
*
*
Empty list when the skill author didn't declare structured
* constraints — the agent then falls back to the SKILL.md content
* loaded via {@code load_skill} (protected by
* {@code PRUNE_EXEMPT_TOOLS} in ConversationWindowManager).
*/
@Builder.Default
private List constraints = List.of();
// ==================== type=code script entrypoints ====================
/**
* Declared script entrypoints from the {@code scripts} frontmatter
* block. Each entry is exposed to the model as a typed wrapper tool —
* the model fills schema-described fields and the runtime serializes
* them into process arguments, so a script consuming a JSON payload
* never depends on the model hand-crafting a JSON string.
*/
@Builder.Default
private List scripts = List.of();
// ==================== Forward-compat catch-all ====================
/** Unknown frontmatter keys are stashed here so a future field
* doesn't drop on parse — the JSON round-trips intact. */
@Builder.Default
private Map extras = Map.of();
// ==================== Nested types ====================
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public static class RequirementDef {
/** Required: stable identifier referenced by {@code features[*].requires}. */
private String key;
/** binary | env_var | api_key | endpoint */
private String type;
/**
* Probe target — for binary, the executable name; for env_var, the
* env name; for endpoint, the service address to TCP-probe
* ({@code http(s)://host[:port][/path]} or {@code host[:port]}).
*/
private String check;
/** Optional means it only blocks features that reference it explicitly. */
@Builder.Default
private boolean optional = false;
private String description;
@Builder.Default
private Map install = Map.of();
}
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public static class FeatureDef {
private String id;
private String label;
@Builder.Default
private List requires = List.of();
@Builder.Default
private List platforms = List.of();
/** Tools advertised only when this feature is READY. Empty
* means "inherit the manifest-level allowed-tools as-is". */
@Builder.Default
private List tools = List.of();
private String fallbackMessage;
private String unsupportedMessage;
}
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public static class SettingDef {
private String key;
private String label;
/** select | text | secret | toggle */
private String type;
private Object defaultValue;
@Builder.Default
private List