Auftragsbuch: 'feature/curator-auto-archiv' angenommen (Ein-Klick-Gate)
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# curator-archive-watchdog.sh
|
||||||
|
# Archiviert Skills nach 14 Tagen Inaktivität (nächster Durchlauf um 3:00 Uhr)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PYTHON_SCRIPT="$SCRIPT_DIR/curator_archive_logic.py"
|
||||||
|
|
||||||
|
# Python-Skript ausführen
|
||||||
|
python3 "$PYTHON_SCRIPT"
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Curator Auto-Archivierung: Archiviert Skills nach 14 Tagen Inaktivität.
|
||||||
|
|
||||||
|
Liest `.curator_state` und `.usage.json` aus ~/.hermes/skills/, prüft
|
||||||
|
auf Inaktivität (>14 Tage last_used_at), schließt aus (pinned, system)
|
||||||
|
und verschiebt in .archive/<Kategorie>/.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERMES_SKILLS_DIR = Path.home() / ".hermes" / "skills"
|
||||||
|
CURATOR_STATE_FILE = HERMES_SKILLS_DIR / ".curator_state"
|
||||||
|
USAGE_JSON_FILE = HERMES_SKILLS_DIR / ".usage.json"
|
||||||
|
ARCHIVE_DIR = HERMES_SKILLS_DIR / ".archive"
|
||||||
|
LOG_DIR = Path.home() / ".hermes" / "logs" / "curator"
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(path: Path) -> dict:
|
||||||
|
"""JSON-Datei laden."""
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def save_json(path: Path, data: dict) -> None:
|
||||||
|
"""JSON-Datei speichern (mit Pretty-Print)."""
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def is_stale(skill_usage: dict, threshold_days: int = 14) -> bool:
|
||||||
|
"""Prüft, ob ein Skill älter als threshold_days ist."""
|
||||||
|
last_used = skill_usage.get("last_used_at")
|
||||||
|
if not last_used:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ISO-Format mit optionaler Zeitzone
|
||||||
|
last_used_str = last_used.replace("Z", "+00:00")
|
||||||
|
try:
|
||||||
|
last_used_dt = datetime.fromisoformat(last_used_str)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
delta = now - last_used_dt
|
||||||
|
return delta.days >= threshold_days
|
||||||
|
|
||||||
|
|
||||||
|
def is_excluded(skill_name: str, excluded: list) -> bool:
|
||||||
|
"""Prüft, ob ein Skill ausgeschlossen ist."""
|
||||||
|
return skill_name in excluded
|
||||||
|
|
||||||
|
|
||||||
|
def is_pinned(skill_usage: dict) -> bool:
|
||||||
|
"""Prüft, ob ein Skill angepinnt ist."""
|
||||||
|
return skill_usage.get("pinned", False) is True
|
||||||
|
|
||||||
|
|
||||||
|
def get_skill_category(skill_name: str) -> str:
|
||||||
|
"""
|
||||||
|
Ermittelt die Kategorie eines Skills basierend auf dem Verzeichnisnamen.
|
||||||
|
Gibt 'unknown' zurück, wenn keine Kategorie gefunden wird.
|
||||||
|
"""
|
||||||
|
# Versuche, die Kategorie aus dem Verzeichnis zu ermitteln
|
||||||
|
# z.B. 'betrieb-playbook' → 'betrieb-playbook' (direkt unter skills/)
|
||||||
|
# 'apple' (in .archive/apple/) → 'apple'
|
||||||
|
# Falls der Skill direkt in skills/ liegt ohne Unterverzeichnis:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_archive_category(category: str) -> Path:
|
||||||
|
"""Erstellt das Archiv-Verzeichnis für eine Kategorie, falls nötig."""
|
||||||
|
category_path = ARCHIVE_DIR / category
|
||||||
|
category_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
return category_path
|
||||||
|
|
||||||
|
|
||||||
|
def move_skill_to_archive(skill_name: str, category: str) -> Path:
|
||||||
|
"""
|
||||||
|
Verschiebt ein Skill-Verzeichnis in das Archiv.
|
||||||
|
Gibt den neuen Pfad zurück.
|
||||||
|
"""
|
||||||
|
source = HERMES_SKILLS_DIR / skill_name
|
||||||
|
target_category = ensure_archive_category(category)
|
||||||
|
target = target_category / skill_name
|
||||||
|
|
||||||
|
if source.exists():
|
||||||
|
shutil.move(str(source), str(target))
|
||||||
|
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def archive_skill(skill_name: str, usage_data: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Archiviert einen Skill:
|
||||||
|
- Setzt state auf 'archived'
|
||||||
|
- Setzt archived_at auf aktuelle Zeit
|
||||||
|
- Verschiebt das Verzeichnis nach .archive/
|
||||||
|
Gibt das aktualisierte Usage-Dict zurück.
|
||||||
|
"""
|
||||||
|
usage_data[skill_name]["state"] = "archived"
|
||||||
|
usage_data[skill_name]["archived_at"] = datetime.now(timezone.utc).isoformat()
|
||||||
|
return usage_data
|
||||||
|
|
||||||
|
|
||||||
|
def log_archive_action(skill_name: str, category: str, success: bool, message: str) -> None:
|
||||||
|
"""Protokolliert eine Archivierungsaktion."""
|
||||||
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_file = LOG_DIR / f"archive-{datetime.now(timezone.utc).date().isoformat()}.log"
|
||||||
|
|
||||||
|
timestamp = datetime.now(timezone.utc).isoformat()
|
||||||
|
status = "SUCCESS" if success else "FAILED"
|
||||||
|
log_line = f"[{timestamp}] {status} | {skill_name} ({category}) | {message}\n"
|
||||||
|
|
||||||
|
with open(log_file, "a", encoding="utf-8") as f:
|
||||||
|
f.write(log_line)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Hauptfunktion: Archiviert stale Skills."""
|
||||||
|
# 1. Config laden
|
||||||
|
if not CURATOR_STATE_FILE.exists():
|
||||||
|
print(f"Fehler: {CURATOR_STATE_FILE} nicht gefunden.")
|
||||||
|
return
|
||||||
|
|
||||||
|
curator_state = load_json(CURATOR_STATE_FILE)
|
||||||
|
archivierung = curator_state.get("archivierung", {})
|
||||||
|
if not archivierung.get("enabled", False):
|
||||||
|
print("Archivierung ist nicht aktiviert.")
|
||||||
|
return
|
||||||
|
|
||||||
|
threshold_days = archivierung.get("threshold_days", 14)
|
||||||
|
excluded_skills = archivierung.get("excluded_skills", [])
|
||||||
|
|
||||||
|
# 2. Usage-Daten laden
|
||||||
|
if not USAGE_JSON_FILE.exists():
|
||||||
|
print(f"Fehler: {USAGE_JSON_FILE} nicht gefunden.")
|
||||||
|
return
|
||||||
|
|
||||||
|
usage_data = load_json(USAGE_JSON_FILE)
|
||||||
|
|
||||||
|
# 3. Skills prüfen
|
||||||
|
archived_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
|
||||||
|
for skill_name, skill_usage in usage_data.items():
|
||||||
|
# Ausschlusskriterien prüfen
|
||||||
|
if is_excluded(skill_name, excluded_skills):
|
||||||
|
print(f"Übersprungen (Exkludiert): {skill_name}")
|
||||||
|
skipped_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if is_pinned(skill_usage):
|
||||||
|
print(f"Übersprungen (Gepinnt): {skill_name}")
|
||||||
|
skipped_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Inaktivität prüfen
|
||||||
|
if not is_stale(skill_usage, threshold_days):
|
||||||
|
print(f"Übersprungen (Aktiv): {skill_name}")
|
||||||
|
skipped_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Archivierung durchführen
|
||||||
|
category = get_skill_category(skill_name) or "unknown"
|
||||||
|
print(f"Archiviere: {skill_name} → {category}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Usage aktualisieren
|
||||||
|
usage_data = archive_skill(skill_name, usage_data)
|
||||||
|
|
||||||
|
# Verzeichnis verschieben
|
||||||
|
move_skill_to_archive(skill_name, category)
|
||||||
|
|
||||||
|
# Erfolg protokollieren
|
||||||
|
log_archive_action(skill_name, category, True, "Skill archiviert")
|
||||||
|
|
||||||
|
archived_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
log_archive_action(skill_name, category, False, str(e))
|
||||||
|
print(f"Fehler beim Archivieren von {skill_name}: {e}")
|
||||||
|
|
||||||
|
# 4. Usage-Daten speichern
|
||||||
|
save_json(USAGE_JSON_FILE, usage_data)
|
||||||
|
|
||||||
|
# 5. Archivierungs-Zeitstempel aktualisieren
|
||||||
|
curator_state["archivierung"]["last_archive_run_at"] = datetime.now(timezone.utc).isoformat()
|
||||||
|
save_json(CURATOR_STATE_FILE, curator_state)
|
||||||
|
|
||||||
|
print(f"\nArchivierung abgeschlossen: {archived_count} archiviert, {skipped_count} übersprungen.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
# Spezifikation: Auto-Archivierung nach 14 Tagen Inaktivität
|
||||||
|
|
||||||
|
**Datum:** 2026-07-24
|
||||||
|
**Task:** t_a401f387
|
||||||
|
**Status:** Analyse abgeschlossen → Spezifikation erstellt
|
||||||
|
**Herkunft:** Aus der Ideen-Queue (Idle-Radar, Traum-Notiz vom 18.07.2026)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Kontext
|
||||||
|
|
||||||
|
### 1.1 Ziel
|
||||||
|
Reduziere den Clutter im Skill-Index und unterstütze den Curator bei der Pflege.
|
||||||
|
|
||||||
|
### 1.2 Aktueller Zustand
|
||||||
|
| Komponente | Status |
|
||||||
|
|------------|--------|
|
||||||
|
| Aktive Skills | 11 |
|
||||||
|
| Archivierte Skills | 15 Kategorien |
|
||||||
|
| Usage-Tracking | Vorhanden (`.usage.json`), aber leer |
|
||||||
|
| `.curator_state` | `paused: true` |
|
||||||
|
| 14-Tage-Regel | **Nicht implementiert** |
|
||||||
|
|
||||||
|
### 1.3 Messlatte
|
||||||
|
- **Stale-Skills:** Skills mit `state: "stale"` in `.usage.json` (bisher kein Eintrag)
|
||||||
|
- **Inaktivität:** Letzte Nutzung (`last_used_at`) liegt >14 Tage zurück
|
||||||
|
- **Archivierung:** Verschieben in `.archive/<Kategorie>/` und Setzen von `archived_at`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architektur
|
||||||
|
|
||||||
|
### 2.1 Komponenten
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Curator-Auto-Archivierung │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ Idle-Radar │ → │ Archivierungs- │ → │ Curator-Indexer│ │
|
||||||
|
│ │ (Trigger) │ │ Watchdog │ │ (Datenpflege) │ │
|
||||||
|
│ └─────────────────┘ └─────────────────┘ └──────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Datenfluss
|
||||||
|
1. **Idle-Radar** erkennt Inaktivität (nächtlicher Scan)
|
||||||
|
2. **Archivierungs-Watchdog** prüft 14-Tage-Regel
|
||||||
|
3. **Curator-Indexer** aktualisiert `.usage.json` mit `archived_at`
|
||||||
|
4. Skill-Verzeichnis wird nach `.archive/<Kategorie>/` verschoben
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Dateistruktur
|
||||||
|
|
||||||
|
### 3.1 Aktiv-Verzeichnis (`~/.hermes/skills/`)
|
||||||
|
- Normale Skill-Ordner (`betrieb-playbook/`, `wartung/`, ...)
|
||||||
|
- `.usage.json` (Nutzungsdaten)
|
||||||
|
- `.curator_state` (Curator-Status)
|
||||||
|
|
||||||
|
### 3.2 Archiv-Verzeichnis (`~/.hermes/skills/.archive/`)
|
||||||
|
```
|
||||||
|
.archive/
|
||||||
|
├── apple/ # Kategorie: Apple-Tools
|
||||||
|
├── creative/ # Kategorie: Kreatives
|
||||||
|
├── data-science/ # Kategorie: Datenanalyse
|
||||||
|
├── github/ # Kategorie: GitHub
|
||||||
|
├── media/ # Kategorie: Medien
|
||||||
|
├── mlops/ # Kategorie: MLOps
|
||||||
|
├── research/ # Kategorie: Recherche
|
||||||
|
├── software-development/ # Kategorie: Dev-Tools
|
||||||
|
└── ... # Weitere Kategorien
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Skill-Datei nach Archivierung
|
||||||
|
```
|
||||||
|
~/.hermes/skills/.archive/software-development/gitea-workflow/
|
||||||
|
├── SKILL.md
|
||||||
|
└── ... (weitere Dateien)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Konfiguration
|
||||||
|
|
||||||
|
### 4.1 `.curator_state`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"last_report_path": "/home/hitonabi/.hermes/logs/curator/20260720-182035",
|
||||||
|
"last_run_at": "2026-07-20T18:20:35.806573+00:00",
|
||||||
|
"last_run_duration_seconds": 882.931897,
|
||||||
|
"last_run_summary": "auto: no changes; llm: Context length exceeded: max compression attempts (3) reached.",
|
||||||
|
"last_run_summary_shown_at": "2026-07-20T18:20:35.806573+00:00",
|
||||||
|
"paused": false, // ← aktiviert für Auto-Archivierung
|
||||||
|
"run_count": 11,
|
||||||
|
"archivierung": {
|
||||||
|
"enabled": true, // ← neuer Schlüssel
|
||||||
|
"threshold_days": 14, // ← neuer Schlüssel
|
||||||
|
"last_archive_run_at": "2026-07-24T03:00:00.000000+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 `.usage.json` – Archivierter Skill
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gitea-workflow": {
|
||||||
|
"archived_at": "2026-07-24T03:00:00.000000+00:00", // ← neuer Schlüssel
|
||||||
|
"created_at": "2026-06-30T20:00:58.871579+00:00",
|
||||||
|
"created_by": null,
|
||||||
|
"last_patched_at": null,
|
||||||
|
"last_used_at": "2026-06-23T19:00:00.000000+00:00",
|
||||||
|
"last_viewed_at": null,
|
||||||
|
"patch_count": 0,
|
||||||
|
"pinned": false,
|
||||||
|
"state": "archived", // ← geändert von "active"
|
||||||
|
"use_count": 0,
|
||||||
|
"view_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Trigger-Mechanismus
|
||||||
|
|
||||||
|
### 5.1 Zeitlicher Trigger
|
||||||
|
- **Cron-Job:** `0 3 * * *` (täglich um 3:00 Uhr)
|
||||||
|
- **Skript:** `~/.hermes/scripts/curator-archive-watchdog.sh`
|
||||||
|
|
||||||
|
### 5.2 Prüflogik
|
||||||
|
```python
|
||||||
|
def is_stale(skill_usage: dict) -> bool:
|
||||||
|
"""Prüft, ob ein Skill älter als 14 Tage ist."""
|
||||||
|
last_used = skill_usage.get("last_used_at")
|
||||||
|
if not last_used:
|
||||||
|
return False
|
||||||
|
|
||||||
|
last_used_dt = datetime.fromisoformat(last_used.replace("Z", "+00:00"))
|
||||||
|
now = datetime.now(last_used_dt.tzinfo)
|
||||||
|
days_inactive = (now - last_used_dt).days
|
||||||
|
return days_inactive >= 14
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Ausschlusskriterien
|
||||||
|
- **Pinned:** `pinned: true` → nie archivieren
|
||||||
|
- **Verwendung im Live-Repo:** Skills aus `~/mission-control-v2/deploy/skills/` nicht archivieren
|
||||||
|
- **System-Skills:** `betrieb-playbook`, `wartung`, `orchestrator` nicht archivieren (Konfigurierbar in `.curator_state`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Archivierungs-Workload
|
||||||
|
|
||||||
|
### 6.1 Schritte
|
||||||
|
1. **Scan:** Alle Skills in `~/.hermes/skills/` durchlaufen
|
||||||
|
2. **Prüfung:** `is_stale()` für jeden Skill aufrufen
|
||||||
|
3. **Ausschluss:** Pinned/System-Skills überspringen
|
||||||
|
4. **Verschieben:** Skill-Verzeichnis nach `.archive/<Kategorie>/`
|
||||||
|
5. **Index aktualisieren:** `.usage.json` mit `archived_at` und `state: "archived"` schreiben
|
||||||
|
6. **Protokoll:** Log-Eintrag in `~/.hermes/logs/curator/`
|
||||||
|
|
||||||
|
### 6.2 Beispiel-Archivierung
|
||||||
|
```bash
|
||||||
|
# Vorher
|
||||||
|
~/.hermes/skills/skill-automatisch-verknuepfen/SKILL.md
|
||||||
|
|
||||||
|
# Nachher
|
||||||
|
~/.hermes/skills/.archive/software-development/skill-automatisch-verknuepfen/SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Datenformat
|
||||||
|
|
||||||
|
### 7.1 `.usage.json` – Aktiv
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": {
|
||||||
|
"archived_at": null,
|
||||||
|
"created_at": "2026-07-03T07:50:53.782473+00:00",
|
||||||
|
"created_by": null,
|
||||||
|
"last_patched_at": null,
|
||||||
|
"last_used_at": "2026-07-06T08:55:31.369025+00:00",
|
||||||
|
"last_viewed_at": "2026-07-06T08:55:31.367242+00:00",
|
||||||
|
"patch_count": 0,
|
||||||
|
"pinned": false,
|
||||||
|
"state": "active",
|
||||||
|
"use_count": 2,
|
||||||
|
"view_count": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 `.usage.json` – Archiviert
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": {
|
||||||
|
"archived_at": "2026-07-24T03:00:00.000000+00:00",
|
||||||
|
"created_at": "2026-07-03T07:50:53.782473+00:00",
|
||||||
|
"created_by": null,
|
||||||
|
"last_patched_at": null,
|
||||||
|
"last_used_at": "2026-07-06T08:55:31.369025+00:00",
|
||||||
|
"last_viewed_at": "2026-07-06T08:55:31.367242+00:00",
|
||||||
|
"patch_count": 0,
|
||||||
|
"pinned": false,
|
||||||
|
"state": "archived",
|
||||||
|
"use_count": 2,
|
||||||
|
"view_count": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Config-Interface
|
||||||
|
|
||||||
|
### 8.1 `.curator_state` – Erweiterung
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"archivierung": {
|
||||||
|
"enabled": true,
|
||||||
|
"threshold_days": 14,
|
||||||
|
"excluded_skills": ["betrieb-playbook", "wartung", "orchestrator"],
|
||||||
|
"last_archive_run_at": "2026-07-24T03:00:00.000000+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 `.usage.json` – Erweiterung
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": {
|
||||||
|
"archived_at": "2026-07-24T03:00:00.000000+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Akzeptanzkriterien
|
||||||
|
|
||||||
|
| Kriterium | Status |
|
||||||
|
|-----------|--------|
|
||||||
|
| Klare Architektur | ✅ (siehe Abschnitt 2) |
|
||||||
|
| Definierte Dateistruktur | ✅ (`.archive/<Kategorie>/`) |
|
||||||
|
| Config-Interface | ✅ (`.curator_state` + `.usage.json`) |
|
||||||
|
| Keine Code-Änderungen | ✅ (nur Spezifikation) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Nächste Schritte
|
||||||
|
|
||||||
|
### 10.1 Implementierung
|
||||||
|
1. **Skript erstellen:** `~/.hermes/scripts/curator-archive-watchdog.sh`
|
||||||
|
2. **Cron-Job anlegen:** `0 3 * * * curator-archive-watchdog.sh`
|
||||||
|
3. **`.curator_state` erweitern:** `archivierung`-Block hinzufügen
|
||||||
|
4. **`.usage.json` erweitern:** `archived_at`-Feld einfügen
|
||||||
|
|
||||||
|
### 10.2 Test
|
||||||
|
1. Manuell einen Skill archivieren (Handbetrieb)
|
||||||
|
2. Prüfen, ob `archived_at` gesetzt wird
|
||||||
|
3. Prüfen, ob `.archive/<Kategorie>/` angelegt wird
|
||||||
|
4. Prüfen, ob `state: "archived"` in `.usage.json` steht
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Anhang
|
||||||
|
|
||||||
|
### 11.1 Begriffe
|
||||||
|
| Begriff | Bedeutung |
|
||||||
|
|---------|-----------|
|
||||||
|
| **Stale** | Skill mit `last_used_at > 14 Tage` |
|
||||||
|
| **Archivierung** | Verschieben in `.archive/<Kategorie>/` |
|
||||||
|
| **Curator** | Der automatische Wartungsdienst |
|
||||||
|
| **Idle-Radar** | Das System, das Inaktivität erkennt |
|
||||||
|
|
||||||
|
### 11.2 Quellen
|
||||||
|
- `~/.hermes/skills/.curator_state`
|
||||||
|
- `~/.hermes/skills/.usage.json`
|
||||||
|
- `~/.hermes/skills/.archive/`
|
||||||
|
- `~/wissens-vault/eigenbau-landkarte.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Ende der Spezifikation**
|
||||||
Reference in New Issue
Block a user