kimal3 0.1.5 β†’ 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
- import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
- import { dirname, join, resolve } from "node:path";
3
- import { feld, ruf } from "../api.js";
4
- import { schlussFehler } from "../fehler.js";
1
+ import { resolve } from "node:path";
2
+ import { dateienSchreiben, vorlageHolen, werkbankOrdner, istUnsichererOrdnername, } from "../arbeitsplatz-dateien.js";
3
+ import { werkbankOrdnerAus, werkbankUmbenennen } from "../werkbank-name.js";
5
4
  /**
6
5
  * Holt die KI-Arbeitsplatz-Vorlage vom Server und legt sie lokal an --
7
6
  * `CLAUDE.md`, Takt-Befehle, die Werkbank fuer eigene Projekte.
@@ -13,272 +12,249 @@ import { schlussFehler } from "../fehler.js";
13
12
  * Befehl die Struktur selbst zusammenbauen, gaebe es zwei Wahrheiten ueber
14
13
  * denselben Ordner, und sie liefen beim naechsten Aendern der Vorlage
15
14
  * auseinander.
15
+ *
16
+ * πŸ“Œ **Einmal pro FIRMA.** Er legt die ganze Ordnerfamilie an, also auch die
17
+ * Firmenebene (`kontext/` mit Marke, Systemen, Sicherheit). Wer nur eine
18
+ * weitere WERKBANK in einer bestehenden Firma braucht, nimmt
19
+ * `kimal3 werkbank neu` -- sonst entsteht eine zweite Firma im Bauch der
20
+ * ersten (gemessen am 2026-08-20, s. `befehle/werkbank.ts`).
21
+ *
22
+ * πŸ” Die Schreib-Maschinerie wohnt seit dem 2026-08-20 in
23
+ * `../arbeitsplatz-dateien.ts`, weil `werkbank neu` dieselbe braucht.
16
24
  */
17
25
  const HILFE = `
18
- kimal3 arbeitsplatz neu [ordnername]
26
+ kimal3 arbeitsplatz neu [dein-vorname]
19
27
 
20
- Holt deine KI-Arbeitsplatz-Vorlage und legt sie hier als Ordner an.
28
+ Holt deine KI-Arbeitsplatz-Vorlage und legt sie hier als Ordner an --
29
+ die ganze Familie: Firmenebene, Werkbank, Beispielprojekt.
21
30
 
22
- ordnername Optional. Ohne Angabe heisst der Ordner wie in der Vorlage.
31
+ Der Ordner heisst IMMER "KI-Arbeitsplatz". Mehrere Firmen trennst du
32
+ ueber den Ordner darueber, z. B. Kunden/ARNO/KI-Arbeitsplatz.
33
+
34
+ dein-vorname Optional. Er benennt die WERKBANK darin -- aus "sabine"
35
+ wird "werkbank-sabine". Ohne Angabe steht dort der Name,
36
+ den der Server aus deiner Anmelde-Adresse gebildet hat.
23
37
 
24
38
  πŸ”΄ Bricht ab, wenn der Zielordner schon existiert und NICHT leer ist --
25
39
  eigene Arbeit wird dadurch nie versehentlich ueberschrieben.
40
+
41
+ Du hast schon einen Firmen-Ordner und brauchst nur eine eigene Werkbank?
42
+ kimal3 werkbank neu <dein-vorname>
26
43
  `;
