kimal3 0.2.0 → 0.2.1

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.
@@ -173,19 +173,52 @@ export async function kontextHoch(argv) {
173
173
  mitnimmt. Gemessen auf der Kundenstrecke am 2026-08-25.
174
174
  */
175
175
  const zuSchreiben = merkzettelNachHoch(ergebnis.merkzettel, lokalerMerkzettel, FIRMEN_TEXTE.map((d) => d.pfad));
176
+ /*
177
+ 🔴 **DIE ERFOLGSMELDUNG STEHT VOR DEM MERKZETTEL** (Review-Runde 4).
178
+
179
+ Sie stand darunter -- und fiel damit in genau dem Fall aus, in dem sie am
180
+ meisten gebraucht wird: Die Firma hat gespeichert, nur die lokale Datei
181
+ liess sich nicht schreiben. Der Mensch las eine Fehlermeldung und erfuhr
182
+ **nicht einmal, WAS oben angekommen ist**, obwohl der Vorgang gelungen war.
183
+
184
+ 🔑 Die Reihenfolge ist die Wahrheit: Erst sagen, was passiert ist, dann,
185
+ was daneben schiefging. Was hochgeschickt wurde, haengt nicht daran, ob ein
186
+ Merkzettel auf der Platte landet.
187
+ */
188
+ console.log(`✓ hochgeschickt: ${ergebnis.uebernommen.join(", ")}`);
176
189
  const merkzettelSchreiben = schreibeMerkzettelDatei(ordner, zuSchreiben);
177
190
  if (!merkzettelSchreiben.ok) {
178
- console.error("Die Firma hat deine Aenderungen gespeichert -- nur der lokale Merkzettel liess sich nicht schreiben:");
191
+ /*
192
+ 🔴 **ECHTE UMLAUTE -- das liest ein Kunde im Terminal** (Projektregel,
193
+ Review-Befund J2; dieselbe Begruendung steht in `kontext-fragen.ts:26`
194
+ und `kontext-gemeinsam.ts:72`). Hier stand die ASCII-Umschrift.
195
+
196
+ ⚠️ Sie war nicht nur unschoen, sie hat etwas kaputt gemacht: Diese Zeile
197
+ ist ein NACHSCHLAGE-SCHLUESSEL. `SKILL.md` und `k3kontext.md` fuehren sie
198
+ woertlich in einer Tabelle "Was in der Meldung steht", damit Claude den
199
+ Fall erkennt -- und dieser Fall ist der teuerste ueberhaupt, weil er ein
200
+ ERFOLG ist, der wie ein Fehler aussieht. Wer nach dem Satz mit Umlauten
201
+ sucht und die Umschrift vorfindet, erkennt ihn nicht und faellt auf die
202
+ naechstbeste Zeile zurueck -- „du bist kein Admin".
203
+ *Ein Text, den eine Anleitung zitiert, ist eine Schnittstelle.*
204
+
205
+ 🔬 Bewacht von `check:cli` -- aber **nur die Fassung in `k3kontext.md`**
206
+ (Review-Runde 5; hier stand, `SKILL.md` sei mitgedeckt, und das stimmte
207
+ nicht). In `SKILL.md` steht derselbe Satz ueber einen Zeilenumbruch
208
+ verteilt und ist durch eine `includes`-Pruefung ohnehin nicht erfassbar.
209
+ *Wer den Satz hier aendert, aendert ihn dort von Hand mit.*
210
+ */
211
+ console.error("Die Firma hat deine Änderungen gespeichert — nur der lokale Merkzettel ließ sich nicht schreiben:");
179
212
  console.error(merkzettelSchreiben.meldung);
180
213
  return 1;
181
214
  }
182
- // US-3: WAS hochgeladen wurde, nicht nur "fertig".
215
+ // US-3: WAS hochgeladen wurde, nicht nur "fertig" -- die Zeile steht seit
216
+ // Runde 4 oben, vor der Merkzettel-Pruefung (Begruendung dort).
183
217
  //
184
218
  // 🔑 Der Erklaersatz darunter ist mit Fund F ersatzlos WEGGEFALLEN. Er
185
219
  // benannte eine Ausnahme, die es nicht mehr gibt -- fuenf Texte, zwei
186
220
  // Richtungen, kein Sonderfall zu lernen. Die Namen kommen aus der Antwort
187
221
  // der Route (`uebernommen`), nicht aus einer zweiten Liste hier.
188
- console.log(`✓ hochgeschickt: ${ergebnis.uebernommen.join(", ")}`);
189
222
  return 0;
190
223
  }
191
224
  /** Prueft die Antwort-Form von POST, ohne `any`. */
@@ -2,7 +2,7 @@ import { zerlege } from "../argumente.js";
2
2
  import { ruf } from "../api.js";
3
3
  import { heuteAus } from "../nachricht.js";
4
4
  import { istUnsichererPfad } from "../arbeitsplatz-dateien.js";
5
- import { LEER, VERWALTETE_DATEIEN, alsMerkzettel, sicherungsOrdner, sicherungsZiel, lageFuerDatei, ratNachUnberuehrt, } from "../kontext-stand.js";
5
+ import { LEER, VERWALTETE_DATEIEN, alsMerkzettel, sicherungsOrdner, sicherungsZiel, lageAusMerkzetteln, ratNachUnberuehrt, } from "../kontext-stand.js";
6
6
  import { leseEineDatei, leseMerkzettelDatei, schreibeMerkzettelDatei, schreibeVerwalteteDatei, sichereDatei, } from "../kontext-dateien.js";
