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.
@@ -0,0 +1,389 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { dateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
4
+ import { werkbankOrdnerAus, werkbankUmbenennen } from "../werkbank-name.js";
5
+ /**
6
+ * Legt **nur eine Werkbank** an — in einem Firmen-Ordner, der schon steht.
7
+ *
8
+ * 🔴 **Warum es diesen Befehl gibt** (gefunden von Dennis am 2026-08-20, im
9
+ * Rollenspiel als Angestellter einer Kundenfirma — nicht von einem Prüfer, und
10
+ * nicht in fünf Review-Runden). Gemessen:
11
+ *
12
+ * Im bestehenden Firmen-Ordner nochmal `kimal3 arbeitsplatz neu`
13
+ * → „Der Ordner gibt es schon und ist nicht leer. Gib einen anderen
14
+ * Namen an."
15
+ * Also gibt er einen an: `kimal3 arbeitsplatz neu werkbank-sabine`
16
+ * → ✓ 22 Dateien, darunter `werkbank-sabine/kontext/`
17
+ *
18
+ * Er bekommt **keinen Fehler**, sondern eine ZWEITE Firma im Bauch der ersten:
19
+ * eigene `firma.md`, eigene `marke.md`, eigene `sicherheit.md`. Und der
20
+ * Fehlertext des Befehls **schlägt diesen Weg selbst vor**.
21
+ *
22
+ * 🔑 **Die Vorlage wusste längst, dass es anders sein sollte.** Die
23
+ * Firmen-`.gitignore` erklärt in ihrem eigenen Kommentar die Ordner
24
+ * `werkbank-<name>` als Geschwister neben `kontext/`. *Der Entwurf sah eine
25
+ * Form vor, die die CLI nicht herstellen konnte.*
26
+ *
27
+ * arbeitsplatz neu → einmal pro FIRMA (die ganze Familie)
28
+ * werkbank neu → einmal pro MENSCH (nur der eigene Teil)
29
+ *
30
+ * 🔴 **GRENZE, die dieser Befehl heute NICHT aufhebt** (Review-Runde 4 zu
31
+ * PR #25, nachgemessen an `src/lib/pro/zugang.ts`): Er holt die Vorlage über
32
+ * `/api/cli/arbeitsplatz`, und die Tür dort fragt `istPro(user_id)` — eine
33
+ * Zeile in `konto_plan`, **pro Person**. Es gibt keinen Weg über
34
+ * `memberships`/`organizations`.
35
+ *
36
+ * Der zweite Mensch einer Kundenfirma — also genau der, für den dieser Befehl
37
+ * gebaut wurde — braucht damit ein **eigenes Pro-Konto**. Hat er keins,
38
+ * bekommt er `402` und Rückgabewert `4`, und `SKILL.md` weist Claude dort
39
+ * ausdrücklich an, NICHT zu wiederholen.
40
+ *
41
+ * ⚠️ Das ist **keine Panne, sondern eine offene Produktfrage**: Erbt ein
42
+ * Firmenmitglied den Zugang, oder kauft jeder einzeln? Sie gehört Dennis, nicht
43
+ * diesem Befehl — deshalb steht sie in `WEITER-HIER.md` unter „Entscheidungen"
44
+ * und nicht als stille Erweiterung von `istPro` hier drin.
45
+ * *Eine Grenze, die man benennt, ist etwas anderes als eine, die man
46
+ * übersieht* — und sie reiht sich neben die schon bekannte: Der Angestellte hat
47
+ * ohnehin noch keinen gebauten Weg an den Firmen-Ordner (Stufe 2).
48
+ *
49
+ * 🔴 **Die wichtigste Eigenschaft: Er fasst die Firmenebene NICHT an.** Kein
50
+ * einziges Byte in `kontext/`, `CLAUDE.md`, `README.md`, `.gitignore` — auch
51
+ * nicht schreibend „identisch drüber". Das ist keine Höflichkeit, sondern
52
+ * Vorbereitung: Perspektivisch soll die Firma ihre `kontext/`-Ebene zentral
53
+ * verwalten (s. `WEITER-HIER.md`, Entscheidungen). Was dann von außen kommt,
54
+ * darf ein lokaler Befehl nicht überschreiben.
55
+ */
56
+ const HILFE = `
57
+ kimal3 werkbank neu [dein-vorname]
58
+
59
+ Legt eine eigene Werkbank in einem bestehenden Firmen-Ordner an.
60
+ Die Firmenebene (kontext/, CLAUDE.md, README.md) wird NICHT angefasst.
61
+
62
+ dein-vorname Optional. Der Ordner traegt deinen Namen -- aus "sabine"
63
+ wird "werkbank-sabine". Ohne Angabe steht dort der Name,
64
+ den der Server aus deiner Anmelde-Adresse gebildet hat.
65
+
66
+ 🔴 Bricht ab, wenn der Zielordner schon existiert und NICHT leer ist.
67
+
68
+ Du hast noch gar keinen Firmen-Ordner?
69
+ kimal3 arbeitsplatz neu <dein-vorname>
70
+ `;
71
+ /**
72
+ * 🔴 **Review-Runde 2 zu PR #25 — und es ist DERSELBE Fehler wie in Runde 1,
73
+ * nur eine Datei weiter.** Hier stand:
74
+ *
75
+ * Gib einen anderen Namen an:
76
+ * kimal3 werkbank neu <ein-anderer-vorname>
77
+ *
78
+ * Der haeufigste Grund fuer diese Absage ist, dass der Mensch seine Werkbank
79
+ * **schon angelegt hat**. Befolgt er den Rat, bekommt er eine ZWEITE
80
+ * persoenliche Werkbank neben der ersten (`werkbank-sabine-2`) — dieselbe
81
+ * stille Verdopplung, die dieser PR bei `arbeitsplatz neu` gerade beseitigt
82
+ * hat.
83
+ *
84
+ * *Ich habe den Fehler bei einem Befehl behoben und ihn beim Zwilling stehen
85
+ * lassen — obwohl die Behebung des einen der Anlass fuer den anderen war.*
86
+ *
87
+ * Der Rat nennt jetzt zuerst den wahrscheinlichen Fall (du bist es selbst) und
88
+ * dann den seltenen (du bist es nicht).
89
+ *
90
+ * ⚠️ Und er ist eine FUNKTION, keine Konstante -- aus demselben Grund wie beim
91
+ * Zwilling in `arbeitsplatz.ts`: Der Befehl kennt den Ordner, in den er schickt.
92
+ * Ein Platzhalter wie `cd <der-ordner-oben>` waere eine Luecke, die der Mensch
93
+ * fuellen muss, obwohl die Antwort danebensteht. *Ein Platzhalter ist ein
94
+ * Eingestaendnis, dass man es nicht weiss -- hier weiss man es.*
95
+ */
96
+ function hinweisFuer(ordnerName) {
97
+ const ziel = ordnerName.includes(" ") ? `"${ordnerName}"` : ordnerName;
98
+ return (" Ist das DEINE Werkbank? Dann steht sie schon -- geh einfach hinein:\n" +
99
+ ` cd ${ziel}\n` +
100
+ " Bist du es nicht, nimm deinen eigenen Vornamen:\n" +
101
+ " kimal3 werkbank neu <dein-vorname>");
102
+ }
103
+ export async function werkbankNeu(argv) {
104
+ if (argv.includes("--help") || argv.includes("-h")) {
105
+ console.log(HILFE);
106
+ return 0;
107
+ }
108
+ /*
109
+ Dieselben drei Absagen wie bei `arbeitsplatz neu` -- bewusst wortgleich: Wer
110
+ den einen Befehl kennt, soll beim anderen nichts Neues lernen muessen.
111
+
112
+ ⚠️ Sie sprechen seit dem 2026-08-20 von einem NAMEN statt von einem
113
+ Ordnernamen. Die alte Hilfe hier hat ausdruecklich
114
+ `kimal3 werkbank neu werkbank-sabine` beigebracht -- deshalb schneidet
115
+ `werkbankOrdnerAus` ein bereits mitgetipptes `werkbank-` wieder ab, statt
116
+ `werkbank-werkbank-sabine` zu bauen. *Alte Anleitungen ueberleben laenger
117
+ als der Code, den sie beschreiben.*
118
+ */
119
+ const nameArg = argv[0];
120
+ if (nameArg !== undefined && nameArg.trim() === "") {
121
+ console.error(`Ein leerer Name ist kein gueltiger Name.\n${HILFE}`);
122
+ return 1;
123
+ }
124
+ if (nameArg !== undefined && nameArg.startsWith("--")) {
125
+ console.error(`Die Option „${nameArg}" kenne ich nicht.\n${HILFE}`);
126
+ return 1;
127
+ }
128
+ if (argv.length > 1) {
129
+ /*
130
+ 🔴 **Der Vorschlag darf keine Optionen einsammeln** (Review-Runde 5 zu
131
+ PR #25). Hier stand `argv.join("-")` -- aus `werkbank neu sabine --force`
132
+ wurde damit der Rat `werkbank neu sabine---force`. Die `--`-Wache ein paar
133
+ Zeilen hoeher sieht nur `argv[0]`.
134
+ *Ein Rat, der ein falsches Kommando erzeugt, ist schlimmer als keiner* --
135
+ dieselbe Klasse wie die Absagen aus Runde 1 und 2, nur kleiner.
136
+
137
+ Deshalb: Sieht irgendein Wort wie eine Option aus, gibt es gar keinen
138
+ Vorschlag. Ein Mensch, der `--force` tippt, sucht keine Schreibhilfe fuer
139
+ seinen Vornamen.
140
+ */
141
+ const woerter = argv.filter((w) => !w.startsWith("-"));
142
+ const vorschlag = woerter.length === argv.length && woerter.length > 1
143
+ ? `Besteht dein Name aus mehreren Woertern? Dann schreib ihn zusammen:\n` +
144
+ ` kimal3 werkbank neu ${woerter.join("-")}\n`
145
+ : "";
146
+ console.error(`Mit „${argv[1]}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n` +
147
+ `${vorschlag}${HILFE}`);
148
+ return 1;
149
+ }
150
+ // `null` heisst: nach dem Umschreiben blieb kein Zeichen uebrig, das in einem
151
+ // Ordnernamen stehen darf. Ein Mensch hat hier getippt und verdient eine
152
+ // Antwort -- der Server schweigt im selben Fall und bleibt beim Vordruck.
153
+ let gewuenschteWerkbank = null;
154
+ if (nameArg !== undefined) {
155
+ gewuenschteWerkbank = werkbankOrdnerAus(nameArg);
156
+ if (gewuenschteWerkbank === null) {
157
+ console.error(`Aus „${nameArg}" kann ich keinen Ordnernamen machen -- es bleiben nur`);
158
+ console.error("Zeichen uebrig, die in keinem Ordnernamen stehen duerfen.");
159
+ console.error("");
160
+ console.error(" Nimm deinen Vornamen: kimal3 werkbank neu sabine");
161
+ return 1;
162
+ }
163
+ }
164
+ const holen = await vorlageHolen();
165
+ if (!holen.ok)
166
+ return holen.wert;
167
+ const daten = holen.daten;
168
+ const werkbank = werkbankOrdner(daten.dateien);
169
+ /*
170
+ 🔴 **Review-Runde 1 zu PR #25: eine Asymmetrie zur Schwesterdatei.**
171
+ `arbeitsplatz.ts` prueft `daten.wurzel` ausdruecklich mit `istUnsichererPfad`
172
+ („kommt genauso ROH vom Server wie jeder `datei.pfad` -- dieselbe Bremse").
173
+ Hier wurde `werkbank` genauso roh benutzt, nur ungeprueft.
174
+
175
+ Der Fall: Eine manipulierte Antwort (z. B. ueber ein umgebogenes
176
+ `KIMAL3_URL`) enthaelt den Pfad `../CLAUDE.md`. `werkbankAusDateien` zerlegt
177
+ ihn in zwei Teile und liefert `".."` -- und `dateienSchreiben` macht daraus
178
+ `resolve("..")`, also den ELTERNORDNER.
179
+
180
+ ⚠️ Und die Firmen-Wache faengt das nicht: `firmenEbeneAus` gibt fuer so eine
181
+ Antwort eine leere Liste zurueck, `fehlend.length > 0` ist damit falsch, und
182
+ die Pruefung geht still durch.
183
+
184
+ 🔬 **Ehrlich nachgemessen: fuer `..` haette er trotzdem nichts geschrieben.**
185
+ Der Angriff wurde mit einem eigenen Server nachgestellt (Antwort mit
186
+ `../CLAUDE.md`), einmal mit und einmal ohne diese Bremse. Ohne sie faengt ihn
187
+ der Nicht-leer-Riegel in `dateienSchreiben`: Ein Elternordner enthaelt immer
188
+ mindestens den Ordner, in dem man steht, ist also nie leer.
189
+
190
+ 🔴 **Fuer `""` stimmte das NICHT -- und genau das stand hier als Begruendung**
191
+ (Review-Runde 2). Der Satz lautete, `""` fuehre auf das aktuelle Verzeichnis,
192
+ „das `kontext/` enthaelt, sonst waere die Wache oben schon rot". Ist sie
193
+ aber nicht: Bei einer Antwort aus `/CLAUDE.md` liefert `werkbankAusDateien`
194
+ den leeren Teil `""`, und `firmenEbeneAus` UEBERSPRINGT genau diese Pfade
195
+ (`teile[0] === werkbank`) -- die Firmen-Wache sieht `[]` und laesst durch.
196
+ In einem leeren Ordner waeren die Dateien dann dorthin gelandet, wo der
197
+ Mensch steht.
198
+ *Ein Kommentar, der eine Luecke mit einer anderen Wache begruendet, muss
199
+ diese Wache im FEHLERFALL gelesen haben, nicht im Normalfall.*
200
+ Deshalb prueft hier jetzt `istUnsichererOrdnername` statt
201
+ `istUnsichererPfad`: Ein Ordnername ist EIN Pfadteil, und der ist weder leer
202
+ noch `.` noch `..`.
203
+
204
+ 🔑 **Der Gewinn ist deshalb die MELDUNG, nicht die Abwehr.** Ohne diese
205
+ Zeile las der Mensch *„Der Ordner `..` gibt es schon und ist nicht leer. Gib
206
+ einen anderen Namen an"* -- und sucht den Fehler bei sich, obwohl der Server
207
+ luegt. Jetzt steht dort `Bitte melden`, und der Rueckgabewert ist `3` statt
208
+ `1`, also „Stoerung" statt „deine Eingabe passt nicht". *Kein Fehlerfall
209
+ traegt den Namen eines anderen* -- und dass die Abwehr schon anderswo steht,
210
+ ist kein Grund, sie hier falsch zu benennen.
211
+ */
212
+ if (werkbank !== null && istUnsichererOrdnername(werkbank)) {
213
+ console.error(`Die Antwort enthaelt einen unsicheren Ordnernamen: „${werkbank}". Bitte melden.`);
214
+ return 3;
215
+ }
216
+ if (werkbank === null) {
217
+ console.error("In der Vorlage steckt kein Werkbank-Ordner -- damit kann ich hier nichts anlegen.");
218
+ console.error("Bitte melden.");
219
+ return 3;
220
+ }
221
+ /*
222
+ 🔑 **Die Prüfung, ob wir überhaupt in einer Firma stehen** -- und sie ist
223
+ keine Förmlichkeit: In `<werkbank>/.claude/settings.json` steht
224
+ `"additionalDirectories": ["../kontext"]`. Eine Werkbank ohne Firmenebene
225
+ daneben zeigt damit ins Leere, und Claude fände den Firmenkontext nie.
226
+ Das merkt man erst Tage später -- genau die Sorte Fehler, gegen die dieser
227
+ Befehl gebaut wurde.
228
+
229
+ ⚠️ Welche Ordner das sind, wird ABGELEITET und nicht getippt (dieselbe Regel
230
+ wie bei `werkbankOrdner`): Firmenebene ist alles, was in der Lieferung
231
+ NEBEN dem Werkbank-Ordner in einem eigenen Unterordner liegt. Heute ist das
232
+ `kontext`. Wird die Vorlage umgebaut, wandert diese Prüfung mit -- statt
233
+ auf einen Ordnernamen zu zeigen, den es nicht mehr gibt.
234
+ */
235
+ const firmenOrdner = firmenEbeneAus(daten.dateien, werkbank);
236
+ const fehlend = firmenOrdner.filter((o) => !istOrdner(o));
237
+ /*
238
+ 🔴 **Die leere Liste ist ein eigener Fall** (Review-Runde 3 zu PR #25).
239
+ Vorher stand hier nur `fehlend.length > 0` -- und eine LEERE Liste hat
240
+ nichts, was fehlen koennte. Liefert die Antwort keine Firmenebene, ging die
241
+ Wache still durch und legte die Werkbank in irgendein Verzeichnis, in dem
242
+ der Mensch gerade steht. Ihr `../kontext` zeigte dann ins Leere.
243
+ *Eine Wache, die aus einer leeren Liste „alles in Ordnung" liest, hat die
244
+ Frage nicht gestellt, fuer die sie gebaut wurde.*
245
+
246
+ Heute nicht ausloesbar (`check:arbeitsplatz` Pruefung 2 erzwingt `kontext/*`)
247
+ -- erreichbar also nur ueber eine kaputte oder manipulierte Antwort.
248
+ */
249
+ /*
250
+ 🔴 **ZWEI Wirklichkeiten, zwei Ausgaenge** (Review-Runde 4 zu PR #25, und der
251
+ Fehler stammt aus dem Fix von Runde 3).
252
+
253
+ Runde 3 hat `firmenOrdner.length === 0` mit in dieselbe Bedingung gehaengt.
254
+ Dieser Zweig hat per Definition NICHTS in `fehlend` -- `join` lieferte `""`,
255
+ und der Mensch las woertlich *„Hier steht kein Firmen-Ordner -- ich sehe
256
+ kein ."* Beim Reparieren fiel der zweite Teil auf: Danach folgten
257
+ unveraendert die Raete fuer jemanden, der am FALSCHEN ORT steht („Geh
258
+ dorthin", „Bist du der ERSTE in deiner Firma?"). Die helfen nicht, wenn der
259
+ SERVER kaputt ist.
260
+
261
+ *Ein Fehlerfall darf nie denselben Namen tragen wie ein anderer* -- und der
262
+ Rueckgabewert ist Teil des Namens: `3` heisst laut `cli/skill/SKILL.md`
263
+ „Stoerung, gleich noch einmal", `1` heisst „deine Eingabe passt nicht".
264
+ Eine kaputte Lieferung ist keine falsche Eingabe.
265
+ */
266
+ if (firmenOrdner.length === 0) {
267
+ console.error("Die Vorlage vom Server nennt gar keine Firmenebene -- damit kann ich");
268
+ console.error("nicht pruefen, ob du an der richtigen Stelle stehst.");
269
+ console.error("Das liegt nicht an dir. Bitte melden.");
270
+ return 3;
271
+ }
272
+ if (fehlend.length > 0) {
273
+ console.error("Hier steht kein Firmen-Ordner -- ich sehe kein " + fehlend.join(", ") + ".");
274
+ console.error("Eine Werkbank braucht die Firmenebene daneben; ohne sie findet Claude");
275
+ console.error("euren Firmenkontext nicht.");
276
+ console.error("");
277
+ console.error(" Steht er woanders? Geh dorthin und versuch es dort noch einmal.");
278
+ console.error("");
279
+ /*
280
+ 🔴 **Hier stand „Du hast noch keinen? kimal3 arbeitsplatz neu"**
281
+ (Befund Dennis, 2026-08-20, wenige Minuten nach dem Bauen). Das ist
282
+ derselbe falsche Rat wie der aus `SKILL.md`, nur eine Ebene hoeher:
283
+
284
+ Fuer den ERSTEN Menschen einer Firma stimmt er. Fuer den ZWEITEN --
285
+ also genau den, fuer den dieser Befehl gebaut wurde -- legt er eine
286
+ leere Zweitfirma an, statt ihn an die echte zu bringen. Und diese
287
+ zweite Firma ist schlimmer als die verschachtelte von vorhin: Sie
288
+ sieht voellig richtig aus.
289
+
290
+ *Ich habe den Fehler aus SKILL.md entfernt und ihn in meiner eigenen
291
+ Absage wieder eingebaut. Ein Rat, der fuer die Haelfte der Leser
292
+ falsch ist, muss die Haelften benennen.*
293
+
294
+ ⚠️ Der zweite Fall hat heute KEINE gute Antwort -- der Angestellte hat
295
+ keinen gebauten Weg an den Firmen-Ordner seiner Firma (s. WEITER-HIER,
296
+ "Verwaltet die Firma ihre kontext/-Ebene kuenftig in der App?"). Solange
297
+ das so ist, ist die ehrliche Auskunft „frag, wo er liegt" -- und nicht
298
+ ein Befehl, der etwas Falsches anlegt.
299
+ */
300
+ console.error(" Bist du der ERSTE in deiner Firma?");
301
+ console.error(" kimal3 arbeitsplatz neu <dein-vorname>");
302
+ console.error("");
303
+ console.error(" Hat deine Firma schon einen? Dann hol dir DEN -- z. B. als Kopie");
304
+ console.error(" oder ueber euer Repo. Leg keinen neuen an, sonst habt ihr zwei");
305
+ console.error(" verschiedene Firmenordner mit verschiedenen Regeln darin.");
306
+ return 1;
307
+ }
308
+ /*
309
+ 🔑 Der Zielordner ist der Werkbank-Name -- entweder der, den der Server
310
+ schon gesetzt hat (`werkbank`, aus der Anmelde-Adresse gebildet), oder der
311
+ mitgegebene. Der Ordner TRAEGT damit immer einen Namen, und genau darum ging
312
+ es bei der Umbenennung: *Wo ein Name im Ordner steht, gehoert er einer
313
+ Person; wo keiner steht, gehoert er allen.*
314
+ */
315
+ const ordnerName = gewuenschteWerkbank ?? werkbank;
316
+ /*
317
+ Erst UMBENENNEN, dann ausschneiden -- und zwar auch im Inhalt.
318
+
319
+ ⚠️ **Hier stand: „Die Dateien muessen dafuer nicht umgeschrieben werden."**
320
+ Der Satz war wahr, als ich ihn schrieb: Heute nennt keine Datei unterhalb der
321
+ Werkbank ihren eigenen Ordner (nachgemessen), und die Texte, die ihn nennen,
322
+ gehoeren zur Firmenebene, die dieser Befehl nicht anfasst.
323
+
324
+ Nur HAELT das nichts. Wer morgen in die Werkbank-`CLAUDE.md` schreibt „deine
325
+ Projekte liegen in `werkbank-sabine/projekte/`", baut damit einen Satz ein,
326
+ der beim naechsten Menschen auf den falschen Ordner zeigt -- ohne dass etwas
327
+ fehlschlaegt. *Die Prueffrage aus CLAUDE.md: Beschreibt der Kommentar ein
328
+ Verhalten, das jemand WILL -- oder nur eines, das PASSIERT?* Hier passierte
329
+ es. Also nehmen wir die Annahme heraus, statt eine Wache um sie zu bauen.
330
+
331
+ Die Reihenfolge ist der ganze Trick: Nach dem Umbenennen heisst der Praefix
332
+ schon wie das Ziel, und der Ausschnitt schneidet ihn mit derselben
333
+ Zeichenkette weg, die vorher eingesetzt wurde. Aus `werkbank-jonas/CLAUDE.md`
334
+ wird `CLAUDE.md`, das Ziel ist der neue Ordner selbst.
335
+
336
+ 🔑 Das ist die einzige Stelle, an der sich dieser Befehl vom grossen
337
+ unterscheidet -- alles andere (Ueberschreib-Schutz, Pfad-Pruefung, Fehler-
338
+ Trennung) ist dieselbe Maschinerie aus `arbeitsplatz-dateien.ts`.
339
+ */
340
+ const umbenannt = werkbankUmbenennen(daten.dateien, werkbank, ordnerName);
341
+ const praefix = `${ordnerName}/`;
342
+ const meine = umbenannt
343
+ .filter((d) => d.pfad.startsWith(praefix))
344
+ .map((d) => ({ pfad: d.pfad.slice(praefix.length), inhalt: d.inhalt }));
345
+ const wert = dateienSchreiben(meine, ordnerName, hinweisFuer(ordnerName));
346
+ if (wert !== 0)
347
+ return wert;
348
+ console.log(`✓ ${meine.length} ${meine.length === 1 ? "Datei" : "Dateien"} angelegt.`);
349
+ console.log(` ${resolve(ordnerName)}`);
350
+ console.log("");
351
+ console.log(" Die Firmenebene ist unberuehrt geblieben.");
352
+ console.log("");
353
+ console.log(" Weiter geht's:");
354
+ console.log(` cd ${ordnerName.includes(" ") ? `"${ordnerName}"` : ordnerName}`);
355
+ console.log(" claude");
356
+ console.log(' dann "Lass uns einrichten" tippen');
357
+ return 0;
358
+ }
359
+ /**
360
+ * Die Ordner der Firmenebene, aus der Lieferung abgeleitet: jeder erste
361
+ * Pfad-Teil, der einen Unterordner benennt und nicht die Werkbank ist.
362
+ *
363
+ * Dateien direkt an der Wurzel (`CLAUDE.md`, `README.md`, `.gitignore`) zaehlen
364
+ * bewusst NICHT mit: Sie sagen weniger verlaesslich, dass hier eine Firma steht
365
+ * -- eine `CLAUDE.md` liegt in fast jedem Projektordner.
366
+ */
367
+ function firmenEbeneAus(dateien, werkbank) {
368
+ const ordner = new Set();
369
+ for (const datei of dateien) {
370
+ const teile = datei.pfad.split("/");
371
+ if (teile.length < 2)
372
+ continue;
373
+ if (teile[0] === werkbank)
374
+ continue;
375
+ ordner.add(teile[0]);
376
+ }
377
+ return [...ordner].sort();
378
+ }
379
+ function istOrdner(name) {
380
+ try {
381
+ return existsSync(name) && statSync(name).isDirectory();
382
+ }
383
+ catch {
384
+ // Nicht lesbar heisst "kann ich nicht bestaetigen" -- und was ich nicht
385
+ // bestaetigen kann, behandle ich als fehlend. Lieber eine Absage zu viel
386
+ // als eine Werkbank, die ins Leere zeigt.
387
+ return false;
388
+ }
389
+ }
@@ -49,15 +49,88 @@ export const BEFEHLE = [
49
49
  },
50
50
  {
51
51
  befehl: "kimal3 arbeitsplatz neu",
52
- beschreibung: "Deine KI-Arbeitsplatz-Vorlage als Ordner anlegen",
52
+ beschreibung: "Den ganzen KI-Arbeitsplatz anlegen — Firmenebene und Werkbank",
53
53
  wer: "du",
54
- rhythmus: "einmal pro Arbeitsplatz",
54
+ rhythmus: "einmal pro Firma",
55
55
  tippen: "! kimal3 arbeitsplatz neu",
56
56
  terminal: "kimal3 arbeitsplatz neu",
57
57
  details: [
58
58
  "Holt die Vorlage von deinem Pro-Konto und legt sie hier als echten Ordner an — CLAUDE.md, Takt-Befehle, die Werkbank für deine Projekte. Dasselbe Paket, das auch als Download auf /pro liegt.",
59
- "Ohne Angabe heißt der Ordner wie in der Vorlage. Brauchst du mehrere (z. B. je Firma), gib einen eigenen Namen mit: kimal3 arbeitsplatz neu <name>.",
59
+ "Der Ordner heißt immer KI-Arbeitsplatz — mehrere Firmen trennst du über den Ordner darüber, z. B. Kunden/ARNO/KI-Arbeitsplatz. Gib deinen Vornamen mit, dann heißt deine Werkbank darin werkbank-sabine: kimal3 arbeitsplatz neu sabine.",
60
60
  "Gibt es den Zielordner schon und ist er nicht leer, bricht der Befehl ab, statt etwas zu überschreiben — deine Arbeit ist sicher. Geklappt hat es, wenn er die Zahl der angelegten Dateien nennt.",
61
+ "⚠️ Er legt auch die Firmenebene an (kontext/ mit Marke, Systemen, Sicherheit). Steht die schon, brauchst du kimal3 werkbank neu — sonst entsteht eine zweite Firma in der ersten.",
62
+ ],
63
+ },
64
+ /*
65
+ 🔴 Der Eintrag steht direkt NEBEN `arbeitsplatz neu` und nicht am Ende der
66
+ Liste (die Reihenfolge ist laut Kopf dieser Datei „der Weg, den ein Mensch
67
+ geht"). Genau hier steht der Mensch vor der Verwechslung: Beide legen
68
+ Ordner aus derselben Vorlage an, und die falsche Wahl merkt man erst Tage
69
+ spaeter. Nebeneinander sind die verschiedenen `rhythmus`-Angaben --
70
+ „einmal pro Firma" gegen „einmal pro Mensch" -- die kuerzeste Erklaerung
71
+ des Unterschieds, die es gibt.
72
+ */
73
+ {
74
+ befehl: "kimal3 werkbank neu",
75
+ beschreibung: "Nur eine eigene Werkbank in einer bestehenden Firma anlegen",
76
+ wer: "du",
77
+ rhythmus: "einmal pro Mensch",
78
+ tippen: "! kimal3 werkbank neu",
79
+ terminal: "kimal3 werkbank neu",
80
+ details: [
81
+ "Für alle außer der ersten Person: Der Firmen-Ordner steht schon (kontext/ mit Marke, Systemen, Sicherheit) — du brauchst nur deinen eigenen Arbeitsplatz darin.",
82
+ "🔴 Er fasst die Firmenebene nicht an. Kein Byte in kontext/, CLAUDE.md oder README.md — auch nicht schreibend „identisch drüber“.",
83
+ "Er läuft nur, wenn du wirklich in einem Firmen-Ordner stehst. Fehlt die Firmenebene daneben, bricht er ab und sagt es — eine Werkbank ohne sie zeigt ins Leere, weil ihre Einstellungen auf ../kontext verweisen.",
84
+ "Gib deinen Vornamen mit — der Ordner trägt ihn: aus sabine wird werkbank-sabine. Ohne Angabe bildet der Server ihn aus deiner Anmelde-Adresse. So sieht man später, wem welche Werkbank gehört.",
85
+ ],
86
+ },
87
+ /*
88
+ 🔴 Auch diese zwei Eintraege stehen direkt NEBEN `werkbank neu` und nicht
89
+ am Ende der Liste, aus demselben Grund wie oben bei `arbeitsplatz neu`:
90
+ Wer hier liest, sucht den naechsten Schritt, nicht das Alphabet. Wer
91
+ einen KI-Arbeitsplatz gerade angelegt oder betreten hat, fragt sich als
92
+ Naechstes, wie der Firmenkontext (CLAUDE.md + kontext/*.md) aktuell
93
+ bleibt -- nicht, wie man ein Projekt eintraegt.
94
+ */
95
+ {
96
+ befehl: "kimal3 kontext runter",
97
+ beschreibung: "Den Firmenkontext holen — CLAUDE.md und die fünf Dateien in kontext/",
98
+ wer: "du",
99
+ rhythmus: "wann immer die Firma etwas geändert haben könnte",
100
+ tippen: "! kimal3 kontext runter",
101
+ terminal: "kimal3 kontext runter",
102
+ details: [
103
+ "Holt CLAUDE.md und die fünf Dateien in kontext/ von deiner Firma und legt sie in deinem KI-Arbeitsplatz ab. Was du selbst lokal geändert hast, fasst er nicht an — er sagt dir, wie du es einbringst.",
104
+ "Hast du eine Datei lokal geändert UND hat die Firma sie inzwischen auch geändert, sichert er deine Fassung zuerst nach kontext/vorher-<datum>/, bevor er sie ersetzt — nichts geht verloren.",
105
+ "Geklappt hat es, wenn er die aktualisierten Dateien nennt oder „Dein Firmenkontext ist aktuell“ sagt.",
106
+ "⚠️ Wie werkbank neu braucht er ein eigenes Pro-Konto. Ohne das kommt Rückgabewert 4 — dann nicht wiederholen.",
107
+ ],
108
+ },
109
+ {
110
+ befehl: "kimal3 kontext fragen",
111
+ beschreibung: "Zeigen, welche Fragen über deine Firma noch unbeantwortet sind",
112
+ wer: "du",
113
+ rhythmus: "bevor ihr den Firmenkontext ausfüllt — und wann immer du wissen willst, was fehlt",
114
+ tippen: "! kimal3 kontext fragen",
115
+ terminal: "kimal3 kontext fragen",
116
+ details: [
117
+ "Liest die fünf Dateien in kontext/ und gibt jede Frage aus, unter der noch nichts steht — mit Datei, Frage und Hinweis. Sind alle beantwortet, sagt er das in einem Satz.",
118
+ "Er liest nur: kein Netz, keine Anmeldung, kein Schreiben. Deshalb ist er der richtige erste Schritt — Claude stellt dir die Fragen dann einzeln und trägt deine Antworten in die Dateien ein.",
119
+ "Danach schickt kimal3 kontext hoch sie an die Firma. Das darf nur der Admin; alle anderen geben ihre Antworten an ihn weiter.",
120
+ ],
121
+ },
122
+ {
123
+ befehl: "kimal3 kontext hoch",
124
+ beschreibung: "Deine lokalen Änderungen an den fünf Firmen-Texten hochschicken",
125
+ wer: "du",
126
+ rhythmus: "nachdem du kontext/*.md bearbeitet hast — nur als Admin",
127
+ tippen: "! kimal3 kontext hoch",
128
+ terminal: "kimal3 kontext hoch",
129
+ details: [
130
+ "Schickt alle fünf Firmen-Texte an deine Firma — die Dateien in kontext/ (kurzprofil.md, firma.md, marke.md, systeme.md, sicherheit.md). Damit kommen sie beim nächsten kontext runter auch bei den Kolleginnen und Kollegen an.",
131
+ "🔴 Nur der Firmen-Admin darf das. Bist du keiner, kommt Rückgabewert 1 — das ist hier kein „nochmal versuchen“, sondern „gib deine Ergänzung an den Admin weiter“.",
132
+ "Sieht eine der fünf Dateien nicht mehr aus wie ihre Frageliste — etwa weil etwas Fremdes darüberkopiert wurde —, bricht er ab und schickt gar nichts, auch die vier anderen nicht. kimal3 kontext runter holt die Datei zurück.",
133
+ "Geklappt hat es, wenn er „✓ hochgeschickt“ mit den Dateinamen meldet.",
61
134
  ],
62
135
  },
63
136
  {
@@ -131,3 +204,25 @@ ${zeilen}
131
204
  Mehr zu einem Befehl: kimal3 projekt neu --help
132
205
  `;
133
206
  }
207
+ /**
208
+ * Wie ein Befehl **getippt** wird — aus dem Katalog geholt, nicht abgeschrieben.
209
+ *
210
+ * 🔑 **Warum das eine eigene Funktion ist** (2026-08-20): Auf `/pro` stand
211
+ * `const ANMELDEN = "! kimal3 login"` als Zeichenkette im Seitencode — eine
212
+ * zweite Wahrheit ueber einen Befehl, den der Katalog schon fuehrt. Wer im
213
+ * Katalog umbenennt, laesst sie stehen, und die Seite zeigt weiter den alten
214
+ * Aufruf. Dieselbe Klasse Fehler wie der fest getippte `cd`-Pfad in Runde 3.
215
+ *
216
+ * ⚠️ Sie **wirft**, statt einen leeren Text zu liefern. Die Aufrufer sind
217
+ * Modul-Konstanten — ein falscher Name fliegt damit beim `npm run build` auf,
218
+ * nicht erst beim Kunden vor einem Kopier-Kasten, in dem nichts steht.
219
+ * *Ein stiller Fehler auf einer bezahlten Seite ist teurer als ein lauter im
220
+ * Build.*
221
+ */
222
+ export function tippBefehl(befehl) {
223
+ const eintrag = BEFEHLE.find((b) => b.befehl === befehl);
224
+ if (!eintrag) {
225
+ throw new Error(`Kein Katalog-Eintrag fuer „${befehl}". Steht der Befehl noch in BEFEHLE?`);
226
+ }
227
+ return eintrag.tippen;
228
+ }