44
+ /*
45
+ πŸ”΄ **Der Rat in den Absagen hat sich am 2026-08-20 UMGEDREHT** -- und das ist
46
+ der Kern der Umbenennung, nicht ihr Nebeneffekt.
47
+
48
+ Vorher hiess er `kimal3 arbeitsplatz neu <anderer-name>`: "Der Ordner ist
49
+ belegt? Nimm einen anderen Namen." Genau diesen Rat hat Dennis am selben Tag
50
+ im Rollenspiel befolgt -- und bekam eine ZWEITE Firma im Bauch der ersten, mit
51
+ eigener `firma.md`, eigener `marke.md`, ohne jede Fehlermeldung. *Die Absage
52
+ schlug den Weg vor, gegen den `werkbank neu` gebaut werden musste.*
53
+
54
+ Jetzt heisst der oberste Ordner immer gleich. Damit KANN dieser Befehl keinen
55
+ zweiten Ordnernamen mehr vorschlagen -- und der einzige verbliebene Rat ist
56
+ der richtige.
57
+ */
58
+ /**
59
+ * Was die Absage zum Weitermachen raet β€” **mit dem `cd`, das dazugehoert**.
60
+ *
61
+ * πŸ”΄ **Review-Runde 1 zu PR #25, und es ist dieselbe Fehlerklasse, gegen die
62
+ * dieser PR gebaut wurde.** Hier stand nur der Befehl:
63
+ *
64
+ * Steht dein Arbeitsplatz schon und dir fehlt nur die eigene Werkbank?
65
+ * kimal3 werkbank neu <dein-vorname>
66
+ *
67
+ * Wer diese Absage liest, steht **ausserhalb** des Ordners β€” genau deshalb hat
68
+ * er sie ja bekommen (β€žder Ordner gibt es schon"). Fuehrt er den Rat dort aus,
69
+ * findet `werkbank neu` kein `kontext/` daneben und bricht ab mit *β€žHier steht
70
+ * kein Firmen-Ordner"*. Der Rat war richtig und der Ort falsch.
71
+ * *Ein Befehl ohne den Ort, an dem er laeuft, ist ein halber Rat.*
72
+ *
73
+ * ⚠️ Der Ordnername wird ABGELEITET, nicht getippt: Er kommt als `ordnerName`
74
+ * herein (aus `daten.wurzel`, also vom Server). Stuende hier `cd KI-Arbeitsplatz`
75
+ * fest im Text, waere das eine zweite Wahrheit ueber den Aufbau β€” dieselbe, die
76
+ * Runde 3 zu PR #24 auf `/pro` gefunden hat.
77
+ */
78
+ function hinweisFuer(ordnerName) {
79
+ const ziel = ordnerName.includes(" ") ? `"${ordnerName}"` : ordnerName;
80
+ return (" Steht dein Arbeitsplatz schon und dir fehlt nur die eigene Werkbank?\n" +
81
+ " Dann geh HINEIN und leg sie dort an:\n" +
82
+ ` cd ${ziel}\n` +
83
+ " kimal3 werkbank neu <dein-vorname>");
84
+ }
27
85
  export async function arbeitsplatzNeu(argv) {
28
- // ⚠️ VOR dem Lesen des Ordnernamens, wie bei `projekt neu` und
29
- // `firma daten`: `--help` ist ein Schalter, kein Wert, der geprueft
30
- // werden muesste.
31
86
  if (argv.includes("--help") || argv.includes("-h")) {
32
87
  console.log(HILFE);
33
88
  return 0;
34
89
  }
35
- const ordnerArg = argv[0];
36
- // πŸ”΄ R2-2: Ein LEERES Argument ist kein "kein Argument" -- `?? daten.wurzel`
37
- // greift nur bei `undefined`, ein leerer String bleibt stehen und `resolve("")`
38
- // ist das aktuelle Verzeichnis. Wer "Ordner anlegen, hinein wechseln, Befehl
39
- // tippen" macht, packt die Vorlage sonst versehentlich ins cwd.
40
- if (ordnerArg !== undefined && ordnerArg.trim() === "") {
41
- console.error(`Ein leerer Ordnername ist kein gueltiger Name.\n${HILFE}`);
90
+ /*
91
+ Dieselben drei Absagen wie bei `werkbank neu` -- bewusst wortgleich: Wer den
92
+ einen Befehl kennt, soll beim anderen nichts Neues lernen muessen.
93
+
94
+ ⚠️ Sie sprechen jetzt von einem NAMEN, nicht von einem Ordnernamen. Das ist
95
+ keine Kosmetik: Wer nach der alten Hilfe `kimal3 arbeitsplatz neu ARNO-KI`
96
+ tippt, bekommt keinen Fehler, sondern eine Werkbank namens
97
+ `werkbank-arno-ki`. Diese Meldungen und die Hilfe sind die einzigen Stellen,
98
+ an denen er das erfaehrt, bevor es passiert.
99
+ */
100
+ const nameArg = argv[0];
101
+ if (nameArg !== undefined && nameArg.trim() === "") {
102
+ console.error(`Ein leerer Name ist kein gueltiger Name.\n${HILFE}`);
42
103
  return 1;
43
104
  }
44
- if (ordnerArg !== undefined && ordnerArg.startsWith("--")) {
45
- console.error(`Die Option β€ž${ordnerArg}" kenne ich nicht.\n${HILFE}`);
105
+ if (nameArg !== undefined && nameArg.startsWith("--")) {
106
+ console.error(`Die Option β€ž${nameArg}" kenne ich nicht.\n${HILFE}`);
46
107
  return 1;
47
108
  }
48
109
  if (argv.length > 1) {
49
- console.error(`Mit β€ž${argv[1]}" kann ich hier nichts anfangen -- nur EIN Ordnername ist erlaubt.\n${HILFE}`);
110
+ /*
111
+ πŸ”΄ **Der Vorschlag darf keine Optionen einsammeln** (Review-Runde 5 zu
112
+ PR #25). Hier stand `argv.join("-")` -- aus `arbeitsplatz neu sabine --force`
113
+ wurde damit der Rat `arbeitsplatz neu sabine---force`. Die `--`-Wache ein paar
114
+ Zeilen hoeher sieht nur `argv[0]`.
115
+ *Ein Rat, der ein falsches Kommando erzeugt, ist schlimmer als keiner* --
116
+ dieselbe Klasse wie die Absagen aus Runde 1 und 2, nur kleiner.
117
+
118
+ Deshalb: Sieht irgendein Wort wie eine Option aus, gibt es gar keinen
119
+ Vorschlag. Ein Mensch, der `--force` tippt, sucht keine Schreibhilfe fuer
120
+ seinen Vornamen.
121
+ */
122
+ const woerter = argv.filter((w) => !w.startsWith("-"));
123
+ const vorschlag = woerter.length === argv.length && woerter.length > 1
124
+ ? `Besteht dein Name aus mehreren Woertern? Dann schreib ihn zusammen:\n` +
125
+ ` kimal3 arbeitsplatz neu ${woerter.join("-")}\n`
126
+ : "";
127
+ console.error(`Mit β€ž${argv[1]}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n` +
128
+ `${vorschlag}${HILFE}`);
50
129
  return 1;
51
130
  }
52
- const antwort = await ruf("/api/cli/arbeitsplatz", { methode: "GET", mitAusweis: true });
53
- if (antwort.status === 200) {
54
- // πŸ”΄ Ein `200` mit unlesbarem Rumpf ist KEIN Erfolg -- dieselbe Wache wie
55
- // bei `firma daten`. `ruf` verschluckt Parse-Fehler zu `null`.
56
- const daten = alsArbeitsplatzAntwort(antwort.koerper);
57
- if (daten === null) {
58
- console.error("Die Antwort war nicht lesbar. Bitte gleich noch einmal.");
59
- return 3;
131
+ /*
132
+ Der Name wird UMGESCHRIEBEN, nicht abgelehnt (s. `werkbankOrdnerAus`) --
133
+ "Sabine Mueller" ist ein gueltiger Name, nur kein gueltiger Ordner. `null`
134
+ heisst: Nach dem Umschreiben war nichts mehr uebrig (etwa bei "!!!"). Das ist
135
+ HIER ein Fehler, weil ein Mensch etwas getippt hat und eine Antwort verdient
136
+ -- der Server schweigt im selben Fall und bleibt beim Vordruck.
137
+ */
138
+ let gewuenschteWerkbank = null;
139
+ if (nameArg !== undefined) {
140
+ gewuenschteWerkbank = werkbankOrdnerAus(nameArg);
141
+ if (gewuenschteWerkbank === null) {
142
+ console.error(`Aus β€ž${nameArg}" kann ich keinen Ordnernamen machen -- es bleiben nur`);
143
+ console.error("Zeichen uebrig, die in keinem Ordnernamen stehen duerfen.");
144
+ console.error("");
145
+ console.error(" Nimm deinen Vornamen: kimal3 arbeitsplatz neu sabine");
146
+ return 1;
60
147
  }
61
- return dateienAnlegen(daten, ordnerArg);
62
- }
63
- const meldung = feld(antwort.koerper, "meldung") ?? "Das hat nicht geklappt.";
64
- // 402 VOR 401: Beide heissen "kommst hier nicht rein", verlangen aber
65
- // Gegenteiliges. Wer sie zusammenwirft, raet `kimal3 login` -- und der
66
- // meldet sich dann erfolgreich an, ohne dass es etwas aendert.
67
- if (antwort.status === 402) {
68
- console.error(meldung);
69
- return 4;
70
148
  }
71
- if (antwort.status === 401) {
72
- console.error(`${meldung} Bitte \`kimal3 login\` ausfΓΌhren.`);
73
- return 2;
74
- }
75
- // πŸ”‘ Den Rest entscheidet `schlussFehler` fuer alle Befehle gemeinsam --
76
- // insbesondere die `404` (die App kennt die Route noch nicht) und `429`/
77
- // `408` (ueberlastet, wiederholbar).
78
- const schluss = schlussFehler(antwort.status, meldung);
79
- console.error(schluss.text);
80
- return schluss.wert;
81
- }
82
- /** Prueft die Antwort-Form, ohne `any` -- eine falsche Form ist unlesbar, keine Ausnahme. */
83
- function alsArbeitsplatzAntwort(koerper) {
84
- if (!koerper || typeof koerper !== "object")
85
- return null;
86
- const wurzel = koerper.wurzel;
87
- const dateienRoh = koerper.dateien;
88
- if (typeof wurzel !== "string" || wurzel.trim() === "")
89
- return null;
90
- if (!Array.isArray(dateienRoh))
91
- return null;
92
- const dateien = [];
93
- for (const eintrag of dateienRoh) {
94
- if (!eintrag || typeof eintrag !== "object")
95
- return null;
96
- const pfad = eintrag.pfad;
97
- const inhalt = eintrag.inhalt;
98
- if (typeof pfad !== "string" || pfad.trim() === "")
99
- return null;
100
- if (typeof inhalt !== "string")
101
- return null;
102
- dateien.push({ pfad, inhalt });
103
- }
104
- return { wurzel, dateien };
105
- }
106
- /**
107
- * πŸ”΄ **Nie ueberschreiben** -- die wichtigste Eigenschaft dieses Befehls.
108
- * Angelegt wird nur, wenn am Zielort NICHTS liegt oder ein LEERER Ordner.
109
- *
110
- * Genauer, weil hier zwei verschiedene Absagen stecken:
111
- * - Zielort ist eine **Datei** (auch eine leere): Abbruch. An einer Datei kann
112
- * kein Ordner entstehen, ohne sie zu loeschen -- und geloescht wird nichts.
113
- * - Zielort ist ein **Ordner mit Inhalt**: Abbruch.
114
- * - Zielort ist ein **leerer Ordner** oder gibt es nicht: los geht's.
115
- *
116
- * ⚠️ **Hier stand "eine leere Datei ... ist kein Hindernis"** (Review-Runde 4 zu
117
- * PR #24) -- das war nie wahr. Wer `touch Mein-KI-Arbeitsplatz` gemacht hat oder
118
- * wessen Editor eine leere Datei dieses Namens liegen liess, bekam den Abbruch,
119
- * waehrend der Kommentar Durchlass versprach.
120
- * *Die Prueffrage aus CLAUDE.md: Beschreibt das ein Verhalten, das jemand WILL --
121
- * oder nur eines, das PASSIERT?* Hier wollte niemand das Beschriebene; der Code
122
- * war richtig und der Satz darueber falsch.
123
- */
124
- function dateienAnlegen(daten, ordnerArg) {
125
- // πŸ”΄ B-7: eine leere Lieferung ist ein FEHLERFALL, kein Erfolg mit null
126
- // Dateien -- sonst meldet der Befehl "βœ“ 0 Dateien angelegt." und legt dabei
127
- // nicht einmal einen Ordner an. Derselbe Rueckgabewert wie bei anderen
128
- // unlesbaren Antworten (3): der Nutzer hat hier nichts falsch gemacht.
129
- if (daten.dateien.length === 0) {
130
- console.error("Die Antwort enthielt keine einzige Datei -- das ist kein gueltiges Ergebnis.");
131
- console.error("Bitte gleich noch einmal versuchen.");
132
- return 3;
133
- }
134
- const ordnerName = ordnerArg ?? daten.wurzel;
149
+ const holen = await vorlageHolen();
150
+ if (!holen.ok)
151
+ return holen.wert;
152
+ const daten = holen.daten;
135
153
  // πŸ”΄ R2-1: `daten.wurzel` kommt genauso ROH vom Server wie jeder `datei.pfad`
136
154
  // -- dieselbe Gefahr (manipulierte Antwort, z. B. ueber ein umgebogenes
137
- // `KIMAL3_URL`), dieselbe Bremse. Nur wenn KEIN eigener Ordnername uebergeben
138
- // wurde, fliesst `daten.wurzel` ueberhaupt in den Zielpfad ein -- aber geprueft
139
- // wird sie immer, damit hier nie zwischen "gerade unbenutzt" und "sicher"
140
- // verwechselt wird.
141
- if (istUnsichererPfad(daten.wurzel)) {
155
+ // `KIMAL3_URL`), dieselbe Bremse.
156
+ //
157
+ // ⚠️ **Hier stand: "Nur wenn KEIN eigener Ordnername uebergeben wurde, fliesst
158
+ // `daten.wurzel` ueberhaupt in den Zielpfad ein -- aber geprueft wird sie
159
+ // immer."** Das beschrieb die Welt vor dem 2026-08-20, in der ein Argument den
160
+ // Zielordner benennen konnte. Seit der Umbenennung heisst er IMMER wie
161
+ // `daten.wurzel`; die Pruefung ist damit nicht mehr die Vorsicht fuer einen
162
+ // selten benutzten Zweig, sondern der einzige Schutz, den dieser Wert hat.
163
+ // *Ein Kommentar, der die Wichtigkeit einer Pruefung kleinredet, ist gefaehr-
164
+ // licher als gar keiner -- er laedt zum Loeschen ein.*
165
+ // πŸ”΄ `istUnsichererOrdnername`, nicht `istUnsichererPfad` (Review-Runde 2, als
166
+ // Zwilling des Befunds in `werkbank.ts` β€” dort gemeldet, hier selbst gesucht).
167
+ // `alsVorlageAntwort` faengt eine LEERE `wurzel` schon ab, aber nicht `"."`:
168
+ // `resolve(".")` ist das aktuelle Verzeichnis, und in einem leeren Ordner
169
+ // waere die ganze Familie einfach dorthin gefallen.
170
+ if (istUnsichererOrdnername(daten.wurzel)) {
142
171
  console.error(`Die Antwort enthaelt einen unsicheren Ordnernamen: β€ž${daten.wurzel}". Bitte melden.`);
143
172
  return 3;
144
173
  }
145
- const ziel = resolve(ordnerName);
146
174
  /*
147
- πŸ”΄ Diese drei Aufrufe brauchen ein eigenes Netz (Review-Runde 3). Sie lagen
148
- ausserhalb jedes `try`: Ist der Zielordner vorhanden, aber nicht lesbar
149
- (fremder Besitzer, gesperrte Netzfreigabe -- EACCES), flog `readdirSync`
150
- bis in den Auffang-Block von `kimal3.ts` durch. Der druckt dann
151
- β€žkimal3 ist gerade nicht erreichbar." und gibt 3 zurueck.
175
+ πŸ”‘ Der Zielordner ist IMMER `daten.wurzel` ("KI-Arbeitsplatz") und kommt damit
176
+ vom Server statt aus der Eingabe. Die Pfad-Pruefung darueber ist deshalb
177
+ nicht mehr die Vorsicht fuer einen selten benutzten Zweig, sondern der
178
+ einzige Schutz, den dieser Wert ueberhaupt hat.
152
179
 
153
- Das ist gleich doppelt falsch: Der Mensch sucht den Fehler beim Server,
154
- obwohl er auf seiner eigenen Platte sitzt -- und 3 heisst fuer Claude laut
155
- SKILL.md β€žgleich noch einmal versuchen", was hier nie helfen kann.
156
- *Ein Fehlerfall darf nie denselben Namen tragen wie ein anderer.*
180
+ Der Name des Menschen wirkt eine Ebene tiefer, auf die Werkbank. Der Server
181
+ hat sie schon benannt (aus der Anmelde-Adresse); ein mitgegebener Name
182
+ sticht das.
157
183
  */
158
- try {
159
- if (existsSync(ziel)) {
160
- if (!statSync(ziel).isDirectory()) {
161
- console.error(`β€ž${ordnerName}" gibt es schon -- als DATEI, nicht als Ordner.`);
162
- console.error("Gib einen anderen Namen an: kimal3 arbeitsplatz neu <anderer-name>");
163
- return 1;
164
- }
165
- /*
166
- ⚠️ **Betriebssystem-Krimskrams zaehlt nicht als Inhalt** (Review-Runde 5).
167
- `readdirSync` zaehlte vorher ALLES. Wer den Ordner angelegt und ihn nur
168
- einmal im Finder (`.DS_Store`) oder Explorer (`desktop.ini`) geoeffnet
169
- hat, bekam "gibt es schon und ist nicht leer" fuer einen Ordner, der fuer
170
- ihn leer AUSSIEHT -- und keine Chance zu verstehen, warum. *Ein Grund, den
171
- der Mensch nicht sehen kann, ist keine Begruendung, sondern eine Absage.*
172
-
173
- Bewusst eine geschlossene Liste und kein "alles, was mit Punkt anfaengt":
174
- Eine `.env` oder ein `.git` IST Inhalt, und den fassen wir nicht an.
175
- */
176
- const OS_KRIMSKRAMS = [".DS_Store", "desktop.ini", "Thumbs.db"];
177
- const echterInhalt = readdirSync(ziel).filter((n) => !OS_KRIMSKRAMS.includes(n));
178
- if (echterInhalt.length > 0) {
179
- console.error(`Der Ordner β€ž${ordnerName}" gibt es schon und ist nicht leer.`);
180
- console.error("Ich fasse ihn nicht an -- so geht nie versehentlich eigene Arbeit verloren.");
181
- console.error("Gib einen anderen Namen an: kimal3 arbeitsplatz neu <anderer-name>");
182
- return 1;
183
- }
184
- }
185
- }
186
- catch (fehler) {
187
- console.error(`Ich komme an den Ordner β€ž${ordnerName}" nicht heran.`);
188
- console.error(`Grund: ${fehler instanceof Error ? fehler.message : String(fehler)}`);
189
- console.error("Das liegt an diesem Rechner, nicht am Server -- Rechte pruefen oder");
190
- console.error("einen anderen Namen angeben: kimal3 arbeitsplatz neu <anderer-name>");
191
- return 1;
192
- }
193
- // πŸ”΄ B-6: ALLE Pfade pruefen, BEVOR die erste Datei geschrieben wird. Wuerde
194
- // erst waehrend des Schreibens geprueft, koennte ein unsicherer Pfad mitten
195
- // in der Liste einen halb beschriebenen Ordner hinterlassen -- der naechste
196
- // Versuch liefe dann in die "nicht leer"-Absage oben, ohne dass der Nutzer
197
- // je erfaehrt, dass der Ordner unvollstaendig ist.
198
- for (const datei of daten.dateien) {
199
- if (istUnsichererPfad(datei.pfad)) {
200
- console.error(`Die Antwort enthaelt einen unsicheren Pfad: β€ž${datei.pfad}". Bitte melden.`);
201
- return 3;
202
- }
203
- }
204
- try {
205
- for (const datei of daten.dateien) {
206
- const ort = join(ziel, ...datei.pfad.split("/"));
207
- mkdirSync(dirname(ort), { recursive: true });
208
- // πŸ”‘ B-2-Folge: Platzhalter wie [DATUM] fuellt seit PR #24 der SERVER
209
- // (`src/lib/arbeitsplatz/ausliefern.ts`), fuer beide Auslieferungswege
210
- // gleichzeitig -- Download auf `/pro` UND diese CLI. Wuerde die CLI
211
- // zusaetzlich selbst ersetzen, gaebe es wieder zwei Wahrheiten ueber
212
- // denselben Inhalt (genau das war der urspruengliche Befund). Die CLI
213
- // schreibt den Inhalt deshalb hier UNVERAENDERT auf die Platte.
214
- writeFileSync(ort, datei.inhalt, "utf8");
215
- }
184
+ const werkbankVomServer = werkbankOrdner(daten.dateien);
185
+ /*
186
+ πŸ”΄ **Review-Runde 2 zu PR #25: ein getippter Name darf nicht still verfallen.**
187
+ Hier stand `gewuenschteWerkbank !== null && werkbankVomServer !== null ? …
188
+ : daten.dateien` β€” enthielt die Lieferung keine Werkbank, bekam der Mensch
189
+ den Vordruck-Namen UND eine Erfolgsmeldung, obwohl er ausdruecklich `sabine`
190
+ getippt hatte.
191
+
192
+ Die Schwesterdatei behandelt dieselbe Lage laengst als Fehler (β€žIn der
193
+ Vorlage steckt kein Werkbank-Ordner … Bitte melden", Rueckgabewert 3).
194
+ *Zwei Wege, dieselbe Wirklichkeit, verschiedene Antworten* β€” genau die
195
+ Asymmetrie, die dieser PR an drei anderen Stellen schon geschlossen hat.
196
+ */
197
+ /*
198
+ πŸ”΄ **Beide Zweige enden gleich** (Review-Runde 5 zu PR #25). Runde 2 hat die
199
+ Asymmetrie zur Schwesterdatei nur fuer den Fall geschlossen, in dem ein Name
200
+ GETIPPT wurde; ohne Namen wurde weiter eine Warnung gedruckt und dann mit
201
+ `0` erfolgreich beendet.
202
+
203
+ Warum das schlimmer ist, als es klingt: Der Weg-Hinweis am Ende faellt dann
204
+ auf den Wurzelordner zurueck und schickt den Menschen mit `cd
205
+ KI-Arbeitsplatz` genau dorthin, wo die ausgelieferte Firmen-`CLAUDE.md`
206
+ ausdruecklich sagt *β€žDu arbeitest nie hier oben"*. Eine Lieferung ohne
207
+ Werkbank ist kein Erfolg mit Randnotiz -- sie ist kaputt.
208
+
209
+ ⚠️ Heute unerreichbar (`check:arbeitsplatz` Pruefung 10 erzwingt genau eine
210
+ Werkbank). Das ist ein Grund, ruhig zu bleiben, kein Grund, zwei Antworten
211
+ auf dieselbe Wirklichkeit zu geben.
212
+ */
213
+ if (werkbankVomServer === null) {
214
+ console.error("In der Vorlage steckt kein Werkbank-Ordner -- damit fehlt der Teil,");
215
+ console.error("in dem du arbeiten wuerdest. Das liegt nicht an dir. Bitte melden.");
216
+ return 3;
216
217
  }
217
- catch (fehler) {
218
- /*
219
- πŸ”΄ **Hier stand `return 3` (Review-Runde 5 zu PR #24).** `3` heisst laut
220
- `cli/skill/SKILL.md` "voruebergehende Stoerung -- gleich noch einmal
221
- versuchen". Ein voller Datentraeger oder ein EACCES ist aber keine
222
- Server-Stoerung, und der zweite Versuch kann gar nicht mehr gutgehen:
223
- Der Zielordner EXISTIERT jetzt und ist nicht leer, also faellt der Lauf
224
- oben in die Nicht-leer-Wache und endet mit `1` = "Eingabe passt nicht".
225
- Claude sagt dem Menschen dann, sein Ordnername sei falsch -- und der
226
- wahre Grund (Platte voll) kommt nie an.
227
-
228
- Dieselbe Begruendung steht 40 Zeilen weiter oben schon einmal, beim
229
- `readdirSync`-catch. *Ein Fehler, der einmal verstanden und
230
- aufgeschrieben wurde, ist im Nachbar-catch trotzdem wieder neu.*
231
- */
232
- console.error(`Schreiben fehlgeschlagen: ${fehler instanceof Error ? fehler.message : String(fehler)}`);
233
- console.error(`Der Ordner β€ž${ordnerName}" ist dadurch UNVOLLSTAENDIG -- er darf geloescht werden.`);
234
- console.error("Das liegt an diesem Rechner, nicht am Server -- Platz und Rechte pruefen.");
235
- return 1;
218
+ const dateien = gewuenschteWerkbank !== null
219
+ ? werkbankUmbenennen(daten.dateien, werkbankVomServer, gewuenschteWerkbank)
220
+ : daten.dateien;
221
+ const ordnerName = daten.wurzel;
222
+ const wert = dateienSchreiben(dateien, ordnerName, hinweisFuer(ordnerName));
223
+ if (wert !== 0)
224
+ return wert;
225
+ // ⚠️ Der ABSOLUTE Pfad, nicht der eingegebene Name. Beim Umzug der
226
+ // Schreib-Maschinerie (2026-08-20) war das kurz ein relativer Name -- ein
227
+ // Rueckschritt: Der Mensch soll sehen, WO der Ordner gelandet ist, und genau
228
+ // das ist der Grund, warum hier `resolve()` steht und nicht `ordnerName`.
229
+ console.log(`βœ“ ${dateien.length} ${dateien.length === 1 ? "Datei" : "Dateien"} angelegt.`);
230
+ console.log(` ${resolve(ordnerName)}`);
231
+ /*
232
+ πŸ”΄ **Der Erfolgsfall muss sagen, was das Argument getan hat** (Review-Runde 5
233
+ zu PR #25). Das Argument hat am 2026-08-20 still seine Bedeutung geaendert:
234
+ In `0.1.5` -- veroeffentlicht, in Gebrauch -- war es der ORDNERNAME. Wer nach
235
+ der alten Hilfe `kimal3 arbeitsplatz neu ARNO-KI` tippt, bekommt heute keinen
236
+ Fehler, sondern einen Ordner `KI-Arbeitsplatz/` mit einer Werkbank namens
237
+ `werkbank-arno-ki`.
238
+
239
+ ⚠️ Im Code stand als Trost, die Absagen und die Hilfe seien β€ždie einzigen
240
+ Stellen, an denen er das erfaehrt" -- aber die Absagen feuern nur im
241
+ FEHLERFALL. Auf dem Erfolgsweg erfuhr er gar nichts.
242
+ *Eine Bedeutung, die sich still aendert, muss sich laut melden -- und zwar
243
+ dort, wo es geklappt hat, nicht nur dort, wo es schiefging.*
244
+ */
245
+ if (gewuenschteWerkbank !== null) {
246
+ console.log(` Deine Werkbank darin heisst β€ž${gewuenschteWerkbank}".`);
236
247
  }
237
- console.log(`βœ“ ${daten.dateien.length} ${daten.dateien.length === 1 ? "Datei" : "Dateien"} angelegt.`);
238
- console.log(` ${ziel}`);
239
248
  console.log("");
240
249
  console.log(" Weiter geht's:");
241
- const werkbank = werkbankOrdner(daten.dateien);
250
+ // ⚠️ NACH dem Umbenennen ableiten, nicht davor -- sonst zeigte der Weg-Hinweis
251
+ // in die Werkbank, die der Server geschickt hat, statt in die, die auf der
252
+ // Platte liegt. *Eine Ableitung, die eine Zeile zu frueh gelesen wird, fuehrt
253
+ // zuverlaessig ins Leere.*
254
+ const werkbank = werkbankOrdner(dateien);
242
255
  const cdZiel = werkbank !== null ? `${ordnerName}/${werkbank}` : ordnerName;
243
256
  console.log(` cd ${cdZiel.includes(" ") ? `"${cdZiel}"` : cdZiel}`);
244
257
  console.log(" claude");
245
258
  console.log(' dann "Lass uns einrichten" tippen');
246
259
  return 0;
247
260
  }
248
- /**
249
- * πŸ”΄ R2-3: Der Name der Werkbank stand hier vorher FEST getippt
250
- * (`meine-werkbank`) -- eine zweite Wahrheit ueber den Aufbau der Vorlage,
251
- * genau das, wogegen der Kopfkommentar dieser Datei argumentiert. Wird der
252
- * Werkbank-Ordner in `arbeitsplatz/vorlage/` umbenannt, bleibt
253
- * `check:arbeitsplatz` gruen -- die CLI wuerde weiter in einen Ordner
254
- * schicken, den es nicht mehr gibt.
255
- *
256
- * Stattdessen wird der Name aus der GELIEFERTEN Dateiliste abgeleitet: Der
257
- * Werkbank-Ordner ist der Unterordner mit einer EIGENEN `CLAUDE.md` direkt
258
- * darin (Pfad `<ordner>/CLAUDE.md`, genau zwei Teile) -- das ist die Stelle,
259
- * an die `README.md` und diese Meldung Menschen schicken, um dort `claude`
260
- * zu starten. Aendert sich der Name in der Vorlage, aendert sich diese
261
- * Meldung automatisch mit.
262
- */
263
- function werkbankOrdner(dateien) {
264
- for (const datei of dateien) {
265
- const teile = datei.pfad.split("/");
266
- if (teile.length === 2 && teile[1] === "CLAUDE.md") {
267
- return teile[0];
268
- }
269
- }
270
- return null;
271
- }
272
- /**
273
- * Ein `..`-Segment oder ein absoluter Pfad duerften das Ziel verlassen.
274
- *
275
- * πŸ”΄ B-8: Zerlegt an `/` UND `\` -- eine fehlerhafte oder manipulierte
276
- * Server-Antwort koennte sonst mit `..\..\boese.md` durchkommen. `join()`
277
- * trennt unter Windows auch am Backslash, dieser Schutz muss also beide
278
- * Trenner kennen wie `join()` selbst.
279
- */
280
- function istUnsichererPfad(pfad) {
281
- if (pfad.startsWith("/") || /^[a-zA-Z]:/.test(pfad))
282
- return true;
283
- return pfad.split(/[\\/]/).some((teil) => teil === "..");
284
- }