# =============================================================================
# config/skill-policy.yaml
# -----------------------------------------------------------------------------
# The skill dedup / overlap decision policy for the KarnelLabs Knowledge
# Warehouse. This is the rulebook that turns a freshly-discovered skill (from
# Scout / signalcraft / deep-research) into a registry decision.
#
# WHO REFERENCES IT:
#   - 40_Skills/registry.yaml  -> `policy: config/skill-policy.yaml`
#   - config/scout.yaml        -> judgment.applies_skill_policy
#   - config/publish.yaml      -> sanitize.bot_name_denylist_source (denylist)
#
# CORE IDEA:
#   When a candidate skill appears, walk the `evaluation_questions` in order.
#   The answers point to one of `outcomes`. The outcome maps to a registry
#   `status` + `action`. NOTHING here auto-promotes a skill to `approved` and
#   NOTHING here installs a plugin/MCP/package -- those are in approval_required
#   and need an explicit human yes (mirrors the global policy).
#
# YAML SUBSET: 2-space indent, block maps/seqs, plain/quoted scalars, empty
# collections only as inline [] / {}. No anchors/flow maps/folding.
# =============================================================================

version: 1
updated: 2026-06-23

# -----------------------------------------------------------------------------
# evaluation_questions
#   The 8 questions, asked in order, when judging a candidate skill against
#   what we already have. Each carries the consequence of a YES answer so the
#   reviewer (human or Hermes) can route to an outcome deterministically.
# -----------------------------------------------------------------------------
evaluation_questions:
  - id: q1_same_engine
    question: 'Does it target the same engine as an existing skill? (unity/unreal/any)'
    if_yes: 'Engine overlap -> keep comparing; raises absorb/replace likelihood.'
    if_no: 'Different engine -> likely additive; lean absorb-or-watch.'
  - id: q2_same_domain
    question: 'Is it in the same domain/category as an existing skill? (ui/game/mcp/...)'
    if_yes: 'Domain overlap -> compare task scope next (q3).'
    if_no: 'New domain -> additive; lean absorb-or-watch.'
  - id: q3_same_task
    question: 'Does it solve the same concrete task as an approved skill?'
    if_yes: 'Direct competitor -> evaluate replace/absorb (q5,q6,q7).'
    if_no: 'Adjacent task -> may still partially overlap (q7).'
  - id: q4_overlaps_approved
    question: 'Does it overlap an already-APPROVED skill specifically?'
    if_yes: 'High bar to add a second; default watch unless clearly better.'
    if_no: 'No approved overlap -> absorb-or-watch is on the table.'
  - id: q5_more_specific
    question: 'Is it more specific / specialized than what we have?'
    if_yes: 'Specialization can justify absorb-or-watch alongside the general one.'
    if_no: 'More general or equal -> weak case to add; watch or reject.'
  - id: q6_good_enough_to_replace
    question: 'Is it good enough to fully REPLACE an existing approved skill?'
    if_yes: 'If it truly beats my skill, ABSORB its approach and re-author mine; deprecate the old (human decides).'
    if_no: 'Not a replacement -> absorb or track instead.'
  - id: q7_partial_absorb
    question: 'Is only PART of it useful (a technique/snippet we can fold in)?'
    if_yes: 'absorb -> fold only the useful feature into one of my authored skills.'
    if_no: 'Whole-or-nothing -> absorb-or-watch or reject.'
  - id: q8_install_risk
    question: 'What is the install/run risk? (license, supply-chain, perms, maintenance)'
    if_yes: 'Material risk -> do NOT install; watch or reject pending review.'
    if_no: 'Low risk -> eligible to proceed to absorb-or-watch (still needs approval).'