7
7
  import { ERLAUBT, absage, arbeitsplatzOderAbsage } from "./kontext-gemeinsam.js";
8
8
  /**
@@ -85,6 +85,11 @@ export async function kontextRunter(argv) {
85
85
  const lokalerMerkzettel = leseMerkzettelDatei(ordner);
86
86
  const heute = heuteAus(new Date());
87
87
  const geschrieben = [];
88
+ // Punkt A-1: die Teilmenge von `geschrieben`, bei der NICHT die Firma etwas
89
+ // geaendert hat, sondern wir unsere eigene Vorlage. Der Mensch soll den
90
+ // Unterschied lesen koennen -- sonst wundert er sich, warum sich eine Datei
91
+ // aendert, an der seine Firma nachweislich nichts getan hat.
92
+ const vorlageErneuert = [];
88
93
  const gesicherte = [];
89
94
  const unberuehrt = [];
90
95
  for (const verwalteterPfad of VERWALTETE_DATEIEN) {
@@ -104,13 +109,26 @@ export async function kontextRunter(argv) {
104
109
  console.error(lokalGelesen.meldung);
105
110
  return 1;
106
111
  }
107
- const eintrag = lokalerMerkzettel.dateien[verwalteterPfad] ?? null;
108
- const serverStand = daten.merkzettel.dateien[verwalteterPfad]?.stand ?? LEER;
109
- const lage = lageFuerDatei({
112
+ /*
113
+ 🔴 **DIE BEIDEN MERKZETTEL GEHEN ALS GANZES HINEIN -- Punkt A-1,
114
+ 2026-08-26.** Hier standen vorher drei Zeilen, die den Eintrag, den
115
+ Firmenstand und den Vordruck-Abdruck einzeln aus den beiden Dateien
116
+ herausklaubten. Genau dieser Schritt war die Blindstelle: Der
117
+ Vordruck-Abdruck wurde nie herausgeklaubt, das Argument war optional,
118
+ und deshalb bekam ein Kunde mit einer bestehenden Datei Verbesserungen
119
+ an unserem Fragenkatalog NIE zu sehen -- der Befehl meldete stattdessen
120
+ „Dein Firmenkontext ist aktuell".
121
+
122
+ 🔑 Die Zuordnung „was kommt aus welchem Merkzettel" wohnt jetzt in
123
+ `lageAusMerkzetteln` (`kontext-stand.ts`), wo `check:kontext` sie messen
124
+ kann. *Was gesichert sein muss, gehoert auf die reine Seite.*
125
+ */
126
+ const lage = lageAusMerkzetteln({
127
+ pfad: verwalteterPfad,
110
128
  lokalerInhalt: lokalGelesen.inhalt,
111
129
  serverInhalt: serverDatei.inhalt,
112
- eintrag,
113
- serverStand,
130
+ lokal: lokalerMerkzettel,
131
+ vomServer: daten.merkzettel,
114
132
  });
115
133
  if (lage === "beides") {
116
134
  // `lokalGelesen.inhalt` ist hier garantiert eine Zeichenkette:
@@ -130,13 +148,29 @@ export async function kontextRunter(argv) {
130
148
  nebenVorhandener: sicherung.nebenVorhandener,
131
149
  });
132
150
  }
