Skip to content

Feature Registry

Overview

The Feature Registry is a centralized configuration that catalogs features across the project ecosystem. It is defined in features/registry.yaml and serves as the source of truth for feature metadata, including associated repositories, code paths, and documentation paths.

The registry is currently used across the following repositories:

  • knowledge-sanctuary — houses the registry definition itself (features/registry.yaml)
  • minh — contains services that programmatically interact with and update the registry

Architecture

The Feature Registry operates as a YAML-based configuration file with supporting automation:

Component Location Purpose
Registry definition knowledge-sanctuary/features/registry.yaml Canonical feature catalog
Registry updater service minh/src/services/feature_registry_updater.py LLM-powered proposals for unmatched MRs
Merge monitor service minh/src/services/merge_monitor_service.py Detects stale docs and triggers registry proposals
Updater tests minh/tests/test_feature_registry_updater.py Validation of updater behavior
flowchart LR
    MM[Merge Monitor] -->|unmatched MRs| FRU[FeatureRegistryUpdater]
    FRU -->|current YAML| GL_READ[GitLab API read]
    FRU -->|prompt| LLM[Claude LLM]
    LLM -->|updated YAML| FRU
    FRU -->|commit + MR| SGW[SafeGitLabWriter]
    SGW -->|branch + MR| GL_WRITE[GitLab API write]
    FRU -->|throttle check| REDIS[Redis]

Registry Entry Structure

Each feature entry in features/registry.yaml uses a list format with the following fields:

features:
  - name: Musical Cantrip System
    description: In-game spell-casting through musical performance
    status: active            # active | planned | deprecated
    feature_type: gameplay    # code | workflow | api | config | gameplay
    repos:
      - mud-onboarding
      - knowledge-sanctuary
    code_paths:
      mud-onboarding:
        - "typeclasses/cantrips/*.py"
        - "commands/cantrip_*.py"
    doc_paths:
      - docs/game-systems/musical-cantrip-system.md
      - docs/game-systems/cantrip-reference.md
    last_updated: 2026-02-25

Key Fields

Field Required Description
name Yes Human-readable feature name (unique key)
description Yes Brief description of the feature
status No active, planned, or deprecated
feature_type No code, workflow, api, config, or gameplay
repos Yes List of repository names where this feature lives
code_paths Yes Repo → list of file globs where the feature is implemented
doc_paths Yes Paths in knowledge-sanctuary that document this feature
last_updated No ISO date of most recent modification

Registry Header Comments

The registry file supports a YAML header comment block at the top of the file. This header documents the schema and purpose of the registry. A production example:

# Feature Registry — Cross-Repo Feature Topology
#
# Defines features that span multiple repositories.
# Minh reads this file to populate Neo4j and detect staleness.
#
# Schema:
#   name:         Human-readable feature name (unique key)
#   description:  Brief description
#   status:       active | planned | deprecated
#   feature_type: code | workflow | api | config
#   repos:        List of repository names where this feature lives
#   code_paths:   Repo → list of file globs where the feature is implemented
#   doc_paths:    Paths in knowledge-sanctuary that document this feature

features:
  ...

The updater service automatically preserves this header block when committing changes (see Header Preservation below).

How Matching Works

When a merge request is merged to main or master, the Merge Monitor compares each changed file against the code_paths globs in the registry using Python's fnmatch:

for changed_file in changed_files:
    if fnmatch.fnmatch(changed_file, glob_pattern):
        # Feature docs are marked stale