# -----------------------------------------------------------------------------
# outcomes
#   An external reference skill resolves to exactly ONE of THREE decisions. The
#   registry is a wiki: nothing external is ever "promoted to approved". `approved`
#   is reserved for skills a human AUTHORS (synthesized from the wiki). So the only
#   moves for a `watch` skill are: keep watching, absorb its feature, or reject.
# -----------------------------------------------------------------------------
outcomes:
  - id: watch
    when: 'Worth tracking but not yet absorbed: promising/overlapping/unproven/risky.'
    maps_to_status: watch
    default_action: watch
    note: 'No use, no install. Stays in the reference wiki; revisit on later sweeps.'
  - id: absorb
    when: 'It has a useful FEATURE worth folding into one of my authored skills.'
    maps_to_status: absorbed
    default_action: 'absorb_into:<my-skill-id>'
    note: 'Extract only the useful feature into my skill (author/improve it); record provenance + license. Do not keep the external skill whole.'
  - id: reject
    when: 'Redundant, low-quality, abandoned, or unacceptable risk/license.'
    maps_to_status: rejected
    default_action: reject
    note: 'Record the reason so we do not re-evaluate it every sweep.'

# -----------------------------------------------------------------------------
# status_definitions
#   The 6 registry statuses (matches the registry schema enum). This is the
#   single source of truth for what each status MEANS.
# -----------------------------------------------------------------------------
status_definitions:
  - status: approved
    meaning: 'A skill I AUTHORED (synthesized from the wiki). Usable/installable; reachable by kickoff SkillLock. Never an external skill adopted as-is.'
  - status: candidate
    meaning: 'An internally AUTHORED skill draft (e.g. a KLV blueprint atom), awaiting human approval. NOT usable and NOT in any project context until a human promotes it candidate -> approved. Distinct from watch, which is external.'
  - status: watch
    meaning: 'External reference, tracked. Studied, not used or installed. Re-check on future sweeps. (merges old watch_only)'
  - status: absorbed
    meaning: 'Its useful feature was folded into one of my authored skills; the external skill is not kept whole.'
  - status: rejected
    meaning: 'Evaluated and declined. Kept on record (with reason) to prevent re-churn.'
  - status: deprecated
    meaning: 'A former approved skill, now superseded or retired. Avoid for new work.'

# -----------------------------------------------------------------------------
# approval_required
#   Actions that may NEVER happen automatically. Each requires an explicit human
#   yes. This is the teeth behind "never auto-promote / auto-install / auto-publish".
# -----------------------------------------------------------------------------
approval_required:
  - action: approve_skill
    meaning: 'Promote any skill to status: approved.'
    who: human
  - action: install_plugin_mcp_or_package
    meaning: 'Install a Claude plugin, MCP server, or any package/dependency.'
    who: human
  - action: change_global_rules
    meaning: 'Edit global policy (CLAUDE.md / README / HERMES / this policy file).'
    who: human
  - action: publish
    meaning: 'Run the publish pipeline / push content to the website.'
    who: human
  - action: private_to_public
    meaning: 'Raise any note visibility from private/internal toward public.'
    who: human

# -----------------------------------------------------------------------------
# bot_name_denylist
#   Internal automation / agent names that must NEVER leak into published output.
#   config/publish.yaml -> sanitize reads THIS list. Matching is case-insensitive
#   whole-word; the sanitize pass redacts or fails the run on a hit.
# -----------------------------------------------------------------------------
# Entries MUST be specific internal identifiers, NOT common English words.
# The sanitizer matches these case-insensitively on word boundaries, so a
# common word (e.g. 'Scout') would block legitimate public prose. Use only
# unambiguous bot/automation tokens (e.g. Hermes, karnel-bot, KarnelLabs-*).
bot_name_denylist:
  - Hermes
  - KarnelLabs-internal
  - karnel-bot

# -----------------------------------------------------------------------------
# defaults
#   Conservative fallbacks when the questions do not cleanly resolve.
# -----------------------------------------------------------------------------
defaults:
  unresolved_outcome: watch   # when in doubt, track -- never auto-approve/install
  new_skill_status: candidate      # anything freshly discovered starts as candidate
  require_reason_on_reject: true   # rejections must record why (anti re-churn)