133
- if (lage === "server-neuer" || lage === "beides") {
151
+ /*
152
+ 🔴 **`vorlage-neuer` schreibt wie `server-neuer` -- und sichert bewusst
153
+ NICHT** (Punkt A-1, 2026-08-26). Diese Lage heisst: Die Datei steht noch
154
+ Zeichen fuer Zeichen so da, wie wir sie zuletzt geliefert haben (die
155
+ Pruefsumme im Merkzettel passt), die Firma hat nichts geaendert -- nur
156
+ WIR haben unsere Frage besser formuliert. Es gibt hier also nichts von
157
+ einem Menschen zu verlieren. Haette er die Datei angefasst, waere die
158
+ Lage `beides` und die Sicherung liefe eine Bedingung weiter oben.
159
+
160
+ ⚠️ Bis zu diesem Punkt fiel `vorlage-neuer` durch ALLE drei `if`s: nicht
161
+ geschrieben, nicht gesichert, nicht gemeldet. Der Fall konnte bisher nie
162
+ entstehen, weil die Kette dahinter tot war -- ein Zweig, den niemand
163
+ vermisst hat, weil ihn niemand erreichen konnte.
164
+ */
165
+ if (lage === "server-neuer" || lage === "beides" || lage === "vorlage-neuer") {
134
166
  const geschriebenErg = schreibeVerwalteteDatei(ordner, verwalteterPfad, serverDatei.inhalt);
135
167
  if (!geschriebenErg.ok) {
136
168
  console.error(geschriebenErg.meldung);
137
169
  return 1;
138
170
  }
139
171
  geschrieben.push(verwalteterPfad);
172
+ if (lage === "vorlage-neuer")
173
+ vorlageErneuert.push(verwalteterPfad);
140
174
  }
141
175
  if (lage === "lokal-geaendert") {
142
176
  unberuehrt.push(verwalteterPfad);
@@ -155,6 +189,15 @@ export async function kontextRunter(argv) {
155
189
  Firma, die diese Antwort tatsaechlich beantwortet hat (`waehleFirma` in
156
190
  der Route), nicht aus dem Merkzettel-Feld selbst. Wer zu MEHREREN Firmen
157
191
  gehoert, soll im Ordner von Firma A niemals lesen "B hat sich geaendert".
192
+
193
+ 🔴 **Punkt A-1: MIT DIESER ZEILE HAELT DER MERKZETTEL DEN NEUEN
194
+ VORLAGEN-FINGERABDRUCK FEST.** `daten.merkzettel.dateien` kommt aus der
195
+ Antwort des Servers und traegt je Datei ihr `vorlage` (`alsMerkzettel`
196
+ verlangt es ausdruecklich). Waere es der LOKALE Merkzettel, staende hier
197
+ weiter der Abdruck von gestern -- und der naechste Lauf meldete denselben
198
+ Unterschied noch einmal, Tag fuer Tag. *Der Teil, den man leicht
199
+ uebersieht: Eine Verbesserung, die jeden Tag neu gemeldet wird, ist
200
+ dieselbe Sorte Fehlalarm wie eine, die nie ankommt.*
158
201
  */
159
202
  const merkzettelZumSchreiben = { firma_id: daten.firma.id, dateien: daten.merkzettel.dateien };
160
203
  const merkzettelSchreiben = schreibeMerkzettelDatei(ordner, merkzettelZumSchreiben);
@@ -191,6 +234,14 @@ export async function kontextRunter(argv) {
191
234
  if (geschrieben.length > 0) {
192
235
  console.log(`✓ aktualisiert: ${geschrieben.join(", ")}`);
193
236
  }
237
+ // 🔴 Punkt A-1: Eine eigene Zeile, kein stilles Mitschreiben. „Aktualisiert"
238
+ // heisst fuer den Leser sonst automatisch „meine Firma hat etwas gesagt" --
239
+ // und dann sucht er in der App eine Aenderung, die es dort nicht gibt.
240
+ if (vorlageErneuert.length > 0) {
241
+ console.log(` Davon aus unserer verbesserten Vorlage: ${vorlageErneuert.join(", ")}`);
242
+ console.log(" Diese Dateien standen noch genau so da, wie wir sie geliefert hatten — es ist nichts\n" +
243
+ " von dir verloren gegangen. Hättest du darin etwas geändert, hätte ich vorher gesichert.");
244
+ }
194
245
  // §8: Ein Fehlerfall darf nie denselben Namen tragen wie ein Erfolgsfall --
195
246
  // "nichts geschrieben" heisst hier DREI verschiedene Dinge, und die Ausgabe
196
247
  // muss sie unterscheiden, nicht nur die Rueckgabe (alle drei geben 0).
@@ -4,22 +4,21 @@ import { fileURLToPath } from "node:url";
4
4
  import { join } from "node:path";
5
5
  import { STEMPEL_MARKE } from "../nachricht.js";
6
6
  import { eigeneVersion } from "../version.js";
7
- /**
8
- * Namen, die frueher einmal ausgeliefert wurden und heute nicht mehr.
9
- *
10
- * 🔑 **Wofuer das da ist** (Review-Befund, 2026-08-18): Die Befehle landen im
11
- * GEMEINSAMEN Ordner `~/.claude/commands/` und werden dort nur ueberschrieben,
12
- * nie entfernt. Wird ein Befehl spaeter umbenannt oder gestrichen, ueberlebt
13
- * die alte Datei jedes `skill install` und bietet weiter Anweisungen an, die
14
- * auf einen nicht mehr vorhandenen Unterbefehl zeigen. `SKILL.md` kann das
15
- * nicht passieren -- es ist EINE Datei, die ueberschrieben wird.
16
- *
17
- * ⚠️ Warum eine Liste und nicht "alles loeschen, was wir nicht ausliefern":
18
- * Der Ordner gehoert dem Menschen. Ein `k3*.md`, das er selbst angelegt hat,
19
- * duerfen wir nicht wegraeumen. **Wer einen Befehl umbenennt oder entfernt,
20
- * traegt den alten Namen hier ein** -- sonst bleibt er beim Nutzer stehen.
21
- */
22
- const VERALTETE_BEFEHLE = [];
7
+ export const VERALTETE_BEFEHLE = [
8
+ /*
9
+ 🔴 **Der ERSTE Eintrag ueberhaupt** (2026-08-28). `/k3firma` heisst jetzt
10
+ `/k3analyse`, weil der Befehl Zahlen holt und daraus eine Frage beantwortet
11
+ -- das ist eine Auswertung, und der alte Name sagte nur, WORUEBER sie geht.
12
+ Ohne diese Zeile staenden beide Dateien nebeneinander und taeten dasselbe;
13
+ nur wuerde `k3firma.md` nie wieder gepflegt.
14
+
15
+ ⚠️ Damit laeuft `raeumeVeralteteAuf` zum ersten Mal scharf. Der Kommentar
16
+ an der Funktion sagt es selbst: Die Schleife ist nie gelaufen. Deshalb
17
+ gehoert zu diesem Eintrag eine Rot-Probe in BEIDE Richtungen -- eine Datei
18
+ MIT unserer Marke muss verschwinden, eine OHNE muss liegen bleiben.
19
+ */
20
+ { alt: "k3firma.md", ersetztDurch: "k3analyse.md" },
21
+ ];
23
22
  /**
24
23
  * Woran wir unsere eigenen Befehlsdateien erkennen.
25
24
  *
@@ -344,7 +343,42 @@ function slashBefehleInstallieren() {
344
343
  console.error(`Die Slash-Befehle liessen sich nicht alle schreiben: ${String(fehler)}`);
345
344
  gestoert = true;
346
345
  }
347
- raeumeVeralteteAuf(zielOrdner, VERALTETE_BEFEHLE);
346
+ /*
347
+ 🔴 **NUR aufraeumen, wenn das Schreiben durchlief** (Review-Runde 2,
348
+ 2026-08-28). Der Aufruf stand ausserhalb des `try` und lief deshalb auch
349
+ nach einem Fehler -- mit einer boesen Folge: `k3firma.md` wird geloescht,
350
+ obwohl `k3analyse.md` womoeglich nie geschrieben wurde. Der Mensch haette
351
+ danach **keinen von beiden** Befehlen.
352
+
353
+ ⚠️ Und der Fall ist naeher, als er klingt: Die Dateien werden alphabetisch
354
+ kopiert, `k3analyse.md` ist die ZWEITE. Ein Rechteproblem oder eine volle
355
+ Platte genau dort genuegt.
356
+
357
+ 🔑 Die sichere Richtung ist eindeutig: Lieber traegt jemand den alten
358
+ Befehl eine Runde laenger mit sich herum, als dass er ohne dasteht. Ein
359
+ zweiter Lauf holt beides nach -- der Rueckgabewert sagt ihm das ohnehin.
360
+ */
361
+ /*
362
+ 🔴 **Nachgeschaerft in Runde 3.** Die erste Fassung hiess `if (!gestoert)`
363
+ und deckte nur EINE der drei Arten ab, auf die eine Datei nicht landet:
364
+ `gestoert` (eine Ausnahme beim Kopieren) -- aber nicht `unlesbar` und nicht
365
+ `uebersprungen` (dort liegt eine Datei OHNE unsere Marke, die wir bewusst
366
+ stehen lassen). In beiden Faellen waere `k3firma.md` geloescht worden,
367
+ obwohl `k3analyse.md` nie geschrieben wurde.
368
+
369
+ 🔑 Die Bedingung trifft jetzt die Absicht direkt: **Ein alter Name wird nur
370
+ entfernt, wenn sein Ersatz wirklich liegt.** Steht kein Ersatz in der
371
+ Tabelle, gilt der Eintrag als ersatzlos gestrichen und darf immer weg.
372
+ */
373
+ /*
374
+ ⚠️ **Beide Schreibweisen, genau wie `raeumeVeralteteAuf`** (Review-Runde 4).
375
+ `VERALTETE_BEFEHLE` nimmt `"k3alt"` UND `"k3alt.md"`; `geschrieben` traegt
376
+ immer den Dateinamen. Ohne die Angleichung schluege der Vergleich fehl, und
377
+ ein Eintrag ohne `.md` bliebe fuer immer liegen.
378
+ */
379
+ const alsDatei = (n) => (n.endsWith(".md") ? n : `${n}.md`);
380
+ const ersatzLiegt = VERALTETE_BEFEHLE.filter((e) => e.ersetztDurch === null || geschrieben.includes(alsDatei(e.ersetztDurch))).map((e) => e.alt);
381
+ raeumeVeralteteAuf(zielOrdner, ersatzLiegt);
348
382
  return {
349
383
  geschrieben: geschrieben.map((n) => n.replace(/\.md$/, "")),
350
384
  uebersprungen: uebersprungen.map((n) => n.replace(/\.md$/, "")),
@@ -1,7 +1,10 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { dateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
3
+ import { FIRMEN_OPTIONEN, dateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
4
+ import { alsBefehlszeile, alsWort, trenneNamenUndOptionen, zerlege } from "../argumente.js";
4
5
  import { werkbankOrdnerAus, werkbankUmbenennen } from "../werkbank-name.js";
6
+ import { FIRMEN_CLAUDE_MD, MERKZETTEL_PFAD, firmenAbgleich, firmennameAusClaudeMd, leseMerkzettel, } from "../kontext-stand.js";
7
+ import { leseEineDatei, leseMerkzettelDatei } from "../kontext-dateien.js";
5
8
  /**
6
9
  * Legt **nur eine Werkbank** an — in einem Firmen-Ordner, der schon steht.
7
10
  *
@@ -54,7 +57,7 @@ import { werkbankOrdnerAus, werkbankUmbenennen } from "../werkbank-name.js";
54
57
  * darf ein lokaler Befehl nicht überschreiben.
55
58
  */
56
59
  const HILFE = `
57
- kimal3 werkbank neu [dein-vorname]
60
+ kimal3 werkbank neu [dein-vorname] [--firma "Name" | --firma-id "…"]
58
61
 
59
62
  Legt eine eigene Werkbank in einem bestehenden Firmen-Ordner an.
60
63
  Die Firmenebene (kontext/, CLAUDE.md, README.md) wird NICHT angefasst.
@@ -63,6 +66,12 @@ const HILFE = `
63
66
  wird "werkbank-sabine". Ohne Angabe steht dort der Name,
64
67
  den der Server aus deiner Anmelde-Adresse gebildet hat.
65
68
 
69
+ --firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst.
70
+ Die Absage nennt dir die Namen als fertige Befehle.
71
+
72
+ --firma-id "…" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
73
+ Auch diese Absage nennt dir fertige Befehle.
74
+
66
75
  🔴 Bricht ab, wenn der Zielordner schon existiert und NICHT leer ist.
67
76
 
68
77
  Du hast noch gar keinen Firmen-Ordner?
@@ -94,7 +103,7 @@ const HILFE = `
94
103
  * Eingestaendnis, dass man es nicht weiss -- hier weiss man es.*
95
104
  */
96
105
  function hinweisFuer(ordnerName) {
97
- const ziel = ordnerName.includes(" ") ? `"${ordnerName}"` : ordnerName;
106
+ const ziel = alsWort(ordnerName);
98
107
  return (" Ist das DEINE Werkbank? Dann steht sie schon -- geh einfach hinein:\n" +
99
108
  ` cd ${ziel}\n` +
100
109
  " Bist du es nicht, nimm deinen eigenen Vornamen:\n" +
@@ -116,37 +125,58 @@ export async function werkbankNeu(argv) {
116
125
  `werkbank-werkbank-sabine` zu bauen. *Alte Anleitungen ueberleben laenger
117
126
  als der Code, den sie beschreiben.*
118
127
  */
119
- const nameArg = argv[0];
128
+ /*
129
+ 🔴 **Erst schneiden, dann zerlegen** -- wortgleich zum Zwilling in
130
+ `arbeitsplatz.ts` (Punkt A-3, 2026-08-26). Dieser Befehl hat ein Wort OHNE
131
+ `--` (den Vornamen), und `zerlege` lehnt genau das ab.
132
+ */
133
+ const { woerter, optionen: optionsArgv } = trenneNamenUndOptionen(argv);
134
+ const nameArg = woerter[0];
120
135
  if (nameArg !== undefined && nameArg.trim() === "") {
121
136
  console.error(`Ein leerer Name ist kein gueltiger Name.\n${HILFE}`);
122
137
  return 1;
123
138
  }
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) {
139
+ if (woerter.length > 1) {
129
140
  /*
130
141
  🔴 **Der Vorschlag darf keine Optionen einsammeln** (Review-Runde 5 zu
131
142
  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]`.
143
+ wurde damit der Rat `werkbank neu sabine---force`.
134
144
  *Ein Rat, der ein falsches Kommando erzeugt, ist schlimmer als keiner* --
135
145
  dieselbe Klasse wie die Absagen aus Runde 1 und 2, nur kleiner.
136
146
 
137
147
  Deshalb: Sieht irgendein Wort wie eine Option aus, gibt es gar keinen
138
148
  Vorschlag. Ein Mensch, der `--force` tippt, sucht keine Schreibhilfe fuer
139
149
  seinen Vornamen.
150
+
151
+ ⚠️ Seit dem 2026-08-26 stehen die `--`-Optionen schon gar nicht mehr in
152
+ `woerter` (der Schnitt oben). Der Filter bleibt trotzdem: Er faengt das
153
+ EINFACHE `-` (`-force`), das kein Schnitt-Zeichen ist.
140
154
  */
141
- const woerter = argv.filter((w) => !w.startsWith("-"));
142
- const vorschlag = woerter.length === argv.length && woerter.length > 1
155
+ const einfacheWoerter = woerter.filter((w) => !w.startsWith("-"));
156
+ /*
157
+ 🔴 **Die getippten Optionen reisen mit** (Review-Befund 3, 2026-08-26).
158
+ Ohne sie riet die Zeile zu einem Befehl, der bei einem Menschen in zwei
159
+ Firmen sofort wieder in dieselbe Absage laeuft -- s. `alsBefehlszeile`.
160
+ */
161
+ const optionsAnhang = optionsArgv.length === 0 ? "" : ` ${alsBefehlszeile(optionsArgv)}`;
162
+ const vorschlag = einfacheWoerter.length === woerter.length
143
163
  ? `Besteht dein Name aus mehreren Woertern? Dann schreib ihn zusammen:\n` +
144
- ` kimal3 werkbank neu ${woerter.join("-")}\n`
164
+ ` kimal3 werkbank neu ${einfacheWoerter.join("-")}${optionsAnhang}\n`
145
165
  : "";
146
- console.error(`Mit „${argv[1]}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n` +
166
+ console.error(`Mit „${woerter[1]}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n` +
147
167
  `${vorschlag}${HILFE}`);
148
168
  return 1;
149
169
  }
170
+ // 🔑 Erst NACH den Namens-Absagen -- s. Zwilling in `arbeitsplatz.ts`.
171
+ const zerlegt = zerlege(optionsArgv, FIRMEN_OPTIONEN);
172
+ if (!zerlegt.ok) {
173
+ console.error(`${zerlegt.meldung}\n${HILFE}`);
174
+ return 1;
175
+ }
176
+ const wunsch = {
177
+ firma: zerlegt.werte.optionen.firma,
178
+ firmaId: zerlegt.werte.optionen["firma-id"],
179
+ };
150
180
  // `null` heisst: nach dem Umschreiben blieb kein Zeichen uebrig, das in einem
151
181
  // Ordnernamen stehen darf. Ein Mensch hat hier getippt und verdient eine
152
182
  // Antwort -- der Server schweigt im selben Fall und bleibt beim Vordruck.
@@ -161,7 +191,17 @@ export async function werkbankNeu(argv) {
161
191
  return 1;
162
192
  }
163
193
  }
164
- const holen = await vorlageHolen();
194
+ /*
195
+ 🔴 **HIER ENTSCHEIDET SICH, OB UEBERHAUPT ETWAS ENTSTEHT** (Punkt A-3) --
196
+ dieselbe Stelle wie beim Zwilling: Bei mehreren Firmen ohne Angabe kommt
197
+ `409`, dieser Aufruf endet mit `1`, und `dateienSchreiben` weiter unten
198
+ wird nie erreicht.
199
+ */
200
+ // 🔑 Der getippte Vorname reist in der Absage mit -- s. Zwilling.
201
+ const befehlsname = nameArg === undefined
202
+ ? "werkbank neu"
203
+ : `werkbank neu ${alsWort(nameArg)}`;
204
+ const holen = await vorlageHolen(befehlsname, wunsch);
165
205
  if (!holen.ok)
166
206
  return holen.wert;
167
207
  const daten = holen.daten;
@@ -305,6 +345,77 @@ export async function werkbankNeu(argv) {
305
345
  console.error(" verschiedene Firmenordner mit verschiedenen Regeln darin.");
306
346
  return 1;
307
347
  }
348
+ /*
349
+ 🔴 **STEHT DIE GELIEFERTE FIRMA UEBERHAUPT HIER?** (Befund 1 der zweiten
350
+ Review-Runde, 2026-08-27.)
351
+
352
+ Bis hierher ist geprueft, dass NEBEN uns eine Firmenebene liegt -- aber
353
+ nicht, ob es DIE Firma ist, deren Namen die Lieferung gleich in die
354
+ `werkbank-<name>/CLAUDE.md` schreibt. Seit A-11 ist das keine Kosmetik
355
+ mehr: Dort stand frueher fuer jede Firma derselbe Platzhalter, jetzt steht
356
+ dort ein Name, den Claude bei jedem Start liest. Die ganze Fallgeschichte
357
+ haengt an `firmenAbgleich` in `../kontext-stand.js`.
358
+
359
+ 🔑 **Der Vergleich laeuft ueber die IDs im Merkzettel** -- der des Ordners,
360
+ in dem wir stehen, gegen den, der gerade geliefert wurde. Beide Seiten
361
+ kennen ihre Firma aus derselben Quelle, es wird nichts geraten.
362
+
363
+ 🔴 **UND ER STEHT VOR `dateienSchreiben`.** Ein Abbruch hier hinterlaesst
364
+ keine halbe Werkbank -- dieselbe Haltung wie in `kontextHoch`
365
+ („ich schicke nichts Halbes hoch“).
366
+ */
367
+ const inhaltAus = (pfad) => daten.dateien.find((d) => d.pfad === pfad)?.inhalt ?? null;
368
+ const hierGelesen = leseEineDatei(process.cwd(), FIRMEN_CLAUDE_MD);
369
+ const abgleich = firmenAbgleich({
370
+ id: leseMerkzettelDatei(process.cwd()).firma_id,
371
+ name: firmennameAusClaudeMd(hierGelesen.ok ? hierGelesen.inhalt : null),
372
+ }, {
373
+ id: leseMerkzettel(inhaltAus(MERKZETTEL_PFAD)).firma_id,
374
+ name: firmennameAusClaudeMd(inhaltAus(FIRMEN_CLAUDE_MD)),
375
+ });
376
+ /*
377
+ 🔴 **DER VERDACHT WARNT, ER SPERRT NICHT** (Befund 1 der vierten
378
+ Review-Runde). Steht der Name nur deshalb anders da, weil jemand die
379
+ `CLAUDE.md` seiner Firma bearbeitet hat -- was er ausdruecklich darf --,
380
+ waere eine Absage falsch UND ausweglos. Die Zeile hier sagt, was sie
381
+ sieht, und laesst den Menschen entscheiden.
382
+ */
383
+ if (abgleich.art === "zweifel") {
384
+ console.error(`Achtung: Dieser Ordner nennt als Firma „${abgleich.hier}",`);
385
+ console.error(`du hast aber „${abgleich.gewaehlt}" gewaehlt.`);
386
+ console.error("Ich lege die Werkbank trotzdem an -- vielleicht heisst die Firma bei euch");
387
+ console.error("nur anders geschrieben. Stimmt es nicht, loesch den neuen Ordner wieder.");
388
+ console.error("");
389
+ }
390
+ if (abgleich.art === "andere-firma") {
391
+ /*
392
+ 🔴 **ZWEI LAGEN, ZWEI SAETZE** (Befund 3 der dritten Review-Runde). Hier
393
+ stand ein Rueckfall `?? "die genannte Firma"`, und der war erreichbar:
394
+ Wer zu genau EINER Firma gehoert, tippt gar kein `--firma`. Steht er im
395
+ Ordner einer anderen Firma, las er woertlich *„Dieser Ordner gehoert zu
396
+ einer ANDEREN Firma als ‚die genannte Firma'."* -- ein Satz, der nichts
397
+ benennt und wie ein Fehler im Programm aussieht.
398
+ *Ein Platzhalter in einer Meldung ist ein Satz, den niemand geschrieben
399
+ hat.*
400
+ */
401
+ const genannt = wunsch.firma ?? wunsch.firmaId;
402
+ if (genannt === undefined) {
403
+ console.error("Dieser Ordner gehoert zu einer anderen Firma als der, zu der du gehoerst.");
404
+ console.error("Ich lege hier keine Werkbank an, die einen fremden Firmennamen traegt --");
405
+ console.error("sie wuerde Claude bei jedem Start die falsche Firma nennen.");
406
+ console.error("");
407
+ console.error(" Deine Werkbank gehoert in den Ordner DEINER Firma. Geh dorthin und");
408
+ console.error(" versuch es dort noch einmal.");
409
+ return 1;
410
+ }
411
+ console.error(`Dieser Ordner gehoert zu einer ANDEREN Firma als „${genannt}".`);
412
+ console.error("Ich lege hier keine Werkbank an, die einen fremden Firmennamen traegt --");
413
+ console.error("sie wuerde Claude bei jedem Start die falsche Firma nennen.");
414
+ console.error("");
415
+ console.error(` Soll die Werkbank fuer „${genannt}" sein? Dann gehoert sie in DEREN Ordner.`);
416
+ console.error(" Soll sie fuer die Firma hier sein? Dann nimm deren Namen.");
417
+ return 1;
418
+ }
308
419
  /*
309
420
  🔑 Der Zielordner ist der Werkbank-Name -- entweder der, den der Server
310
421
  schon gesetzt hat (`werkbank`, aus der Anmelde-Adresse gebildet), oder der
@@ -351,7 +462,7 @@ export async function werkbankNeu(argv) {
351
462
  console.log(" Die Firmenebene ist unberuehrt geblieben.");
352
463
  console.log("");
353
464
  console.log(" Weiter geht's:");
354
- console.log(` cd ${ordnerName.includes(" ") ? `"${ordnerName}"` : ordnerName}`);
465
+ console.log(` cd ${alsWort(ordnerName)}`);
355
466
  console.log(" claude");
356
467
  console.log(' dann "Lass uns einrichten" tippen');
357
468
  return 0;
@@ -42,7 +42,13 @@ export const BEFEHLE = [
42
42
  tippen: "! kimal3 skill install",
43
43
  terminal: "kimal3 skill install",
44
44
  details: [
45
- "Der Befehl, der aus den anderen erst etwas macht: Er legt 🤖 Claudes Anleitung ab (den „Skill“) und 🧑 deine vier Slash-Befehle.",
45
+ // ⚠️ KEINE Anzahl hier (Review-Runde 5): Hier stand „deine vier
46
+ // Slash-Befehle“, und die Zahl war ab `/k3kontext` falsch — auf der
47
+ // Pro-Seite UND in der Terminal-Hilfe, denn dieser Text wird an beiden
48
+ // Stellen ausgegeben. Dieselbe Lehre wie bei „13 Pruefungen“ in
49
+ // `check:arbeitsplatz`: Eine Anzahl im Fliesstext ist eine Behauptung,
50
+ // die verrottet. Welche es sind, sagt `/k3` beim Laufen.
51
+ "Der Befehl, der aus den anderen erst etwas macht: Er legt 🤖 Claudes Anleitung ab (den „Skill“) und 🧑 deine Slash-Befehle.",
46
52
  "Er läuft NICHT automatisch — einmal nach der Installation, und nach jedem Update noch einmal. Sonst arbeitet Claude weiter mit der alten Anleitung.",
47
53
  "Geklappt hat es, wenn er „Skill angelegt“ meldet und die Slash-Befehle aufzählt. Beides kennt Claude ab der NÄCHSTEN Sitzung — die laufende einmal neu starten.",
48
54
  ],
@@ -108,10 +114,10 @@ export const BEFEHLE = [
108
114
  },
109
115
  {
110
116
  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",
117
+ beschreibung: "Die Fragen über deine Firma beantworten — Claude führt dich durch",
118
+ wer: "claude",
119
+ ausloeser: "beantworten wir die Fragen über meine Firma",
120
+ tippen: "/k3kontext",
115
121
  terminal: "kimal3 kontext fragen",
116
122
  details: [
117
123
  "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.",
@@ -128,7 +134,8 @@ export const BEFEHLE = [
128
134
  terminal: "kimal3 kontext hoch",
129
135
  details: [
130
136
  "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“.",
137
+ "🔴 Nur der Firmen-Admin darf das. Bist du keiner, sagt er es dir — dann gib deine Ergänzung an den Admin weiter, ein zweiter Versuch ändert daran nichts.",
138
+ "⚠️ Klappt etwas nicht, zählt die MELDUNG, nicht die Zahl dahinter: Dieselbe 1 steht für „du bist kein Admin“, „hier steht kein Arbeitsplatz“, „jemand anderes hat inzwischen geändert“ (Ausweg --ueberschreiben) und mehreres mehr. Sie steht sogar unter einer Meldung, die mit „Die Firma hat deine Änderungen gespeichert“ beginnt und nur sagt, dass eine Datei auf deiner Platte nicht geschrieben werden konnte — DAS ist ein Erfolg: alles ist oben angekommen. Lies den Satz, nicht die Zahl.",
132
139
  "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
140
  "Geklappt hat es, wenn er „✓ hochgeschickt“ mit den Dateinamen meldet.",
134
141
  ],
@@ -162,7 +169,7 @@ export const BEFEHLE = [
162
169
  beschreibung: "Die Daten deiner Firma holen, damit Claude sie auswerten kann",
163
170
  wer: "claude",
164
171
  ausloeser: "werte meine Firma aus",
165
- tippen: "/k3firma <deine Frage>",
172
+ tippen: "/k3analyse <deine Frage>",
166
173
  terminal: 'kimal3 firma daten --firma "Nordwind Handel GmbH"',
167
174
  details: [
168
175
  "Holt Kennzahlen, Menschen und Projekte deiner Firma — auswerten tut Claude. Frag einfach los: „Welche drei Projekte bringen den meisten messbaren Ertrag?“",
@@ -2,8 +2,10 @@
2
2
  * Die Firmen-Auswahl, die eine Absage dem Menschen anbietet.
3
3
  *
4
4
  * 🔑 Eine reine Rechnung, damit `check:cli` sie ohne Server pruefen kann --
5
- * dieselbe Ueberlegung wie bei `fehler.ts`.
5
+ * dieselbe Ueberlegung wie bei `fehler.ts`. `alsWort` unten ist ebenfalls eine
6
+ * reine Rechnung, der Import bricht das also nicht.
6
7
  */
8
+ import { alsWort } from "./argumente.js";
7
9
  /**
8
10
  * Aus den Namen der eigenen Firmen die Zeilen bauen, die unter „Deine Firmen:"
9
11
  * stehen.
@@ -42,7 +44,7 @@ export function firmenZeilen(namen) {
42
44
  // Fertiger Befehl statt blosser Liste: Der naechste Schritt ist dann
43
45
  // Kopieren statt Abtippen -- und ein Name mit Leerzeichen braucht
44
46
  // Anfuehrungszeichen, an die sonst niemand denkt.
45
- zeilen.push(` kimal3 firma daten --firma "${name}"`);
47
+ zeilen.push(` kimal3 firma daten --firma ${alsWort(name, true)}`);
46
48
  }
47
49
  else {
48
50
  zeilen.push(` „${name}" — kommt ${wieOft}× vor und ist über den Namen nicht wählbar.`);
package/dist/kimal3.js CHANGED
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { existsSync, readFileSync } from "node:fs";
15
15
  import { BASIS_URL, leseAnmeldung } from "./anmeldung.js";
16
- import { gemerkterHinweis, NichtAngemeldet } from "./api.js";
16
+ import { gemerkterHinweis, lokalerKontextStandJetzt, NichtAngemeldet } from "./api.js";
17
17
  import { leseGesehen, merkeGesehen } from "./gesehen.js";
18
18
  import { formatiere, heuteAus, skillMeldung, skillZustand, sollZeigen, } from "./nachricht.js";
19
19
  import { eigeneVersion } from "./version.js";
@@ -99,8 +99,26 @@ function hinweisText() {
99
99
  if (eigene === null)
100
100
  return null;
101
101
  const heute = heuteAus(new Date());
102
+ /*
103
+ 🔴 HIER, UND NICHT FRUEHER (offener Punkt A-4). Diese Zeile liest den
104
+ Merkzettel auf der Platte in genau dem Augenblick, in dem der Hinweis
105
+ ausgegeben werden soll -- also NACH dem Befehl.
106
+
107
+ Genau darin lag der Fehler: Den Stand, den der Server fuer seinen
108
+ Hinweis vergleicht, schickt die CLI im Kopf mit, und der wird VOR dem
109
+ Netzaufruf gelesen (`kontextStandKopf` in `api.ts`). Direkt nach
110
+ `kimal3 kontext runter` stand deshalb "Dein Firmenkontext ist aktuell."
111
+ und darunter der Rat, `kimal3 kontext runter` auszufuehren. Der zweite
112
+ Aufruf schwieg dann von selbst -- die Meldung kam schlicht einen Aufruf
113
+ zu spaet.
114
+
115
+ 🔑 `sollZeigen` VERLANGT diesen Wert (kein optionales Argument): Wer ihn
116
+ hier vergisst, bekommt einen Uebersetzungsfehler statt einer Rechnung,
117
+ die niemand mehr benutzt.
118
+ */
119
+ const standJetzt = lokalerKontextStandJetzt();
102
120
  const h = gemerkterHinweis();
103
- if (h && sollZeigen(h, eigene, leseGesehen(), heute)) {
121
+ if (h && sollZeigen(h, eigene, leseGesehen(), heute, standJetzt)) {
104
122
  // Hoechstens EIN Hinweis je Aufruf -- zwei sind eine Wand.
105
123
  return { text: formatiere(h), kennung: h.id, tag: heute };
106
124
  }