For example, when typeclasses/cantrips/fire_cantrip.py changes in mud-onboarding, fnmatch matches it against typeclasses/cantrips/*.py, and all doc_paths for that feature are marked stale.

If a merged MR matches zero features, it is classified as "unmatched" and may trigger an automatic registry update proposal (see below).

Feature Registry Updater

The FeatureRegistryUpdater class (src/services/feature_registry_updater.py) proposes registry.yaml updates when the Merge Monitor detects MRs that don't match any tracked feature. It uses an LLM to analyze unmatched file paths and suggest code_paths additions or new feature entries.

Constructor

from services.feature_registry_updater import FeatureRegistryUpdater

updater = FeatureRegistryUpdater(
    gitlab_client=gitlab_client,           # Required: GitLab API client
    safe_gitlab_writer=safe_gitlab_writer, # Required: SafeGitLabWriter for branch/MR creation
    llm_manager=llm_manager,               # Required: LLM client for generating proposals
    slack_client=slack_client,             # Optional: Slack client for notifications
    alert_channel_id=alert_channel_id,     # Optional: Slack channel for alerts
    redis_client=redis_client,             # Optional: Redis client for throttling
)

Main Method: propose_registry_update

result = await updater.propose_registry_update(unmatched_mrs)
# Returns: {"success": True, "mr_url": "...", "unmatched_count": N}
# Or:      {"success": False, "error": "reason"}

Each MR dict must include iid, title, web_url, project_path, and changed_files (list of strings).

Safety Guards

The updater implements several safety mechanisms to prevent bad proposals:

Guard Behavior
Redis throttle Duplicate proposals (same fingerprint) are blocked for 24 hours
Open MR dedup Skips if a Minh-authored registry MR is already open
Stale MR auto-close Open registry MRs older than 7 days are automatically closed so fresh proposals can proceed
Feature count guard Rejects LLM output that drops existing features
YAML validation Rejects invalid YAML or output missing the features key
MR cap At most 10 unmatched MRs are sent to the LLM per proposal
File cap At most 30 changed files per MR are included in the prompt

Header Preservation

When the updater service commits changes to the registry, it preserves the original header comment block from the existing file. The updater handles several edge cases:

  • LLM echoes the header — If the LLM response includes a copy of the original header comments, the updater strips the echoed header before prepending the original. This prevents duplicate header blocks in the committed output.
  • LLM adds its own comments — If the LLM introduces unrelated leading comments (e.g., # Updated registry), these are detected as a header block and replaced with the original header.
  • No header present — If the existing registry file has no header comments, the updater commits the LLM output as-is without prepending anything.

Trailing Newline Normalization

The updater ensures that committed content always ends with exactly one trailing newline character. This maintains consistent file formatting and avoids no-newline-at-end-of-file warnings.

Throttle Fingerprinting

The throttle fingerprint is a SHA-256 hash of the target file path plus the sorted repo names and top-5 files from each MR. This ensures the same set of unmatched MRs won't generate duplicate proposals within the 24-hour TTL window.

Integration with Merge Monitor

The MergeMonitorService triggers registry update proposals automatically when MINH_AUTO_FEATURE_REGISTRATION_ENABLED is set to true. The flow:

  1. Merge Monitor polls for recently-merged MRs (every 30 minutes)
  2. Each MR's changed files are matched against tracked features
  3. MRs that match zero features are collected as "unmatched"
  4. If unmatched MRs exist and auto-registration is enabled, propose_registry_update() is called
  5. A Slack notification is posted with the proposal MR link and list of unmatched MRs

Commit Message Format

Registry update MRs use the following commit message format:

feat(registry): propose code_paths for N unmatched MR(s) (!iid1, !iid2, ...)

If more than 5 MRs are included, the message shows the first 5 with a (+N more) suffix.

Configuration

Environment Variables

Variable Default Purpose
MINH_AUTO_FEATURE_REGISTRATION_ENABLED false Enable automatic registry update proposals for unmatched MRs
MINH_DEFAULT_REVIEWERS (empty) Comma-separated list of GitLab usernames to assign as MR reviewers

File Location

The registry lives at:

knowledge-sanctuary/features/registry.yaml

The updater reads this path via the KNOWLEDGE_SANCTUARY_PROJECT constant, which resolves to the-smithy1/agents/knowledge-sanctuary from the repo registry.

Prerequisites

  • Access to the knowledge-sanctuary repository for reading or editing the registry directly
  • Access to the minh repository for programmatic registry updates
  • A configured GitLab API client with read/write access to knowledge-sanctuary
  • A configured LLM manager (Claude) for generating proposals
  • Optional: Redis for throttling duplicate proposals

Error Cases and Failure Modes

Scenario Behavior
No unmatched MRs Returns {"success": false, "error": "No unmatched MRs provided"}
Throttled Returns {"success": false, "error": "Throttled — duplicate proposal"}
Open MR exists Returns {"success": false, "error": "Open registry MR already exists: !N"}
Registry fetch fails Returns {"success": false, "error": "Could not fetch registry.yaml"}
LLM error Returns {"success": false, "error": "LLM error: ..."}
Invalid YAML from LLM Returns {"success": false, "error": "LLM returned invalid YAML"}
Missing features key Returns {"success": false, "error": "LLM output missing 'features' key"}
LLM drops features Returns {"success": false, "error": "LLM dropped features (N -> M)"}
GitLab write error Returns {"success": false, "error": "..."} with the exception message

Audit Trail

Changes to the Feature Registry are tracked via version control in the knowledge-sanctuary repo. The last_updated field on each entry provides a human-readable timestamp of the most recent modification. All registry update MRs created by Minh follow the branch naming convention minh-batch-YYYYMMDD-HHMMSS and require human review before merging.