sync: global→pool, pool-guard raus, Session-Watcher-Setup, verbotenes Wort Ehrlich

This commit is contained in:
marcus.hinz
2026-07-04 19:09:13 +02:00
parent a7ff9378da
commit 79314d6140
43 changed files with 669 additions and 33 deletions
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+8
View File
@@ -0,0 +1,8 @@
{
"pool": "privateDefault",
"terminal": {
"theme": "one-dark",
"icon": "/Users/marcus.hinz/projects/claude-app-builder-4-mac/icons/08-07.png",
"title": "08-07"
}
}
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+3
View File
@@ -0,0 +1,3 @@
{
"pool": "privateDefault"
}
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# connect-test — prüft, ob claude mit der aktuellen Env authentifizieren und antworten kann.
# Exit 0 = Verbindung ok. Für Negativtest mit ungültigem Key:
# ANTHROPIC_API_KEY=sk-ant-ungueltig connect-test
set -euo pipefail
"${CLAUDE_BIN:-$HOME/.local/bin/claude}" -p 'Antworte nur mit: OK' --model claude-sonnet-5
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# kc-backup — sichert das Secret des Claude-Code-Keychain-Eintrags in eine Datei.
# Ablage außerhalb des Git-Repos (Default: ~/.ssh), damit das Secret nie committet wird.
# Usage: kc-backup [zieldatei]
set -euo pipefail
dest=${1:-$HOME/.ssh/claude-keychain-backup.json}
security find-generic-password -s "Claude Code-credentials" -a "$USER" -w > "$dest"
chmod 600 "$dest"
echo "Keychain-Secret gesichert nach: $dest ($(wc -c < "$dest" | tr -d ' ') Bytes)"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# kc-delete — löscht den Claude-Code-Keychain-Eintrag.
# Vorher kc-backup ausführen; zurückspielen mit kc-restore.
set -euo pipefail
security delete-generic-password -s "Claude Code-credentials" -a "$USER"
echo "Keychain-Eintrag 'Claude Code-credentials' gelöscht."
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# kc-restore — spielt das mit kc-backup gesicherte Secret in den Keychain zurück.
# -U aktualisiert einen ggf. vorhandenen Eintrag statt zu scheitern.
# Usage: kc-restore [backupdatei]
set -euo pipefail
src=${1:-$HOME/.ssh/claude-keychain-backup.json}
security add-generic-password -U -s "Claude Code-credentials" -a "$USER" -w "$(cat "$src")"
echo "Keychain-Eintrag aus $src wiederhergestellt."
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# kc-status — zeigt Zustand des Claude-Code-Keychain-Eintrags und der Auth-Env.
# mdat = letzte Änderung des Eintrags; ändert sich, wenn eine Session den Keychain beschreibt.
set -euo pipefail
echo "== Keychain-Eintrag 'Claude Code-credentials' =="
security find-generic-password -s "Claude Code-credentials" -a "$USER" 2>&1 | grep -E '"(mdat|cdat)"' || echo "kein Eintrag vorhanden"
echo "== Auth-Env dieser Shell =="
env | grep -E '^(ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CONFIG_DIR)=' | sed -E 's/=(.).*/=\1…/' || echo "keine Auth-Env gesetzt"
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# oauth-configdir-test — prüft, ob claude auf macOS die .credentials.json aus
# CLAUDE_CONFIG_DIR liest, wenn kein Keychain-Eintrag existiert.
# Ablauf: Backup → Keychain löschen → claude mit bereinigter Auth-Env →
# Nachkontrollen → Restore. Restore läuft auch bei fehlgeschlagenem Testlauf.
# Usage: oauth-configdir-test [pool] (Default: Mx9Privat)
set -u
cd "$(dirname "$0")/.."
pool=${1:-Mx9Privat}
pooldir="$HOME/claude-pools/$pool"
bin/kc-backup || exit 1
bin/kc-delete || exit 1
echo "== Testlauf: Env ohne ANTHROPIC_API_KEY/CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_CONFIG_DIR=$pooldir =="
env -u ANTHROPIC_API_KEY -u CLAUDE_CODE_OAUTH_TOKEN \
CLAUDE_CONFIG_DIR="$pooldir" \
"${CLAUDE_BIN:-$HOME/.local/bin/claude}" -p 'Antworte nur mit: OK' --model claude-sonnet-5
echo "EXIT: $?"
echo "== credentials-Datei nach Lauf (#10039: darf nicht gelöscht sein) =="
ls -la "$pooldir/.credentials.json"
echo "== Keychain nach Lauf (es darf KEIN Eintrag entstanden sein) =="
bin/kc-status
echo "== Restore =="
bin/kc-restore
rm "$HOME/.ssh/claude-keychain-backup.json"
bin/kc-status
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+5 -1
View File
@@ -1,3 +1,7 @@
# Memory Index
- [Multi-Account & Keychain](multi-account-keychain.md) — Keychain trennt nicht pro CLAUDE_CONFIG_DIR; Tokens pro Instanz via Env
- [Go-Pflicht](feedback-go-pflicht.md) — weitreichende Änderungen nie ohne ausdrückliches Go; eine Rückfrage von Marcus ist kein Go (Vorfall 2026-07-04)
- [Terminal-Tests mit 08-07](feedback-terminal-test-08-07.md) — ABSOLUTES VERBOT: nie ein Terminal für/aus ai-control starten (killt die eigene Session, 3× Kontext verloren am 2026-07-04). Terminal-Tests nur mit 08-07 und nur auf ausdrückliche Ansage
- [Multi-Account & Keychain](multi-account-keychain.md) — Stand 2.1.199: suffixierter Keychain-Eintrag pro Config-Dir verifiziert; Pools umbenannt, Keychain-Endstand ein Eintrag 096c4ef9 (privateDefault); Kaltstart über Starter läuft (2026-07-04), Qualität laut Marcus noch nicht zufriedenstellend
- [Pool-Setup Stand](ai-control-pool-setup.md) — Layout ~/.config/ai-control/pools, Starter, alle Projekte auf privateDefault; seit 2026-07-04 eingebautes Terminal in der App (xterm.js + portable-pty, startet claude-sync), offene Punkte
@@ -0,0 +1,28 @@
---
name: ai-control-pool-setup
description: "Stand des Pool-Setups seit 2026-07-03 — Layout, Starter, was gelöscht wurde, offene Punkte"
metadata:
node_type: memory
type: project
originSessionId: e108ad75-f176-43b6-901d-c3ec19ec3961
---
Seit 2026-07-03 produktiv:
- Pools: `~/.config/ai-control/pools/<pool>/` = CLAUDE_CONFIG_DIR. `pool.json` (name, credentialType), oauth: `.credentials.json`, apikey: `apikey` + `apiKeyHelper` in pool-`settings.json`. Pools (umbenannt 2026-07-03 abends): privateDefault (vorher Mx9Privat; oauth), apiDefault (vorher TestWürth; apikey — 2026-07-03 kurz auf oauth, am selben Tag zurück auf apikey via `apiKeyHelper` in Pool-settings.json; model claude-sonnet-5). Alle 7 Projekte → privateDefault; apiDefault derzeit ohne Projekt. Pool-Wechsel eines Projekts = nur noch `ai-control.json` ändern (einzige Quelle; zieht beim nächsten Start). Pool-Umbenennung ändert den Pfad und damit den Keychain-Suffix — alte Einträge greifen dann nicht mehr.
- `CLAUDE.md`, `commands/`, `settings.json` liegen als physische Kopien in jedem Pool — bewusst poolspezifisch, keine Symlinks.
- Zuordnung: `~/claude-projects/<projekt>/ai-control.json` `{ "pool": ... }`.
- Starter: `~/Applications/Ghostty-<projekt>.app`, `LSEnvironment` setzt nur noch XDG_CONFIG_HOME (stabil, nie poolabhängig). Alle Ghostty-Configs starten `command = ~/.local/bin/claude-sync` (ggf. mit `--add-dir`-Args). `claude-sync` löst seit 2026-07-04 den Pool selbst auf (liest `./ai-control.json` im CWD, exportiert `CLAUDE_CONFIG_DIR`, laut scheiternd ohne Pool), macht git pull davor und commit+push danach. `claude-start` ist gelöscht — in `claude-sync` integriert. Env/plist tragen keinen Pool mehr.
- Pool-Guard (seit 2026-07-04): `claude-sync` schreibt Sentinel `.ai-control-running` in den Projekt-Root (Inhalt pool=CLAUDE_CONFIG_DIR, pid; FD offen gehalten, nach claude-Ende gelöscht, in .gitignore). Vorab-Check (Endstand 2026-07-03 spät): Fensterzahl statt Prozesszustand, ohne Apple-Events — `ghostty-wincount <app-pid>` (Swift-Helfer, CGWindowListCopyWindowInfo onScreenOnly + Layer 0; Binary `~/.local/bin/ghostty-wincount`, Quelle `~/claude-projects/robotunits/bin/ghostty-wincount.swift`; App-pid via ps+grep auf den App-Pfad). Das Skript läuft selbst in Fenster 1; ≥2 → Arbeitsfenster existiert → `open -a` (Launch Services, kein Prompt) + 4 s + Exit 0; sonst → Sentinel-pid und FD-Holder kill -9, Sentinel kommentarlos löschen, normal starten. Gescheiterte Vorstufen: tty-Heuristik (Zombie behält totes tty), Schreiber-PID-Check (claude-sync-Bash überlebt Ghosttys X-Close manchmal kopflos), osascript count/activate (Automation-Prompt „App X will App Y steuern" = NOGO). Bekannte Grenze: minimierte/Space-fremde Fenster zählen als unsichtbar. Traps in claude-sync: EXIT löscht Sentinel, HUP/TERM killt claude (claude läuft als Hintergrund-Kind mit `<&0` — sonst stdin=/dev/null! — und `wait`), INT no-op; greifen aber bei Ghostty-X/Quit oft nicht (hartes Kill), Rest-Aufräumen beim nächsten Start ist der Normalfall. Alle 7 Ghostty-Configs (`~/.config-ghostty/<projekt>/ghostty/config`): `wait-after-command = false`; claude-sync zeigt Schlussmeldungen per sleep (2 s aktivieren, 3 s Ende, 5 s Fehler), dann schließt das Fenster selbst. SessionStart-Hook `~/claude-projects/robotunits/bin/pool-guard` in allen 7 Projekt-settings.json: Sentinel fehlt / Pool-Mismatch / Schreiber-PID tot → rot blinkender Banner auf /dev/tty + kill des claude-Elternprozesses. RAW-Nutzung außerhalb der Projekte bleibt frei; RAW-Start IM Projekt wird beendet. Achtung: greift auch bei /clear in Sessions, die noch ohne Sentinel gestartet wurden.
- Gelöscht: `~/.claude` (komplett), Keychain-Eintrag `Claude Code-credentials`, `~/claude-pools`. Unterhaltungen (90 MB) + `history.jsonl` nach Mx9Privat umgezogen — `/resume` pro Pool.
- App-Code (`~/projects/ai-control`, Tauri): Kernlogik mit injizierbarem Home, 22 Tests (`./build.sh`-Toolchain: CARGO_HOME=~/tools/.cargo; node via fnm, nur in interaktiver zsh). Commands unassign_pool/create_project/delete_project, seit 2026-07-04 restart_project (osascript-Quit → warten → open) + Modal in ProjectList: nach Umhängen eines laufenden Projekts Restart-Angebot inkl. Hinweis, dass der claude-sync-Push beim Abschießen entfällt.
- Eingebautes Terminal (2026-07-04, Prototyp, funktioniert laut Marcus): `src-tauri/src/terminal.rs` (portable-pty, Commands open_terminal/term_start/term_write/term_resize) + `terminal.html`/`src/terminal.ts` (xterm.js, Output base64-Events, Capability `term-*`). „Terminal"-Button in ProjectList öffnet pro Klick ein eigenes Fenster; PTY startet `zsh -ic claude-sync` im Projektordner — Pool/Sentinel/git-Sync unverändert bei claude-sync, App setzt kein CLAUDE_CONFIG_DIR. Fenster-X killt Kind + schließt PTY (HUP wie Ghostty-X, Abschluss-Push entfällt). Damit Weg frei, die Ghostty-Starter abzulösen ("wir haben alles in der Hand"). Offen: Scrollback-Restore, Fensterposition merken.
- Stand 2026-07-04 abends, alles committed+gepusht (bis inkl. Todo-Feature): UI komplett auf Catppuccin-Mocha-Token + JetBrains Mono (frontend-design-Skill; Plugin `frontend-design` war kaputt — installPaths in installed_plugins.json zeigten nach Pool-Umbenennung noch auf Mx9Privat, gefixt), i18n de/en (vue-i18n, Umschalter im Header). Pools: löschbar mit Zuordnungs-Auflösung, „Neu anmelden" löscht vorher den suffixierten Keychain-Eintrag (nur bei ungenutztem Pool, Warn-Dialog), Renew entfernt (Datei-Refresh-Token tot, Keychain hat Vorrang), hasCredentials (apikey-Dateicheck) mit Start-Blockierung. Verbrauch-Tab: usage_stats parst <pool>/projects/*/*.jsonl (Dedup message.id+requestId, Preistabelle Fable 10/50 Opus 5/25 Sonnet 3/15 Haiku 1/5, Cache 1.25x/0.1x), Pool-Zeilen aggregiert + Projekte aufklappbar; apiDefault hat keinerlei Transcripts (Marcus' apiKey-Abfragen vom 04.07. liefen nachweislich nicht über claude-code auf diesem Rechner — Console zeigt die Wahrheit). Projekt-Wizard (create_project_full: Scaffold nach 08-07-Muster inkl. pool-guard-Hook, Arbeitsordner anlegen/verknüpfen) + Löschen (Guard bei laufender Session, optional Arbeitsordner aus additionalDirectories mit). Todo-Feature nur Projektebene (robotunits-Muster: OFFENE-PUNKTE.md + jq-SessionStart-Hook, zuschaltbar in Wizard+Zahnrad; Pool-Ebene bewusst nicht). App heißt ai-control (Cargo-Paket umbenannt), Hand-Icon (Tray per include_bytes + Template-Icon; Dock-Icon der Terminals via NSApplication nur in RunEvent::Ready). 38 Rust-Tests. Offen/geplant: Baustein-Bibliothek (CLAUDE.md-Marker-Mechanismus, Konzept steht: ~/.config/ai-control/blocks/, verwaltete Abschnitte zwischen ai-control:block-Markern), weitere Wizard-Optionen, Split privates Repo / offizielles GitHub-Repo (Ist-Stand als Initial-Commit ohne History; OAuth-Login-Teil wegen ToS-Grauzone fürs öffentliche Repo rausnehmen — apiKey-Teil unkritisch), Vitest für UI-Tests (Vorschlag steht), Dock-Name macOS/Wayland „später überlegen".
- Alter Zwischenstand (Commit f747379 = Terminal + Start/Beenden-Button): (a) ProjectList: ein Button pro Zeile — grün „Starten" (open_terminal) / rot „Beenden" (neues Command stop_project: SIGTERM auf exakte PIDs per `pgrep -f -- "--terminal <projekt>$"`, sonst Ghostty-Quit; NIE Quit über die App-Bundle-ID — träfe die Haupt-App); is_running = Ghostty ODER --terminal-Prozess; Status-Kreis ohne Prozess unsichtbar. (b) Terminal-Config-Dialog (Zahnrad): `terminal: { theme, icon, title }` in projekt-`ai-control.json` (pool-Feld jetzt Option, Datei bleibt bei unassign wenn terminal gesetzt); 5 Themes (mocha/dracula/solarized-dark/gruvbox/one-dark, Paletten in terminal.ts, Fenster-BG gespiegelt in terminal.rs theme_background — muss synchron bleiben); Icon = PNG/ICNS-Pfad via tauri-plugin-dialog-Picker, gesetzt per NSApplication.applicationIconImage (objc2, nur in RunEvent::Ready — in setup() überschreibt macOS es wieder); Titel: Fenstertitel + Header, leer → Projektname. `tauri::generate_context!` darf nur 1× expandieren (_EMBED_INFO_PLIST, fällt nur im Release-Build auf).
- Dock-Grenze macOS: Hover-Label im Dock = Bundle-Name, zur Laufzeit nicht öffentlich änderbar; Wrapper-Bundle pro Projekt von Marcus abgelehnt („verlieren zu viel"). Windows/X11: Titel+Icon greifen fensterbasiert; Wayland-Docks bräuchten .desktop pro Projekt (xdg-toplevel-icon nur KDE). → „da müssen wir was überlegen, nicht jetzt".
**Why:** Getrennte Zugänge pro Pool, [[multi-account-keychain]].
**Agenda 2026-07-04:** Start/Stopp-Architektur grundsätzlich besprechen — Stand vom 2026-07-03 spät (wincount/kill-9/Sentinel-Reste) funktioniert im Test, ist aber Flickwerk um Ghosttys unzuverlässiges Kill-Verhalten herum; Marcus will eine saubere Lösung statt Workarounds. Außerdem: Pool-Guard + Kaltstart über Starter testen; App: UI aufräumen, Projekte managen, Defaults/Standardverhalten (z. B. Todolist); Linux gesondert; Open-Source-Frage („works for my workflow") entschieden nach Feinschliff; außerdem Ghostty und Zed besprechen. Stand App: beta-0.8 gepusht (Tray statt Dock, Autostart-Schalter, Fenster startet unsichtbar, Restart-Dialog).
**How to apply — offen (Stand 2026-07-03):** (1) Sync-Konzept für `~/.config/ai-control` inkl. Unterhaltungen, Secrets-Frage (git). (2) UI kennt die drei neuen Commands nicht. (3) Erledigt durch Integration in `claude-sync` (2026-07-04): der „Flicker" betraf das gelöschte `claude-start`; `claude-sync` läuft in den Ghostty-Wrappern nachweislich interaktiv.
+14
View File
@@ -0,0 +1,14 @@
---
name: feedback-go-pflicht
description: Weitreichende Änderungen NIE ohne ausdrückliches Go; eine Rückfrage von Marcus ist kein Go
metadata:
node_type: memory
type: feedback
originSessionId: 6c3fc02d-bc5c-4476-9384-8062394a85ff
---
Weitreichende Änderungen (Verhaltensänderungen im Backend, Löschlogik, alles mit Datenverlust-Potenzial) nie umsetzen, bevor Marcus ausdrücklich Go gegeben hat. Eine Rückfrage oder Diskussionsfrage von Marcus („was machen wir dann mit X?") ist KEIN Go — Frage beantworten, dann stoppen und warten.
**Why:** 2026-07-04: Nach Marcus' Rückfrage zur Pool-Löschung habe ich direkt implementiert; Marcus hat abgebrochen („ICH HATTE NICHT GESAGT, DASS DU DAS TUN SOLLST"). Sein „ja" galt dem Plan davor, seine Folgefrage hat die Diskussion wieder geöffnet.
**How to apply:** Bei jedem Plan mit offener Designfrage: erst alle offenen Punkte klären, dann explizites Go für den Gesamtumfang abwarten. Antwort auf eine Frage endet mit der Antwort, nicht mit Tool-Calls.
@@ -0,0 +1,20 @@
---
name: feedback-terminal-test-08-07
description: "Terminal-/Restart-Tests der App immer mit Projekt 08-07, nie mit ai-control"
metadata:
node_type: memory
type: feedback
originSessionId: dd1d2798-9e9a-4e95-bf63-7793e7801020
---
Für Tests des eingebauten Terminals und von restart_project immer das Projekt **08-07** als Beispiel nehmen, nie **ai-control**.
**Why:** Die Arbeitssession läuft selbst im Projekt ai-control — ein Terminal-/Restart-Test dort killt die eigene Session und der Kontext ist weg.
**How to apply:** In Testanweisungen, Beispielaufrufen und UI-Tests (`open_terminal`, `restart_project`) als Projektname `08-07` verwenden. Siehe [[ai-control-pool-setup]].
**Stand 2026-07-04:** Die Session läuft selbst im eingebauten Terminal der App (xterm.js + portable-pty). Ein Neustart/Beenden der App killt die Session direkt — die 08-07-Regel schützt nur gegen `restart_project ai-control`, nicht gegen Tests auf App-Ebene.
**Verschärfung (2026-07-04, nach erneutem Vorfall):** Alles, was den App-Prozess oder das eigene Terminal berührt, ist tabu — App-Neustart, App-Build+Relaunch, Kill von Electron-/pty-Prozessen, `pkill`/`kill` auf Prozesse, deren Zugehörigkeit nicht geprüft ist. Vor jedem Kill/Restart erst prüfen, ob der Zielprozess in der eigenen Prozesskette hängt. Solche Tests laufen nur nach Ansage von Marcus und dann in 08-07 bzw. einer separaten App-Instanz.
**Absolutes Verbot (2026-07-04, dritter Vorfall — Kontext erneut verloren):** NIE ein Terminal für oder aus ai-control starten — kein `open_terminal ai-control`, kein Terminal-Start über die App, keine Ausnahme „nur kurz testen". Jeder Terminal-Start, der ai-control betrifft, killt diese Session. Terminal-Funktionen werden ausschließlich mit 08-07 getestet, und nur wenn Marcus es ausdrücklich anweist.
+11 -5
View File
@@ -1,14 +1,20 @@
---
name: multi-account-keychain
description: macOS-Keychain trennt Claude-Code-Credentials NICHT pro CLAUDE_CONFIG_DIR — Multi-Account nur über Env-Tokens
description: "Multi-Account auf macOS — Stand 2.1.199: pro CLAUDE_CONFIG_DIR eigener suffixierter Keychain-Eintrag; .credentials.json-Ansatz überholt"
metadata:
node_type: memory
type: project
originSessionId: 5c4c75da-b313-4740-9988-49533ba77ed1
originSessionId: e108ad75-f176-43b6-901d-c3ec19ec3961
---
Verifiziert 2026-07-03: Claude Code nutzt auf macOS einen einzigen Keychain-Eintrag (Service `Claude Code-credentials`, Account = Unix-User) für alle `CLAUDE_CONFIG_DIR`-Profile. Mehrere OAuth-Logins überschreiben sich gegenseitig (Issue anthropics/claude-code#20553); der Datei-Fallback `.credentials.json` wird auf macOS gelöscht (#10039).
ÜBERHOLT (galt bis Version vor 2.1.199): claude las die `.credentials.json` direkt aus `CLAUDE_CONFIG_DIR` (verifiziert 2026-07-03 vormittags, Test `bin/oauth-configdir-test`).
**Why:** ai-control soll 34 Claude-Zugänge (Abo + Kunden-API-Keys) strikt getrennt auf einem Rechner betreiben.
Stand 2026-07-03 abends, Version 2.1.199 (native Installation): Neu-Login war nötig. Die `.credentials.json` im Pool `Mx9Privat` ist weg; claude speichert jetzt pro Config-Dir einen Keychain-Eintrag `Claude Code-credentials-<suffix>`, Suffix = erste 8 Hex-Zeichen von SHA-256 über den `CLAUDE_CONFIG_DIR`-Pfad (Mx9Privat → `a014e206`, TestWürth → erwartet `4c0ad9a9`, ggf. NFC/NFD-Frage wegen „ü"). Ohne gesetztes `CLAUDE_CONFIG_DIR` entsteht der unsuffixierte Eintrag `Claude Code-credentials` plus `~/.claude`/`~/.claude.json`. Damit ist #20553 (Überschreiben) upstream gelöst — pro Pool ein eigener Eintrag. Verifiziert 2026-07-03 abends: Nach `/login` in ai-control (Mx9Privat) lief wuerth-plato (gleicher Pool) ohne erneutes Login — Eintrag `a014e206` wird poolweit geteilt. Keychain enthielt danach nur diesen einen Claude-Code-Eintrag (kein unsuffixierter). Zweiter Pool ebenfalls verifiziert: 08-07 (TestWürth, oauth) verlangte wie erwartet erneutes Login und erzeugte `Claude Code-credentials-4c0ad9a9` — Suffix stimmt mit der Vorab-Berechnung überein, NFC/NFD-Frage wegen „ü" damit geklärt. Keychain-Stand: genau zwei Einträge, `a014e206` (Mx9Privat) und `4c0ad9a9` (TestWürth). Multi-Account über suffixierte Keychain-Einträge funktioniert damit vollständig. Dritter Beleg: robotunits nach Umzug auf TestWürth ohne Login gestartet (Starter-Pfad hasht identisch auf `4c0ad9a9`). Mit gesetztem `CLAUDE_CONFIG_DIR` entsteht dabei kein neues `~/.claude` im Home — das gelöschte bleibt weg.
**How to apply:** Pro Instanz Credentials über Env setzen: `CLAUDE_CODE_OAUTH_TOKEN` (via `claude setup-token`, 1 Jahr gültig) für Abo-Zugänge, `ANTHROPIC_API_KEY` für Kunden-Keys. `CLAUDE_CONFIG_DIR` zusätzlich pro Zugang für getrennte settings/History/Projektzustand — nur nicht für Credentials verlassen.
Umbenennung 2026-07-03 spätabends: Mx9Privat → privateDefault, TestWürth → apiDefault. Suffixe: privateDefault → `096c4ef9`, apiDefault → `15e10251`. Die alten Einträge `a014e206`/`4c0ad9a9` wurden gelöscht; kurz darauf lag `096c4ef9` im Keychain — eine laufende Session hat die Credentials unter dem neuen Pfad selbst neu geschrieben, ohne Login-Prompt. Weitere Session-Starts erzeugten keinen zusätzlichen Eintrag. Keychain-Endstand 2026-07-03: genau ein Eintrag `096c4ef9`; für apiDefault (`15e10251`) existiert noch keiner, dort ist beim ersten Start Login nötig.
Kaltstart-Test über die umgestellten Starter (2026-07-04): durchgeführt, funktioniert grundsätzlich (kein Login nötig), aber laut Marcus nicht in der gewünschten Qualität — worin genau der Mangel liegt, ist noch nicht festgehalten.
**Why:** Mehrere Claude-Zugänge (Abo + Kunden-Keys) strikt getrennt auf einem Rechner.
**How to apply:** Pro Pool ein Ordner als `CLAUDE_CONFIG_DIR` (siehe [[ai-control-pool-setup]]). OAuth: `.credentials.json` (0600) in den Pool-Ordner. API-Key: `apiKeyHelper` in der Pool-`settings.json` auf die Key-Datei. `CLAUDE_CODE_OAUTH_TOKEN`-Export ist unnötig.
+15 -3
View File
@@ -1,12 +1,24 @@
{
"autoMemoryDirectory": "~/claude-projects/limbach/memory",
"hooks": {
"SessionStart": [
{
"hooks": [
{
"command": "jq -Rs '{systemMessage: ., hookSpecificOutput:{hookEventName:\"SessionStart\", additionalContext: .}}' /Users/marcus.hinz/claude-projects/limbach/OFFENE-PUNKTE.md",
"type": "command"
}
]
}
]
},
"permissions": {
"additionalDirectories": [
"~/projects/limbach"
],
"allow": [
"Edit(~/projects/limbach/**)",
"Edit(~/claude-projects/limbach/**)"
],
"additionalDirectories": [
"~/projects/limbach"
]
}
}
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+3
View File
@@ -0,0 +1,3 @@
# Offene Punkte — bei jedem Start prüfen und abhaken
Keine offenen Punkte.
+6
View File
@@ -0,0 +1,6 @@
{
"pool": "privateDefault",
"terminal": {
"icon": "/Users/marcus.hinz/projects/claude-app-builder-4-mac/icons/limbach.png"
}
}
+1 -1
View File
@@ -19,6 +19,6 @@ Plan — ein `update_scripts`-Command, der im aktuellen Projektordner die `addit
2. **cd-Aliasse** `cd-project-root` / `cd-claude-root`: exakte Bash-Regeln je Pfad statt `cd:*`, z. B. `Bash(cd ~/projects/limbach)` und `Bash(cd ~/claude-projects/limbach)`. cwd persistiert zwischen Bash-Calls, reicht zum Springen.
3. **Read/Edit/Write-Globs** je Verzeichnis: `Read(~/projects/limbach/**)`, `Edit(~/projects/limbach/**)`, `Write(~/projects/limbach/**)` und analog für `~/claude-projects/limbach/**`. Damit läuft Coden in genau diesen Dirs prompt-frei, außerhalb wird weiter gefragt.
Aufräumen: globales `Bash(cd:*)` in `~/claude-projects/global/settings.json` (Symlink-Ziel von `~/.claude/settings.json`) wieder entfernen — vom User als „Mist" bewertet. Globale git-Regeln (`git add/commit/push/…:*`) ebenfalls raus, sobald die Wrapper stehen.
Aufräumen: globales `Bash(cd:*)` in `~/claude-projects/pool/settings.json` (Symlink-Ziel von `~/.claude/settings.json`) wieder entfernen — vom User als „Mist" bewertet. Globale git-Regeln (`git add/commit/push/…:*`) ebenfalls raus, sobald die Wrapper stehen.
Hintergrund: `git-sync` ist davon getrennt (synct alle Repos batch-weise, [[git-sync]]). Ursache früherer Prompts war u. a. das Bündeln von Git mit Nicht-Git-Befehlen (`cd … && ls`, `printf > datei`) in einer Zeile — Permission-Matching prüft jedes `&&`-Segment einzeln; die Wrapper umgehen das.
+23
View File
@@ -0,0 +1,23 @@
{
"autoMemoryDirectory": "~/claude-projects/misc/memory",
"hooks": {
"SessionStart": [
{
"hooks": [
{
"command": "jq -Rs '{systemMessage: ., hookSpecificOutput:{hookEventName:\"SessionStart\", additionalContext: .}}' /Users/marcus.hinz/claude-projects/misc/OFFENE-PUNKTE.md",
"type": "command"
}
]
}
]
},
"permissions": {
"additionalDirectories": [
"~/projects/misc"
],
"allow": [
"Edit(~/projects/misc/**)"
]
}
}
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+3
View File
@@ -0,0 +1,3 @@
# Offene Punkte — bei jedem Start prüfen und abhaken
Keine offenen Punkte.
+3
View File
@@ -0,0 +1,3 @@
{
"pool": "privateDefault"
}
+2 -1
View File
@@ -23,7 +23,7 @@
# Anker
- Diese Datei ist die Quelle für `~/.claude/CLAUDE.md` (Symlink). Änderungen direkt hier in `~/claude-projects/global/CLAUDE.md` vornehmen, nicht über den Symlink-Pfad.
- Diese Datei ist die Quelle für `~/.claude/CLAUDE.md` (Symlink). Änderungen direkt hier in `~/claude-projects/pool/CLAUDE.md` vornehmen, nicht über den Symlink-Pfad.
- Anker für projektübergreifend Gemeinsames (Checklisten, Notizen, Memory, geteilte Dateien) ist `~/claude-projects/robotunits`. Solche Dateien gehören dorthin, nicht unter `~/projects/...`.
- Projektspezifisches (z. B. SessionStart-Hooks) bleibt im jeweiligen Projekt unter dessen `.claude/settings.json` — nicht global.
@@ -54,3 +54,4 @@ Nie verwenden, weder in Texten noch in Antworten:
- „Der Clou" / „Der Witz" / „Der Hit" / „Die Pointe"
- Sinngleiche Effekthascherei: „der Knaller", „das Highlight", „der Wow-Effekt", „die Sensation"
- Englische Technikbegriffe nicht holprig eindeutschen (z. B. nicht „spritzen" für *inject*).
- „Ehrlich" als Einleitung oder Anrede (z. B. „Ehrlich gesagt", „Ehrlich zum …") — stattdessen direkt die Aussage.
@@ -5,7 +5,7 @@ description: Commit + Push aller persistent konfigurierten Projekt-Repos
Führe genau dieses eine Kommando aus, sonst nichts:
```bash
~/claude-projects/global/bin/git-sync
~/claude-projects/pool/bin/git-sync
```
Das Skript liest selbst die `permissions.additionalDirectories` aus den Settings und
+3 -3
View File
@@ -11,9 +11,10 @@
"Bash(git mv:*)",
"Bash(git check-ignore:*)",
"Bash(cd:*)",
"Bash(~/claude-projects/global/bin/git-sync:*)"
"Bash(~/claude-projects/pool/bin/git-sync:*)"
]
},
"model": "claude-fable-5[1m]",
"enabledPlugins": {
"frontend-design@claude-plugins-official": true,
"swift-lsp@claude-plugins-official": true
@@ -21,6 +22,5 @@
"promptSuggestionEnabled": false,
"awaySummaryEnabled": false,
"tui": "fullscreen",
"theme": "dark",
"model": "claude-fable-5[1m]"
"theme": "dark"
}
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+338
View File
@@ -0,0 +1,338 @@
<!--
RSS generated by JIRA (1001.0.0-SNAPSHOT#100292-rev:f152b838bb945118c071b297741ecc5e36093b62) at Fri Jul 03 09:13:25 UTC 2026
It is possible to restrict the fields that are returned in this document by specifying the 'field' parameter in your request.
For example, to request only the issue key and summary add field=key&field=summary to the URL of your request.
-->
<rss version="0.92" >
<channel>
<title>Jira adesso Group extern</title>
<link>https://adesso-group-extern.atlassian.net</link>
<description>This file is an XML representation of an issue</description>
<language>en-us</language> <build-info>
<version>1001.0.0-SNAPSHOT</version>
<build-number>100292</build-number>
<build-date>30-06-2026</build-date>
</build-info>
<item>
<title>[GEKO-16] Schutzzaun im Layout schlie&#223;en/Schutzzaun zwischen zwei Punkten planen</title>
<link>https://adesso-group-extern.atlassian.net/browse/GEKO-16</link>
<project id="12341" key="GEKO">GEKO</project>
<description>&lt;p&gt;Dieses Ticket implementiert die interaktive Planung eines Schutzzauns zwischen zwei Punkten im 3D-Layout. Die Funktion erm&#246;glicht das Ziehen einer Strecke, berechnet die Geometrie und generiert den Zaun als kaskadierende Baugruppe. Neu ist die intelligente Vererbungslogik: Werden PMIs als Start- oder Endpunkt genutzt, erbt der Zaun Typ und F&#252;llung automatisch; andernfalls werden diese &#252;ber ein modales Overlay abgefragt.&lt;/p&gt;
&lt;h3&gt;&lt;a name=&quot;%F0%9F%9F%A6DefinitionofReady%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt; Definition of Ready&lt;/b&gt;&#160;&lt;/h3&gt;
&lt;h4&gt;&lt;a name=&quot;Wer%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Wer?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;p&gt;Anwender des Schutzzaun-Konfigurators im 3D-Layout.&lt;/p&gt;
&lt;h4&gt;&lt;a name=&quot;Was%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Was?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Interaktives Setzen von Start- und Endpunkt (frei oder gesnappt an PMIs).&lt;/li&gt;
&lt;li&gt;Dynamische Anzeige von 3 Bema&#223;ungen (L&#228;nge, Diagonale, Winkel) w&#228;hrend der Mausbewegung.&lt;/li&gt;
&lt;li&gt;Intelligente Eigenschafts-Vererbung (Typ/F&#252;llung) basierend auf Start-/End-PMIs zur Vermeidung unn&#246;tiger UI-Abfragen.&lt;/li&gt;
&lt;li&gt;Filterung von PMIs: Inkompatible End-PMIs werden nach der Auswahl des Start-PMIs ignoriert und nicht gehighlighted.&lt;/li&gt;
&lt;li&gt;Mathematische Berechnung von 2D-L&#228;nge (XZ-Ebene) und Ausrichtungswinkel.&lt;/li&gt;
&lt;li&gt;Algorithmus-gest&#252;tzte Aufteilung der Strecke in Standard- und Sonderelemente.&lt;/li&gt;
&lt;li&gt;Generierung des Zauns als kaskadierender Verbund (Typ 2 Einbaubedingungen).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;a name=&quot;Warum%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Warum?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;p&gt;Effiziente Planung von Schutzz&#228;unen &#252;ber gr&#246;&#223;ere Distanzen, ohne dass der Nutzer jedes Element einzeln platzieren muss und unter Vermeidung von fehlerhaften Typen-Mischungen.&lt;/p&gt;
&lt;h4&gt;&lt;a name=&quot;Wozu%3F%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;Wozu?&lt;/b&gt;&#160;&lt;/h4&gt;
&lt;p&gt;Ein strukturierter, in den Szenegraphen integrierter Schutzzaun-Zug, mit dem offene Z&#252;ge geschlossen werden k&#246;nnen.&lt;/p&gt;</description>
<environment></environment>
<key id="139286">GEKO-16</key>
<summary>Schutzzaun im Layout schlie&#223;en/Schutzzaun zwischen zwei Punkten planen</summary>
<type id="12871" iconUrl="https://adesso-group-extern.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium">Task</type>
<parent id="119259">GEKO-6</parent>
<priority id="10001" iconUrl="https://adesso-group-extern.atlassian.net/images/icons/priorities/major.svg">Major</priority>
<status id="14794" iconUrl="https://adesso-group-extern.atlassian.net/" description="">To Do</status>
<statusCategory id="2" key="new" colorName="blue-gray"/>
<resolution id="-1">Unresolved</resolution>
<assignee accountid="712020:1a31ac71-0e6f-49af-83f9-0d630a810204">Marcus Hinz</assignee>
<reporter accountid="712020:0a2bb4c7-7da6-48f0-8264-d021465e5f0f">Lukas H&#246;rger</reporter>
<labels>
</labels>
<created>Wed, 8 Apr 2026 11:43:54 +0200</created>
<updated>Thu, 2 Jul 2026 14:28:25 +0200</updated>
<due></due>
<votes>0</votes>
<watches>0</watches>
<attachments>
</attachments>
<subtasks>
</subtasks>
<customfields>
<customfield id="customfield_10000" key="com.atlassian.jira.plugins.jira-development-integration-plugin:devsummarycf">
<customfieldname>Development</customfieldname>
<customfieldvalues>
</customfieldvalues>
</customfield>
<customfield id="customfield_10019" key="com.pyxis.greenhopper.jira:gh-lexo-rank">
<customfieldname>Rank</customfieldname>
<customfieldvalues>
<customfieldvalue>0|i5ji9k:</customfieldvalue>
</customfieldvalues>
</customfield>
<customfield id="customfield_13432" key="com.atlassian.jira.plugin.system.customfieldtypes:select">
<customfieldname>T-Shirt Size - Storypoint</customfieldname>
<customfieldvalues>
<customfieldvalue key="20365"><![CDATA[L]]></customfieldvalue>
</customfieldvalues>
</customfield>
<customfield id="customfield_13430" key="com.atlassian.jira.plugin.system.customfieldtypes:textarea">
<customfieldname> Definition of Ready - Formale Kriterien</customfieldname>
<customfieldvalues>
<customfieldvalue>&lt;h3&gt;&lt;a name=&quot;%F0%9F%93%98Funktionsbeschreibung%3A&quot;&gt;&lt;/a&gt;&lt;b&gt; Funktionsbeschreibung:&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Input:&lt;/b&gt; Klick-Events im 3D-Viewport und ggf. Auswahl-Events im modalen Overlay.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Verhalten / Ablauf:&lt;/b&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;Startpunkt (Klick 1):&lt;/b&gt; Nutzer setzt den ersten Punkt (Typ 1 Weltkoordinaten oder Typ 2 PMI).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Zeichnen &amp;amp; Filtern:&lt;/b&gt; Unmittelbar &lt;b&gt;nach dem 1. Klick&lt;/b&gt; (sofern dieser an einem PMI erfolgte) filtert die Engine den Szenegraphen. Anhand des Typs des Start-PMIs (z. B. Basic) werden alle inkompatiblen PMIs (z. B. Allround) f&#252;r den aktuellen Zeichen-Modus als ung&#252;ltig markiert. W&#228;hrend der Nutzer nun die Gummiband-Linie zum Endpunkt zieht, werden diese inkompatiblen PMIs konsequent nicht gehighlighted und k&#246;nnen nicht angeklickt werden.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Endpunkt (Klick 2):&lt;/b&gt; Nutzer setzt den zweiten Punkt.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Vererbungslogik &amp;amp; Konfiguration:&lt;/b&gt; Die Engine entscheidet anhand folgender Logik-Matrix, woher die Parameter (Typ/F&#252;llung) stammen:
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Start: Frei | Ende: Frei&lt;/b&gt; &#8594; Keine Vererbung. Das modale Auswahl-Overlay &#246;ffnet sich. Standard-Elemente werden nach Best&#228;tigung generiert.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Start: PMI | Ende: Frei&lt;/b&gt; &#8594; Vererbung der Properties vom Start-PMI. Das Overlay erscheint &lt;b&gt;nicht&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Start: Frei | Ende: PMI&lt;/b&gt; &#8594; Vererbung der Properties vom End-PMI. Das Overlay erscheint &lt;b&gt;nicht&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Start: PMI | Ende: PMI&lt;/b&gt; &#8594; Vererbung der Properties vom Start-PMI. (Typengleichheit ist durch das Filtern in Schritt 2 bereits sichergestellt). Das Overlay erscheint &lt;b&gt;nicht&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Geometrie-Berechnung:&lt;/b&gt; Y-Offset und Rotationen werden ignoriert. Absolute Strecke &#252;ber Pythagoras ({{L = &#8730;(&#916;x&#178; + &#916;z&#178;)}}), Winkel &#252;ber &lt;tt&gt;atan2(&#916;z, &#916;x)&lt;/tt&gt; (auf XZ-Ebene projiziert).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Element-Aufteilung:&lt;/b&gt; Die Logik &lt;tt&gt;berechnung_aufteilung_laenge&lt;/tt&gt; ermittelt die Rasterelemente &lt;em&gt;(siehe Algorithmus-Abschnitt unten)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Generierung:&lt;/b&gt; Elemente werden kaskadierend via PMI (Typ 2) eingebaut.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Validierung:&lt;/b&gt; Trigger der lokalen Validierungsengine.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;a name=&quot;Algorithmus%3AElementAufteilung%28%7B%7Bberechnungaufteilunglaenge%7D%7D%29&quot;&gt;&lt;/a&gt;Algorithmus: Element-Aufteilung (&lt;tt&gt;berechnung_aufteilung_laenge&lt;/tt&gt;)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;1. Grundkonfiguration:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Typ &quot;Basic&quot;:&lt;/b&gt; Der Elementabstand wird als Parameter &#252;bergeben; es gibt keinen Steherzusatz (0 mm).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Typ &quot;Allround&quot;:&lt;/b&gt; Fester Elementabstand von &lt;b&gt;11 mm&lt;/b&gt;, Steherzusatz von &lt;b&gt;51 mm&lt;/b&gt; pro Element.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;T&#252;ren:&lt;/b&gt; Ist eine T&#252;r in der Konfiguration enthalten, wird pauschal ein Ma&#223; von &lt;b&gt;1124 mm&lt;/b&gt; (zzgl. Elementabstand) von der Gesamtbreite abgezogen.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2. Standardelemente (Greedy):&lt;/b&gt; Der Raum wird systematisch mit den gr&#246;&#223;ten passenden Standardelementen gef&#252;llt (Priorit&#228;t: 1507, 1034, 776, 518 mm).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;3. Dead Zone Correction (Todeszone):&lt;/b&gt; Bleibt eine kritische Restbreite (zwischen 43 mm und Mindestbreite der F&#252;llung) &#252;brig, wird das kleinste bereits verplante Element downgradet (z. B. 776 mm -&amp;gt; 518 mm) oder gel&#246;scht, um Platz f&#252;r ein produzierbares Sonderelement zu schaffen.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;4. Sonderelement (Rasterung):&lt;/b&gt; Aus der Restbreite wird im &lt;b&gt;43-mm-Raster&lt;/b&gt; das finale Passst&#252;ck ermittelt. Restl&#252;cken &amp;lt; 43 mm werden als Toleranz ignoriert.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Pseudocode der Aufteilungslogik:&lt;/b&gt;&lt;/p&gt;
&lt;div class=&quot;preformatted panel&quot; style=&quot;border-width: 1px;&quot;&gt;&lt;div class=&quot;preformattedContent panelContent&quot;&gt;
&lt;pre&gt;FUNCTION calculate_width_distribution(type, has_door, gap_input, total_width, filling_type)
INITIALIZE results AS dictionary
// --- 1. Base Setup &amp;amp; Door Deduction ---
IF type == &quot;Basic&quot; THEN
gap = gap_input
post_addition = 0
ELSE IF type == &quot;Allround&quot; THEN
gap = 11
post_addition = 51
END IF
IF has_door == TRUE THEN
deduction = 1124 + gap
results[&quot;door_count&quot;] = 1
ELSE IF type != &quot;0&quot; THEN
deduction = 51
results[&quot;door_count&quot;] = 0
ELSE
deduction = 0
results[&quot;door_count&quot;] = 0
END IF
remaining_width = total_width - deduction
test_width = remaining_width
// --- 2. Distribute Standard Elements (Greedy Approach) ---
STANDARD_SIZES = [1507, 1034, 776, 518]
element_counts = {1507: 0, 1034: 0, 776: 0, 518: 0}
FOR EACH size IN STANDARD_SIZES DO
required_width = size + post_addition
WHILE test_width &amp;gt;= required_width DO
element_counts[size] = element_counts[size] + 1
remaining_width = test_width - required_width
test_width = remaining_width - gap
END WHILE
END FOR
// --- 3. Dead Zone Correction ---
min_special_element = (IF filling_type == 0 THEN 174 ELSE 131) + post_addition
IF test_width &amp;gt;= 43 AND test_width &amp;lt;= min_special_element THEN
smallest_assigned = FIND_SMALLEST_ASSIGNED_ELEMENT(element_counts)
next_smaller = FIND_NEXT_SMALLER_SIZE(STANDARD_SIZES, smallest_assigned)
IF smallest_assigned EXISTS THEN
element_counts[smallest_assigned] = element_counts[smallest_assigned] - 1
IF next_smaller EXISTS THEN
// Downgrade to next smaller standard element
element_counts[next_smaller] = element_counts[next_smaller] + 1
difference = smallest_assigned - next_smaller
remaining_width = remaining_width + difference
test_width = test_width + difference
ELSE
// Lowest element was 518, remove it completely
freed_space = post_addition + smallest_assigned + gap
remaining_width = remaining_width + freed_space
test_width = test_width + freed_space
END IF
END IF
END IF
SAVE element_counts TO results
test_width = test_width - post_addition
// --- 4. Special Element (Sonderelement) Calculation ---
special_element_width = 0
test_width_int = FLOOR(test_width)
IF test_width_int &amp;lt; 43 THEN
special_element_width = 0
ELSE IF test_width_int &amp;gt;= 518 AND test_width_int &amp;lt; 561 THEN
results[&quot;518_count&quot;] = results[&quot;518_count&quot;] + 1
remaining_width = remaining_width - 518 - gap - post_addition
ELSE IF test_width_int &amp;gt;= 776 AND test_width_int &amp;lt; 819 THEN
results[&quot;776_count&quot;] = results[&quot;776_count&quot;] + 1
remaining_width = remaining_width - 776 - gap - post_addition
ELSE IF test_width_int &amp;gt;= 131 AND test_width_int &amp;lt; 776 THEN
// Calculate based on a grid step of 43
calculated_size = ((test_width_int - 131) / 43) * 43 + 131
IF calculated_size == 131 AND min_special_element != 131 + post_addition THEN
special_element_width = 0 // Min size rule prevents a 131 element
ELSE
special_element_width = calculated_size
END IF
END IF
// --- 5. Final Remainder Output ---
results[&quot;special_element&quot;] = special_element_width
IF special_element_width &amp;gt; 0 THEN
remaining_width = remaining_width - special_element_width - gap - post_addition
END IF
results[&quot;final_remainder&quot;] = remaining_width
RETURN results
END FUNCTION
FUNCTION determine_parameters_and_generate(start_point, end_point)
// --- 1. Vererbungs-Weiche (Typ und F&#252;llung ermitteln) ---
IF start_point.is_pmi == TRUE THEN
// Vererbung vom Start-PMI (gilt f&#252;r PMI+Frei und PMI+PMI)
fence_type = start_point.parent.type
fence_filling = start_point.parent.filling
ELSE IF end_point.is_pmi == TRUE THEN
// R&#252;ckw&#228;rtsvererbung vom End-PMI (gilt f&#252;r Frei+PMI)
fence_type = end_point.parent.type
fence_filling = end_point.parent.filling
ELSE
// Beide Punkte frei (Frei+Frei) -&amp;gt; Modales UI-Overlay
ui_selection = SHOW_MODAL_OVERLAY()
IF ui_selection == CANCELLED THEN RETURN
fence_type = ui_selection.type
fence_filling = ui_selection.filling
END IF
// --- 2. Geometrie berechnen ---
total_width = CALCULATE_2D_DISTANCE(start_point, end_point)
angle = CALCULATE_2D_ANGLE(start_point, end_point)
// --- 3. Aufteilung berechnen (Kern-Algorithmus bleibt unver&#228;ndert) ---
distribution = calculate_width_distribution(fence_type, FALSE, 0, total_width, fence_filling)
// --- 4. Kaskadierende Platzierung triggern ---
PLACE_FENCE_ELEMENTS(distribution, start_point, angle)
END FUNCTION&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h4&gt;&lt;a name=&quot;NichtZiele%28OutofScope%29&quot;&gt;&lt;/a&gt;Nicht-Ziele (Out of Scope)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Live-Vorschau (Ghosting):&lt;/b&gt; Das Rendern einer 3D-Vorschau des Zauns w&#228;hrend der Mausbewegung.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Passst&#252;cke (Sonderbau):&lt;/b&gt; Generierung freier Sonder-Bauteile au&#223;erhalb des 43-mm-Rasters.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spezifikation des Overlays:&lt;/b&gt; Das UI des modalen Overlays wird separat spezifiziert.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a name=&quot;%F0%9F%93%91Akzeptanzkriterien%3A&quot;&gt;&lt;/a&gt;&lt;b&gt; Akzeptanzkriterien:&lt;/b&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Die Vererbungsmatrix wird strikt eingehalten: Das Konfigurations-Overlay &#246;ffnet sich &lt;em&gt;ausschlie&#223;lich&lt;/em&gt;, wenn sowohl Start- als auch Endpunkt frei im Raum (Typ 1) liegen.&lt;/li&gt;
&lt;li&gt;Ist der Startpunkt ein PMI, werden inkompatible PMIs im Viewer beim Hovern ignoriert und nicht als Snapping-Ziel angeboten.&lt;/li&gt;
&lt;li&gt;Startet der Zaun frei und endet an einem PMI, &#252;bernimmt der Zaun die Parameter des Ziel-Elements fehlerfrei (R&#252;ckw&#228;rtsvererbung).&lt;/li&gt;
&lt;li&gt;Geometrie wird flach auf der XZ-Ebene berechnet; H&#246;henunterschiede zwischen den Punkten f&#252;hren nicht zu schiefen Z&#228;unen.&lt;/li&gt;
&lt;li&gt;Kaskadierendes Verhalten: Bei Verschiebung des Eltern-Bauteils bewegt sich der gesamte Zaun-Verbund mit.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a name=&quot;%F0%9F%A7%AATestCases%3A&quot;&gt;&lt;/a&gt;&lt;b&gt; Test Cases:&lt;/b&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;R&#252;ckw&#228;rtsvererbung&amp;#93;&lt;/span&gt;&lt;/b&gt;: Startpunkt ins Freie klicken -&amp;gt; Endpunkt an PMI eines Basic-Zauns mit Polycarbonat-F&#252;llung snappen -&amp;gt; &lt;em&gt;Erwartetes Ergebnis:&lt;/em&gt; Kein Overlay &#246;ffnet sich. Es wird sofort ein Basic-Zaun mit Polycarbonat generiert, der an das Ziel snappt.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;PMI Typen-Filter&amp;#93;&lt;/span&gt;&lt;/b&gt;: Startpunkt an Allround-PMI klicken -&amp;gt; Maus &#252;ber ein Basic-PMI bewegen -&amp;gt; &lt;em&gt;Erwartetes Ergebnis:&lt;/em&gt; Das Basic-PMI wird nicht gehighlighted und kann nicht als Endpunkt ausgew&#228;hlt werden.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Overlay-Trigger&amp;#93;&lt;/span&gt;&lt;/b&gt;: Startpunkt frei klicken -&amp;gt; Endpunkt frei klicken -&amp;gt;&lt;em&gt;Erwartetes Ergebnis:&lt;/em&gt; Modales Overlay zur Parameterauswahl &#246;ffnet sich.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a name=&quot;%E2%9A%A0%EF%B8%8FEdgeCases%3A%C2%A0&quot;&gt;&lt;/a&gt;&lt;b&gt;&#9888;&#65039; Edge Cases:&lt;/b&gt;&#160;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Abbruch w&#228;hrend Zeichnen&amp;#93;&lt;/span&gt;&lt;/b&gt;: &lt;tt&gt;ESC&lt;/tt&gt; oder ein Klick au&#223;erhalb des Canvas bricht den Modus nach Klick 1 restlos ab.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Distanz zu kurz&amp;#93;&lt;/span&gt;&lt;/b&gt;: Ist die Gesamtdistanz kleiner als das schmalste verf&#252;gbare Rasterelement, f&#252;hrt der Abschluss im Overlay zu einem No-Op (keine Generierung).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span class=&quot;error&quot;&gt;&amp;#91;Eltern-L&#246;schung&amp;#93;&lt;/span&gt;&lt;/b&gt;: Wird das Bauteil gel&#246;scht, an dessen PMI der Zaun beginnt, verliert das erste Zaunelement seine Typ 2 Bedingung, f&#228;llt auf Typ 1 zur&#252;ck und der Zaun bleibt stehen.&lt;/li&gt;
&lt;/ul&gt;
</customfieldvalue>
</customfieldvalues>
</customfield>
<customfield id="customfield_13429" key="com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes">
<customfieldname> DoR - Checkliste</customfieldname>
<customfieldvalues>
<customfieldvalue key="20347"><![CDATA[Ziel eindeutig formuliert (W-Fragen vollständig?)]]></customfieldvalue>
<customfieldvalue key="20349"><![CDATA[Akzeptanzkriterien definiert]]></customfieldvalue>
<customfieldvalue key="20350"><![CDATA[mind. ein Testfall dokumentiert]]></customfieldvalue>
<customfieldvalue key="20351"><![CDATA[Priorität gesetzt]]></customfieldvalue>
<customfieldvalue key="20352"><![CDATA[Edge Cases beschrieben]]></customfieldvalue>
<customfieldvalue key="20353"><![CDATA[TShirtAufwand geschätzt]]></customfieldvalue>
</customfieldvalues>
</customfield>
</customfields>
</item>
</channel>
</rss>
+3
View File
@@ -0,0 +1,3 @@
{
"pool": "privateDefault"
}
+15
View File
@@ -0,0 +1,15 @@
// ghostty-wincount <pid> Anzahl sichtbarer Normal-Fenster (Layer 0) des
// Prozesses, direkt vom Window-Server. Keine Apple-Events, kein TCC-Prompt.
import CoreGraphics
import Foundation
guard CommandLine.arguments.count == 2, let pid = Int32(CommandLine.arguments[1]) else {
FileHandle.standardError.write(Data("usage: ghostty-wincount <pid>\n".utf8))
exit(2)
}
let list = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: Any]] ?? []
let n = list.filter {
($0[kCGWindowOwnerPID as String] as? Int32) == pid &&
($0[kCGWindowLayer as String] as? Int) == 0
}.count
print(n)
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# pool-guard — SessionStart-Hook: prüft direkt nach dem claude-Start, ob die
# Session über claude-sync (Pool-Mechanismus) läuft. claude-sync schreibt die
# Sentinel-Datei in den Projekt-Root und hält sie offen; ein RAW-Start hat
# keine lebende Sentinel dahinter und wird beendet.
set -uo pipefail
sentinel="$CLAUDE_PROJECT_DIR/.ai-control-running"
fail() {
# claude im Elternbaum finden und beenden, dann Banner aufs Terminal.
p=$PPID
while [ -n "$p" ] && [ "$p" -gt 1 ]; do
case "$(ps -o comm= -p "$p")" in
*claude*) kill "$p"; break ;;
esac
p=$(ps -o ppid= -p "$p" | tr -d ' ')
done
sleep 0.3
{
printf '\n\033[1;5;97;41m POOL-GUARD: %s \033[0m\n\n' "$1"
printf '\033[1;31mDiese Session lief außerhalb des Pool-Mechanismus und wurde beendet.\nStart nur über die Ghostty-App bzw. claude-sync.\033[0m\n'
} >/dev/tty
exit 2
}
[ -f "$sentinel" ] || fail "keine Sentinel-Datei ($sentinel)"
pool=$(sed -n 's/^pool=//p' "$sentinel")
pid=$(sed -n 's/^pid=//p' "$sentinel")
[ "$pool" = "${CLAUDE_CONFIG_DIR:-}" ] || fail "Pool-Mismatch: Sentinel=$pool, Session=${CLAUDE_CONFIG_DIR:-<leer>}"
kill -0 "$pid" 2>/dev/null || fail "claude-sync (PID $pid) lebt nicht mehr"
exit 0
+3 -1
View File
@@ -8,4 +8,6 @@
- [cpq Storybook starten](cpq-storybook-start.md) — pnpm --filter @ad-cpq/common-frontend storybook (Port 6006)
- [Git nur Fast-Forward](feedback-git-nur-fast-forward.md) — kein rebase/merge/force; bei Divergenz melden statt auflösen
- [fire-visual Placement-Mode](fire-visual-placement-mode.md) — snap-to-point: DragPoints/DropPoints, processInput-Algorithmus, start_placement_mode-Command, PMI-DropPoints
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Engine kann's, cpq-GroupProcessor (translation-only) plättet die Rotation beim Replay; Punktdaten tragen die Orientierung schon
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Rotation ist im GroupProcessor-Replay angekommen (Edge.rot/computeLayout); fachliche 90°-Klärung läuft noch
- [cpq Configurator Modul-Registry](cpq-configurator-modul-registry.md) — GEKO-76/16: Registry in configurator/modules, Hook nach replay(), Heilung=Ableitung, Overrides=Events
- [Suche nur in Projektordnern](feedback-suche-nur-projektordner.md) — find/grep nie über ~ oder den ganzen Rechner, nur im konkreten erlaubten Verzeichnis
@@ -0,0 +1,19 @@
---
name: cpq-configurator-modul-registry
description: "GEKO-76/16-Konzept: Modul-Registry im cpq-Configurator — Injection über DefaultCommandProcessor, Hook nach replay(), Heilung = Ableitung, Overrides = Events"
metadata:
node_type: memory
type: project
originSessionId: 8d7f85b8-4927-4426-9e04-a8be7def3b5e
---
Konzept-Entscheidung (2026-07-03) für die cpq/geko-Abgrenzung der Schutzzaun-Tickets GEKO-76 (Steher-Logik) und GEKO-16 (Zaun zwischen zwei Punkten):
- **Registry-Ort:** `packages/frontend/src/configurator/` (neues Verzeichnis `modules/`), kein neues Package. cpq bleibt domänen-agnostisch; geko liefert Module (Steher, Zaun-Zug) von außen.
- **Injection:** Konstruktor des `DefaultCommandProcessor`; `Fire3D.vue` nimmt bereits `props.processor` entgegen — die geko-App baut den Processor mit ihren Modulen und reicht ihn durch.
- **Zentraler Hook:** nach `groups.replay(...)` in `DefaultCommandProcessor.replay()` — einziger Engpass für Live, Undo/Redo und Placement; Module können Live/Replay nicht auseinanderlaufen lassen.
- **Semantik:** Selbstheilung (Bauform AG, Höhe) ist reine Ableitung aus der Topologie (`occupiedPmis()`, `Edge.rot`) und landet nie im EventStore; manuelle Detailmenü-Overrides sind echte, undo-bare Events (Modul-eigene Event-Typen).
- **Mit aufzuräumen:** hartkodierte `.name-CS_EIN_*`-Selektoren im `DefaultCommandProcessor` → Placement-Konfiguration des Moduls; Löschen ist im Processor noch unbehandelt → Kaskaden-Hook von Anfang an als Modul-Vertrag entwerfen.
- Ticket-Quellen lokal: `~/claude-projects/robotunits/GEKO-76.xml`, `GEKO-16.xml`; DrawIO-Abgrenzung: `GEKO-76-steher-cpq-geko.drawio`.
Siehe [[cpq-verbau-architektur]], [[cpq-verbau-90grad-steher]].
+9 -17
View File
@@ -1,28 +1,20 @@
---
name: cpq-verbau-90grad-steher
description: "Ziel 90°-Anbau am Steher wie jetzt Links/Rechts — Engine kann es, cpq-GroupProcessor (translation-only) ist der Blocker"
description: "90°-Anbau am Steher: Rotation ist im GroupProcessor-Replay angekommen (Edge.rot, computeLayout) — fachliche 90°-Klärung läuft noch"
metadata:
node_type: memory
type: project
originSessionId: e3d54daa-eb88-474f-9e99-62bfe83821b2
---
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der jetzige Links/Rechts-Verbau.
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der Links/Rechts-Verbau.
**Wer ist der Blocker: wir (cpq), nicht fire-visual.**
**Stand 2026-07-03 (verifiziert im Code):** Der frühere Blocker — translation-only-Replay im cpq-`GroupProcessor` — ist behoben. `packages/frontend/src/configurator/model/GroupProcessor.ts`:
- `Edge` trägt `rot?: Quat` (relative Rotation pro Dock-Kante).
- `computeLayout` komponiert Rotationen über den ungerichteten Kantengraphen (Quaternionen, vorwärts `R_to = R_from ∘ edge.rot`, rückwärts invers); mit Identity-Rotationen reduziert es sich exakt auf den alten translation-only-Solver.
- `dockRotation` leitet `Edge.rot` aus den PMI-Frame-Orientierungen ab (`pmiRotation` liest `properties.rotation` als Euler ZYX Grad → Quat).
- Placement nutzt die `CS_EIN_VORNE`/`CS_EIN_HINTEN`-Selektoren (hart im `DefaultCommandProcessor` — Kandidat für Modul-Konfiguration).
- **fire-visual kann es.** Das Live-Snapping im PlacementMode setzt die Subject-Matrix als `bestMatrix · dragPointTransform⁻¹` — die Rotation des Drag-Punkts ist drin, die Engine richtet also den vollen Frame aus und dreht das Teil beim Snap schon korrekt um 90°. (Vorbehalt: aus der Placement-Matrix-Notiz abgeleitet, nicht aus frischem Quell-Read; bei Bedarf `PlacementMode`-Quelle prüfen, ob DropPoints den Rotations-Frame durchreichen.)
- **cpq verliert es beim Replay.** Nach dem Snap übernehmen wir das Engine-Ergebnis nicht, sondern merken nur „Punkt A trifft Punkt B" (`group-merge` mit zwei PMI-IDs) und rechnen die Anordnung im `GroupProcessor` selbst neu — rein translatorisch. Beim nächsten `setScene` wird die 90°-Drehung auf achsparallel zurückgeplättet.
Die fachliche Klärung der 90°-Frage (GEKO-Seite) läuft laut Marcus noch separat.
**Warum schwer — das Dock-Modell ist translation-only, einachsig, gemeinsame Orientierung. Links/Rechts geht nur, weil nie rotiert wird.** Zu ändernde Schichten:
1. `Edge` = `{from,to,fromPmi,toPmi}` kennt keine Rotation → relative Rotation pro Kante nötig.
2. `computePositions` dockt mit `pos(to)=pos(from)+pmi(from)pmi(to)` (nur Position) → Rotation entlang der Kanten komponieren.
3. `pmiLocal` liest nur `position.value`. **Die Orientierung steckt schon in den Daten** (`properties.rotation`), wird aber ignoriert — und Format passt nicht: 3 Euler-Grad (z. B. `CS_EIN_90_0_0``[0,-0,180]`), deklariert als „quaternion", während `transformMath` `[w,x,y,z]` erwartet.
4. Placement-Mode hartverdrahtet auf `.name-CS_LINKS``.name-CS_RECHTS`; Steher-Anschlüsse leben in der `CS_EIN_*`-Familie → neue Drag-/Drop-Selektoren + Open-End-Logik (`occupiedPmis`).
5. `buildDetachEvent`/`areConnected` nehmen einen 1-achsigen Lauf an (dominante Achse, Links→Rechts-Leseordnung) → 90°-Anbau macht den Verbau zum 2D-Baum, Annahmen brechen.
**Datenlage:** kein plain `CS_VORNE`/`CS_HINTEN` (0 Teile); `CS_LINKS`/`CS_RECHTS` nur auf 12 Teilen; Vorne/Hinten nur in `CS_EIN_*` (rotationscodiert: `CS_EIN_VORNE`, `CS_EIN_HINTEN`, `CS_EIN_STEHER_VORNE/HINTEN`, `CS_EIN_90_0_0` …).
**Machbar:** Erweiterung unseres Modells (Rotation konsumieren + entlang Kanten propagieren + Format-Fix), keine Nachgenerierung der Teile — die nötige Orientierung liefern die Punktdaten bereits.
Siehe [[cpq-verbau-architektur]], [[fire-visual-placement-mode]].
Siehe [[cpq-verbau-architektur]], [[fire-visual-placement-mode]], [[cpq-configurator-modul-registry]].
@@ -0,0 +1,14 @@
---
name: feedback-suche-nur-projektordner
description: "Suchen/find/grep nur innerhalb der erlaubten Projektverzeichnisse, nie über ~ oder den ganzen Rechner"
metadata:
node_type: memory
type: feedback
originSessionId: 8d7f85b8-4927-4426-9e04-a8be7def3b5e
---
Such-Befehle (find, grep, ls …) nur innerhalb der erlaubten Arbeitsverzeichnisse des jeweiligen Projekts ausführen — nicht über `/Users/marcus.hinz`, nicht über breite Pfad-Listen quer durch `~/projects`, nicht über den ganzen Rechner.
**Why:** Marcus erlaubt Zugriff gezielt pro Projektordner; breite Sweeps über Home verletzen diese Abgrenzung, auch wenn sie technisch funktionieren.
**How to apply:** Vor einem Such-Befehl den konkretesten bekannten Pfad wählen (z. B. das genannte Repo oder `~/claude-projects/robotunits`). Liegt das Ziel dort nicht, melden statt den Suchraum eigenmächtig auszuweiten. Gilt zusätzlich zu [[feedback-keine-intent-unterstellung]]s Grundregel „nur suchen, wenn angewiesen".
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+3
View File
@@ -0,0 +1,3 @@
{
"pool": "privateDefault"
}
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+3
View File
@@ -0,0 +1,3 @@
{
"pool": "privateDefault"
}