Lucy kennt sich: Selbst-Inventur, Karten-Gutachter, Lern-Klick, Radar->Queue

User-Auftrag 12.07. ("bau das alles ein im Sinne der Autonomie. Auch muss
Lucy selbst bemerken wenn sich etwas aendert"):

- deploy/selbst-inventur.sh (Cron 03:35): SELBST-STECKBRIEF wird LIVE aus
  der Realitaet GENERIERT (Versionen, Dienste, Timer, Modelle, Crons,
  Queue, Karten -> ~/.hermes/state/selbst-steckbrief.md) statt von Hand
  gepflegt. Diff gegen gestern => die Box BEMERKT SELBST Aenderungen an
  sich (stille Chronik-Karte + kurze Telegram-Zeile; sonst still).
- deploy/karten-gutachter.sh (Cron 04:00): stempelt jede neue Karte mit
  "Empfehlung: ANNEHMEN/ABLEHNEN/UNKLAR + ein ehrlicher Satz" (prueft
  Redundanz gegen den Steckbrief, Risiko, Nutzen; Ein-Schuss-Richter
  gpt-oss/GLM; Stempel = Meinung, kein Gate; neuer Commit entwertet den
  alten Stempel). Backend mischt den Stempel in /api/auftragsbuch,
  UI zeigt ihn farbig auf der Karte.
- Ablehnen mit Grund (Lern-Klick): UI-Inline-Feld beim Ablehnen ->
  /srv/models/mc2-ablehnungen.jsonl -> Idle-Radar bekommt die Gruende
  als "NIE wieder vorschlagen"-Material vorgelegt.
- idle-radar-feed.sh: Material + Raster erweitert um Selbst-Steckbrief,
  Inventur-Diff und Ablehn-Gruende (Fehlerfall 12.07.: redundanter
  Gateway-Restart-Vorschlag waere damit gestorben).
- werkstatt-SOUL Regel 7 + wartung-Skill: REALITAETS-CHECK - existiert es
  schon? Dann kanban_block statt Branch (ein Branch fuer Ueberfluessiges
  ist ein gescheiterter Lauf).
- hermes-release-radar-feed.sh: echter Treffer legt zusaetzlich EINE rohe
  Idee in die Queue (idempotent je Release-Tag) - der Blick nach draussen
  fuettert denselben Kreislauf.
- deploy.sh: kopiert beide neuen Skripte; Doku AUFTRAGSBUCH.md +
  wissen/OFFENE-FAEDEN.md; frontend/dist frisch gebaut (tsc+vite gruen,
  UI gegen Live-Box verifiziert).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-12 19:58:00 +02:00
parent 961b003df4
commit 1570b2fc71
18 changed files with 556 additions and 75 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-ZY9WIA27.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Be3zHdDf.css">
<script type="module" crossorigin src="/assets/index-D-87Dv5s.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cq3xDDcK.css">
</head>
<body>
<div id="root"></div>
+2
View File
@@ -462,6 +462,8 @@ export interface AuftragItem {
files_truncated: boolean
frontend_ohne_build: boolean // Frontend-Quelltext ohne dist → Box kann nicht bauen
status: AuftragStatus | null
// Empfehlungs-Stempel des nächtlichen Karten-Gutachters (Meinung, kein Gate).
empfehlung: { empfehlung: "ANNEHMEN" | "ABLEHNEN" | "UNKLAR"; satz: string; richter: string } | null
}
export interface SkillKandidat {
file: string
+47 -4
View File
@@ -283,6 +283,10 @@ function ProposalCard({
const [open, setOpen] = useState(false)
const [diff, setDiff] = useState<string | null>(null)
const [diffLoading, setDiffLoading] = useState(false)
// Ablehnen mit Grund: der Grund ist das Lern-Gedächtnis des Kreislaufs (Radar/Specifier
// bekommen ihn als „nie wieder vorschlagen"-Material) — deshalb ein kleines Inline-Feld.
const [rejecting, setRejecting] = useState(false)
const [grund, setGrund] = useState("")
const running = item.status?.state === "laeuft" || item.status?.state === "rollback"
const decided = item.status?.state === "eingespielt"
// Lucy-Karten (Desktop-App): Annehmen baut am PC (dist) statt auf der Box zu deployen.
@@ -326,6 +330,16 @@ function ProposalCard({
</div>
<p className="mt-1.5 text-sm font-semibold text-foreground">{item.subject || "(ohne Titel)"}</p>
{item.body && <p className="mt-1 whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground line-clamp-6">{item.body}</p>}
{item.empfehlung && (
<p className={cn("mt-1.5 flex items-start gap-1.5 rounded-lg border px-2 py-1 text-[11px]",
item.empfehlung.empfehlung === "ANNEHMEN" && "border-emerald-500/30 bg-emerald-500/5 text-emerald-300",
item.empfehlung.empfehlung === "ABLEHNEN" && "border-red-500/30 bg-red-500/5 text-red-300",
item.empfehlung.empfehlung === "UNKLAR" && "border-amber-500/30 bg-amber-500/5 text-amber-300")}
title={`Nächtliches Kurz-Gutachten (${item.empfehlung.richter}) — eine Meinung, kein Muss. Die Entscheidung bleibt bei dir.`}>
<Sparkles className="mt-0.5 h-3 w-3 shrink-0" />
<span><b>Empfehlung: {item.empfehlung.empfehlung === "ANNEHMEN" ? "Annehmen" : item.empfehlung.empfehlung === "ABLEHNEN" ? "Ablehnen" : "Unklar"}.</b> {item.empfehlung.satz}</span>
</p>
)}
</div>
{!running && !decided && (
@@ -343,10 +357,7 @@ function ProposalCard({
</button>
<button
disabled={!!busy["ab:" + k]}
onClick={() =>
showConfirm("Vorschlag ablehnen?",
`Der Branch '${item.branch}' wird auf Gitea gelöscht. Die Arbeit bleibt in der Historie, aber die Karte verschwindet.`,
() => onAct("ab:" + k, "/api/auftragsbuch/ablehnen", { branch: item.branch, repo }))}
onClick={() => setRejecting((v) => !v)}
className="flex h-9 items-center gap-1.5 rounded-lg border border-red-500/30 bg-red-500/5 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300/90 transition-all hover:bg-red-500/15 cursor-pointer disabled:opacity-50">
{busy["ab:" + k] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />} Ablehnen
</button>
@@ -354,6 +365,38 @@ function ProposalCard({
)}
</div>
{rejecting && !running && !decided && (
<div className="space-y-2 rounded-xl border border-red-500/25 bg-red-500/5 p-3">
<p className="text-[11px] text-muted-foreground">
Der Branch '{item.branch}' wird auf Gitea gelöscht (die Arbeit bleibt in der Historie).
Sag der Box kurz <b>warum</b> dann schlägt sie dir so etwas nicht wieder vor.
</p>
<input
value={grund}
onChange={(e) => setGrund(e.target.value)}
placeholder={"Grund (optional) — z. B. „gibt es schon“ oder „will ich nicht“"}
maxLength={400}
className="w-full rounded-lg border border-border/60 bg-background/60 px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-red-400/50 focus:outline-none"
/>
<div className="flex items-center gap-2">
<button
disabled={!!busy["ab:" + k]}
onClick={() => {
setRejecting(false)
onAct("ab:" + k, "/api/auftragsbuch/ablehnen", { branch: item.branch, repo, grund: grund.trim() })
}}
className="flex h-8 items-center gap-1.5 rounded-lg border border-red-500/40 bg-red-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300 transition-all hover:bg-red-500/20 cursor-pointer disabled:opacity-50">
<X className="h-3.5 w-3.5" /> Wirklich ablehnen
</button>
<button
onClick={() => setRejecting(false)}
className="flex h-8 items-center rounded-lg border border-border/60 bg-background/40 px-3 text-[11px] font-semibold text-muted-foreground transition-all hover:text-foreground cursor-pointer">
Abbrechen
</button>
</div>
</div>
)}
{item.status && (running || !decided) && item.status.detail && (
<p className="text-[11px] text-muted-foreground">{item.status.detail}</p>
)}