Projekt-Identität und -Config neu: .ai-control/config.json mit UUID und Name

- Projekt-Config zieht von ai-control.json nach .ai-control/config.json um,
  das Icon liegt daneben und synct mit dem Projekt.
- Jedes Projekt trägt eine UUID (id) und den Anzeigenamen (name) in der
  Config; die UUID ist Registry-Schlüssel und project-Parameter aller
  Commands, der Name reine Darstellung.
- Pool-Zuordnung ist maschinenlokal und liegt in der projects.json-Registry,
  nicht mehr in der syncbaren Projekt-Config.
- open_terminal prüft die ID im Ordner gegen die Registry (verschobene oder
  ersetzte Ordner scheitern laut).
- Import übernimmt eine mitgebrachte ID, legt Arbeitsordner und Archiv-Home
  an und zieht die Archiv-Permission nach.
- Migration beim App-Start: Alt-Datei umziehen, Pool in die Registry,
  Icons aus ~/.config/ai-control/icons ins Projekt, Registry auf UUIDs.
- ProjectConfig reicht unbekannte Keys per serde(flatten) durch den
  Round-Trip (Regressionstest config_roundtrip_erhaelt_fremde_keys).
This commit is contained in:
marcus hinz
2026-07-20 10:55:21 +02:00
parent 6d44f77671
commit b9d006184a
20 changed files with 830 additions and 346 deletions
+12 -5
View File
@@ -11,7 +11,7 @@ use crate::domain::project::{
};
use crate::domain::registry::project_dir;
/// Konfiguriertes Archiv-Home des Projekts (ai-control.json: archiveHome),
/// Konfiguriertes Archiv-Home des Projekts (config.json: archiveHome),
/// Home-expandiert; None, wenn nicht gesetzt.
pub(crate) fn project_archive_home(project: &str) -> Option<PathBuf> {
let paths = Paths::real();
@@ -27,7 +27,7 @@ pub(crate) fn require_archive_home(project: &str) -> Result<PathBuf, String> {
Ok(home)
}
/// Setzt das Archiv-Home: ai-control.json (~-relativ) und ein Eintrag in
/// Setzt das Archiv-Home: config.json (~-relativ) und ein Eintrag in
/// permissions.additionalDirectories + Edit der Projekt-settings.json, damit
/// claude das Archiv später lesen/scannen darf.
pub(crate) fn set_project_archive_home(project: &str, dir: &str) -> Result<(), String> {
@@ -53,7 +53,12 @@ pub(crate) fn set_project_archive_home(project: &str, dir: &str) -> Result<(), S
}
/// Trägt den Archiv-Ordner idempotent in additionalDirectories + Edit-Allow ein.
fn add_archive_permission(paths: &Paths, project: &str, dir: &str) -> Result<(), String> {
/// Auch beim Projekt-Import im Einsatz (mitgebrachtes archiveHome).
pub(crate) fn add_archive_permission(
paths: &Paths,
project: &str,
dir: &str,
) -> Result<(), String> {
let sp = settings_path(&project_dir(paths, project)?);
let mut v: serde_json::Value = if sp.is_file() {
serde_json::from_str(&fs::read_to_string(&sp).map_err(|e| format!("{}: {e}", sp.display()))?)
@@ -90,7 +95,7 @@ fn add_archive_permission(paths: &Paths, project: &str, dir: &str) -> Result<(),
fs::write(&sp, raw + "\n").map_err(|e| format!("{}: {e}", sp.display()))
}
/// Wählt das Archiv ab: archiveHome aus der ai-control.json entfernen und die
/// Wählt das Archiv ab: archiveHome aus der config.json entfernen und die
/// beim Setzen eingetragenen Rechte (additionalDirectories + Edit-Allow) aus
/// der Projekt-settings.json zurücknehmen. Der Ordner selbst bleibt liegen.
pub(crate) fn clear_project_archive_home(project: &str) -> Result<(), String> {
@@ -171,7 +176,9 @@ pub(crate) fn archive_panel_content(
let (stamp, iso) = utc_stamp(secs);
let title = first_line(&text);
let path = free_path(&dir, &stamp, &slugify(&title));
let doc = format!("{}{}\n", frontmatter(&title, project, &iso, meta), text.trim_end());
// Frontmatter trägt den Anzeigenamen, nicht die Projekt-ID.
let name = crate::domain::project::display_name_in(&Paths::real(), project)?;
let doc = format!("{}{}\n", frontmatter(&title, &name, &iso, meta), text.trim_end());
fs::write(&path, doc).map_err(|e| format!("{}: {e}", path.display()))?;
Ok(path)
}