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:
- Merge Monitor polls for recently-merged MRs (every 30 minutes)
- Each MR's changed files are matched against tracked features
- MRs that match zero features are collected as "unmatched"
- If unmatched MRs exist and auto-registration is enabled,
propose_registry_update()is called - 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:
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:
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-sanctuaryrepository for reading or editing the registry directly - Access to the
minhrepository 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.