kimal3 0.2.9 → 0.3.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.
Files changed (67) hide show
  1. package/README.md +135 -0
  2. package/dist/api.js +16 -7
  3. package/dist/arbeitsplatz-dateien.js +62 -30
  4. package/dist/argumente.js +25 -97
  5. package/dist/befehle/arbeitsplatz.js +51 -68
  6. package/dist/befehle/firma-daten.js +24 -26
  7. package/dist/befehle/kontext-fragen.js +62 -44
  8. package/dist/befehle/kontext-gemeinsam.js +86 -43
  9. package/dist/befehle/kontext-hoch.js +40 -46
  10. package/dist/befehle/kontext-runter.js +70 -51
  11. package/dist/befehle/login.js +23 -9
  12. package/dist/befehle/logout.js +9 -4
  13. package/dist/befehle/projekt-liste.js +24 -29
  14. package/dist/befehle/projekt-neu.js +25 -31
  15. package/dist/befehle/skill-install.js +78 -49
  16. package/dist/befehle/skill-tausch.js +28 -0
  17. package/dist/befehle/wache.js +12 -6
  18. package/dist/befehle/werkbank.js +118 -88
  19. package/dist/befehle-katalog-en.js +185 -0
  20. package/dist/befehle-katalog.js +10 -49
  21. package/dist/befehlswoerter.js +69 -0
  22. package/dist/einstellungen-mischen.js +2 -2
  23. package/dist/einstellungen-schreiben.js +8 -5
  24. package/dist/fehler.js +5 -5
  25. package/dist/firmen-liste.js +42 -5
  26. package/dist/format.js +41 -0
  27. package/dist/fragen-lesen.js +3 -3
  28. package/dist/hilfe.js +24 -0
  29. package/dist/kimal3.js +77 -71
  30. package/dist/kontext-dateien.js +30 -23
  31. package/dist/kontext-hoch-zeile.js +5 -3
  32. package/dist/kontext-rat.js +151 -0
  33. package/dist/kontext-stand.js +68 -210
  34. package/dist/nachricht.js +39 -15
  35. package/dist/projekt-neu-firma.js +9 -9
  36. package/dist/projekt-zeilen.js +40 -54
  37. package/dist/slash-befehle.js +36 -0
  38. package/dist/sprache.js +114 -0
  39. package/dist/texte/allgemein.js +99 -0
  40. package/dist/texte/arbeitsplatz.js +135 -0
  41. package/dist/texte/kontext-hoch.js +128 -0
  42. package/dist/texte/kontext.js +171 -0
  43. package/dist/texte/projekte.js +126 -0
  44. package/dist/texte/skill.js +87 -0
  45. package/dist/texte/wache.js +36 -0
  46. package/dist/texte/werkbank.js +161 -0
  47. package/dist/wache-rechnung.js +27 -40
  48. package/dist/wache-schalter.js +40 -5
  49. package/dist/werkbank-name.js +28 -10
  50. package/dist/wort.js +98 -0
  51. package/package.json +27 -27
  52. package/skill/SKILL.md +18 -10
  53. package/skill/commands/k3.md +1 -1
  54. package/skill/commands/k3feierabend.md +99 -97
  55. package/skill/commands/k3kontext.md +2 -2
  56. package/skill/commands/k3kurs.md +6 -5
  57. package/skill/commands/k3start.md +2 -1
  58. package/skill/en/SKILL.md +451 -0
  59. package/skill/en/commands/k3.md +106 -0
  60. package/skill/en/commands/k3analyse.md +59 -0
  61. package/skill/en/commands/k3context.md +92 -0
  62. package/skill/en/commands/k3course.md +54 -0
  63. package/skill/en/commands/k3list.md +26 -0
  64. package/skill/en/commands/k3project.md +50 -0
  65. package/skill/en/commands/k3start.md +47 -0
  66. package/skill/en/commands/k3wrapup.md +99 -0
  67. package/dist/zz-kaputt.js +0 -1
@@ -1,9 +1,13 @@
1
1
  import { resolve } from "node:path";
2
2
  import { FIRMEN_OPTIONEN, dateienSchreiben, vorlageHolen, werkbankOrdner, istUnsichererOrdnername, } from "../arbeitsplatz-dateien.js";
3
3
  import { alsBefehlszeile, alsWort, trenneNamenUndOptionen, zerlege } from "../argumente.js";
4
+ import { befehlIn } from "../befehlswoerter.js";
5
+ import { slashIn } from "../slash-befehle.js";
6
+ import { sprache } from "../sprache.js";
7
+ import { ARBEITSPLATZ_TEXTE } from "../texte/arbeitsplatz.js";
4
8
  import { fragenAus, traegtFragenVon } from "../fragen-lesen.js";
5
- import { FIRMEN_TEXTE } from "../kontext-stand.js";
6
- import { werkbankOrdnerAus, werkbankUmbenennen } from "../werkbank-name.js";
9
+ import { ORDNER_NAMEN, firmenTexte } from "../kontext-stand.js";
10
+ import { werkbankOrdnerAus, werkbankSpracheAus, werkbankUmbenennen } from "../werkbank-name.js";
7
11
  /**
8
12
  * Holt die KI-Arbeitsplatz-Vorlage vom Server und legt sie lokal an --
9
13
  * `CLAUDE.md`, Takt-Befehle, die Werkbank fuer eigene Projekte.
@@ -25,32 +29,10 @@ import { werkbankOrdnerAus, werkbankUmbenennen } from "../werkbank-name.js";
25
29
  * 🔁 Die Schreib-Maschinerie wohnt seit dem 2026-08-20 in
26
30
  * `../arbeitsplatz-dateien.ts`, weil `werkbank neu` dieselbe braucht.
27
31
  */
28
- const HILFE = `
29
- kimal3 arbeitsplatz neu [dein-vorname] [--firma "Name" | --firma-id "…"]
30
-
31
- Holt deine KI-Arbeitsplatz-Vorlage und legt sie hier als Ordner an --
32
- die ganze Familie: Firmenebene, Werkbank, Beispielprojekt.
33
-
34
- Der Ordner heisst IMMER "KI-Arbeitsplatz". Mehrere Firmen trennst du
35
- ueber den Ordner darueber, z. B. Kunden/ARNO/KI-Arbeitsplatz.
36
-
37
- dein-vorname Optional. Er benennt die WERKBANK darin -- aus "sabine"
38
- wird "werkbank-sabine". Ohne Angabe steht dort der Name,
39
- den der Server aus deiner Anmelde-Adresse gebildet hat.
40
-
41
- --firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst -- der
42
- Firmenkontext kommt gleich mit in den Ordner.
43
- Die Absage nennt dir die Namen als fertige Befehle.
44
-
45
- --firma-id "…" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
46
- Auch diese Absage nennt dir fertige Befehle.
47
-
48
- 🔴 Bricht ab, wenn der Zielordner schon existiert und NICHT leer ist --
49
- eigene Arbeit wird dadurch nie versehentlich ueberschrieben.
50
-
51
- Du hast schon einen Firmen-Ordner und brauchst nur eine eigene Werkbank?
52
- kimal3 werkbank neu <dein-vorname>
53
- `;
32
+ /** Die Hilfe von `arbeitsplatz neu` / `workplace new` in der Sprache des Menschen. */
33
+ export function hilfe(s) {
34
+ return ARBEITSPLATZ_TEXTE[s].help;
35
+ }
54
36
  /*
55
37
  🔴 **Der Rat in den Absagen hat sich am 2026-08-20 UMGEDREHT** -- und das ist
56
38
  der Kern der Umbenennung, nicht ihr Nebeneffekt.
@@ -85,17 +67,13 @@ const HILFE = `
85
67
  * fest im Text, waere das eine zweite Wahrheit ueber den Aufbau — dieselbe, die
86
68
  * Runde 3 zu PR #24 auf `/pro` gefunden hat.
87
69
  */
88
- function hinweisFuer(ordnerName) {
89
- const ziel = alsWort(ordnerName);
90
- return (" Steht dein Arbeitsplatz schon und dir fehlt nur die eigene Werkbank?\n" +
91
- " Dann geh HINEIN und leg sie dort an:\n" +
92
- ` cd ${ziel}\n` +
93
- " kimal3 werkbank neu <dein-vorname>");
70
+ function hinweisFuer(ordnerName, s) {
71
+ return ARBEITSPLATZ_TEXTE[s].adviceWorkbenchOnly(alsWort(ordnerName), befehlIn("werkbank neu", s)); // kein-anzeigetext: Befehlsschlüssel
94
72
  }
95
- export function fragenstandAus(dateien) {
73
+ export function fragenstandAus(dateien, sprache) {
96
74
  let offen = 0;
97
75
  let gesamt = 0;
98
- for (const text of FIRMEN_TEXTE) {
76
+ for (const text of firmenTexte(sprache)) {
99
77
  const geliefert = dateien.find((d) => d.pfad === text.pfad);
100
78
  if (geliefert === undefined)
101
79
  return { art: "unbekannt" };
@@ -114,8 +92,10 @@ export function fragenstandAus(dateien) {
114
92
  return offen === 0 ? { art: "keine" } : { art: "offen", offen, gesamt };
115
93
  }
116
94
  export async function arbeitsplatzNeu(argv) {
95
+ let s = sprache();
96
+ let t = ARBEITSPLATZ_TEXTE[s];
117
97
  if (argv.includes("--help") || argv.includes("-h")) {
118
- console.log(HILFE);
98
+ console.log(hilfe(s));
119
99
  return 0;
120
100
  }
121
101
  /*
@@ -138,7 +118,7 @@ export async function arbeitsplatzNeu(argv) {
138
118
  const { woerter, optionen: optionsArgv } = trenneNamenUndOptionen(argv);
139
119
  const nameArg = woerter[0];
140
120
  if (nameArg !== undefined && nameArg.trim() === "") {
141
- console.error(`Ein leerer Name ist kein gueltiger Name.\n${HILFE}`);
121
+ console.error(t.emptyName + hilfe(s));
142
122
  return 1;
143
123
  }
144
124
  if (woerter.length > 1) {
@@ -166,20 +146,18 @@ export async function arbeitsplatzNeu(argv) {
166
146
  */
167
147
  const optionsAnhang = optionsArgv.length === 0 ? "" : ` ${alsBefehlszeile(optionsArgv)}`;
168
148
  const vorschlag = einfacheWoerter.length === woerter.length
169
- ? `Besteht dein Name aus mehreren Woertern? Dann schreib ihn zusammen:\n` +
170
- ` kimal3 arbeitsplatz neu ${einfacheWoerter.join("-")}${optionsAnhang}\n`
149
+ ? t.nameSuggestion(befehlIn("arbeitsplatz neu", s), einfacheWoerter.join("-"), optionsAnhang) // kein-anzeigetext: Befehlsschlüssel
171
150
  : "";
172
- console.error(`Mit „${woerter[1]}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n` +
173
- `${vorschlag}${HILFE}`);
151
+ console.error(t.onlyOneName(woerter[1]) + vorschlag + hilfe(s));
174
152
  return 1;
175
153
  }
176
154
  /*
177
155
  🔑 Erst JETZT die Optionen -- nach den Namens-Absagen, damit ein Mensch mit
178
156
  zwei Vornamen die Schreibhilfe bekommt und nicht eine Options-Meldung.
179
157
  */
180
- const zerlegt = zerlege(optionsArgv, FIRMEN_OPTIONEN);
158
+ const zerlegt = zerlege(optionsArgv, FIRMEN_OPTIONEN, s);
181
159
  if (!zerlegt.ok) {
182
- console.error(`${zerlegt.meldung}\n${HILFE}`);
160
+ console.error(`${zerlegt.meldung}\n${hilfe(s)}`);
183
161
  return 1;
184
162
  }
185
163
  const wunsch = {
@@ -193,14 +171,14 @@ export async function arbeitsplatzNeu(argv) {
193
171
  HIER ein Fehler, weil ein Mensch etwas getippt hat und eine Antwort verdient
194
172
  -- der Server schweigt im selben Fall und bleibt beim Vordruck.
195
173
  */
196
- let gewuenschteWerkbank = null;
174
+ // 🌐 Teil 2: Hier geht es nur um „bleibt etwas uebrig?" -- das Praefix ist egal. Welches es
175
+ // wird, sagt erst die Lieferung (die Werkbank vom Server traegt die Kontosprache), s. unten.
197
176
  if (nameArg !== undefined) {
198
- gewuenschteWerkbank = werkbankOrdnerAus(nameArg);
199
- if (gewuenschteWerkbank === null) {
200
- console.error(`Aus „${nameArg}" kann ich keinen Ordnernamen machen -- es bleiben nur`);
201
- console.error("Zeichen uebrig, die in keinem Ordnernamen stehen duerfen.");
177
+ if (werkbankOrdnerAus(nameArg, "de") === null) {
178
+ console.error(t.unusableName1(nameArg));
179
+ console.error(t.unusableName2);
202
180
  console.error("");
203
- console.error(" Nimm deinen Vornamen: kimal3 arbeitsplatz neu sabine");
181
+ console.error(t.unusableName3(befehlIn("arbeitsplatz neu", s))); // kein-anzeigetext: Befehlsschlüssel
204
182
  return 1;
205
183
  }
206
184
  }
@@ -218,12 +196,13 @@ export async function arbeitsplatzNeu(argv) {
218
196
  hineinzuschreiben -- sonst hiesse seine Werkbank nach dem zweiten Versuch
219
197
  anders als beim ersten gewollt, und niemand saehe, woran es lag.
220
198
  */
221
- const befehlsname = nameArg === undefined
222
- ? "arbeitsplatz neu"
223
- : `arbeitsplatz neu ${alsWort(nameArg)}`;
224
- const holen = await vorlageHolen(befehlsname, wunsch);
199
+ // `null`: Der Ordner entsteht hier erst -- es gibt keine Firma, die man aus ihm lesen koennte.
200
+ const holen = await vorlageHolen("arbeitsplatz neu", nameArg === undefined ? "" : ` ${alsWort(nameArg)}`, wunsch, null); // kein-anzeigetext: Befehlsschlüssel
225
201
  if (!holen.ok)
226
202
  return holen.wert;
203
+ // 🌐 Die Antwort kann die Kontosprache umgestellt haben (Spec 3.2, Schritt 3): Was ab hier gedruckt wird, in der neuen.
204
+ s = sprache();
205
+ t = ARBEITSPLATZ_TEXTE[s];
227
206
  const daten = holen.daten;
228
207
  // 🔴 R2-1: `daten.wurzel` kommt genauso ROH vom Server wie jeder `datei.pfad`
229
208
  // -- dieselbe Gefahr (manipulierte Antwort, z. B. ueber ein umgebogenes
@@ -243,7 +222,7 @@ export async function arbeitsplatzNeu(argv) {
243
222
  // `resolve(".")` ist das aktuelle Verzeichnis, und in einem leeren Ordner
244
223
  // waere die ganze Familie einfach dorthin gefallen.
245
224
  if (istUnsichererOrdnername(daten.wurzel)) {
246
- console.error(`Die Antwort enthaelt einen unsicheren Ordnernamen: „${daten.wurzel}". Bitte melden.`);
225
+ console.error(t.unsafeFolderName(daten.wurzel));
247
226
  return 3;
248
227
  }
249
228
  /*
@@ -286,22 +265,25 @@ export async function arbeitsplatzNeu(argv) {
286
265
  auf dieselbe Wirklichkeit zu geben.
287
266
  */
288
267
  if (werkbankVomServer === null) {
289
- console.error("In der Vorlage steckt kein Werkbank-Ordner -- damit fehlt der Teil,");
290
- console.error("in dem du arbeiten wuerdest. Das liegt nicht an dir. Bitte melden.");
268
+ console.error(t.noWorkbank1);
269
+ console.error(t.noWorkbank2);
291
270
  return 3;
292
271
  }
272
+ // 🌐 Teil 2: Das Praefix der Lieferung ist die Kontosprache (`/pro` verspricht englischen
273
+ // Konten `workbench-sabine`). Ein getipptes fremdes Praefix wird ersetzt, nie verdoppelt.
274
+ const gewuenschteWerkbank = nameArg === undefined ? null : werkbankOrdnerAus(nameArg, werkbankSpracheAus(werkbankVomServer) ?? "de");
293
275
  const dateien = gewuenschteWerkbank !== null
294
276
  ? werkbankUmbenennen(daten.dateien, werkbankVomServer, gewuenschteWerkbank)
295
277
  : daten.dateien;
296
278
  const ordnerName = daten.wurzel;
297
- const wert = dateienSchreiben(dateien, ordnerName, hinweisFuer(ordnerName));
279
+ const wert = dateienSchreiben(dateien, ordnerName, hinweisFuer(ordnerName, s));
298
280
  if (wert !== 0)
299
281
  return wert;
300
282
  // ⚠️ Der ABSOLUTE Pfad, nicht der eingegebene Name. Beim Umzug der
301
283
  // Schreib-Maschinerie (2026-08-20) war das kurz ein relativer Name -- ein
302
284
  // Rueckschritt: Der Mensch soll sehen, WO der Ordner gelandet ist, und genau
303
285
  // das ist der Grund, warum hier `resolve()` steht und nicht `ordnerName`.
304
- console.log(`✓ ${dateien.length} ${dateien.length === 1 ? "Datei" : "Dateien"} angelegt.`);
286
+ console.log(t.filesCreated(dateien.length));
305
287
  console.log(` ${resolve(ordnerName)}`);
306
288
  /*
307
289
  🔴 **Der Erfolgsfall muss sagen, was das Argument getan hat** (Review-Runde 5
@@ -318,19 +300,19 @@ export async function arbeitsplatzNeu(argv) {
318
300
  dort, wo es geklappt hat, nicht nur dort, wo es schiefging.*
319
301
  */
320
302
  if (gewuenschteWerkbank !== null) {
321
- console.log(` Deine Werkbank darin heisst „${gewuenschteWerkbank}".`);
303
+ console.log(t.workbenchNamed(gewuenschteWerkbank));
322
304
  }
323
305
  console.log("");
324
- console.log(" Weiter geht's:");
306
+ console.log(t.nextStepsHeader);
325
307
  // ⚠️ NACH dem Umbenennen ableiten, nicht davor -- sonst zeigte der Weg-Hinweis
326
308
  // in die Werkbank, die der Server geschickt hat, statt in die, die auf der
327
309
  // Platte liegt. *Eine Ableitung, die eine Zeile zu frueh gelesen wird, fuehrt
328
310
  // zuverlaessig ins Leere.*
329
311
  const werkbank = werkbankOrdner(dateien);
330
312
  const cdZiel = werkbank !== null ? `${ordnerName}/${werkbank}` : ordnerName;
331
- console.log(` cd ${alsWort(cdZiel)}`);
332
- console.log(" claude");
333
- console.log(' dann "Lass uns einrichten" tippen');
313
+ console.log(` cd ${alsWort(cdZiel)}`); // kein-anzeigetext: Befehlszeile zum Kopieren
314
+ console.log(" claude"); // kein-anzeigetext: Befehlszeile zum Kopieren
315
+ console.log(t.nextStepsSay);
334
316
  /*
335
317
  🔴 **HIER RISS DIE KETTE.**
336
318
  Bis zum 2026-08-28 endete dieser Befehl immer mit denselben drei Zeilen: Der
@@ -371,12 +353,13 @@ export async function arbeitsplatzNeu(argv) {
371
353
  daran nichts mehr aendern. Deshalb steht das hier ganz unten und kann nur
372
354
  noch schweigen, nie abbrechen.
373
355
  */
374
- const stand = fragenstandAus(dateien);
356
+ // Die Wurzel kommt vom Server und sagt die Sprache des gemeinsamen Teils.
357
+ const stand = fragenstandAus(dateien, daten.wurzel === ORDNER_NAMEN.en.wurzel ? "en" : "de");
375
358
  if (stand.art === "offen") {
376
359
  console.log("");
377
- console.log(` Danach: ${stand.offen} von ${stand.gesamt} Fragen über deine Firma sind noch offen.`);
378
- console.log(" Claude stellt sie dir einzeln und trägt deine Antworten ein:");
379
- console.log(" /k3kontext");
360
+ console.log(t.questionsOpen(stand.offen, stand.gesamt));
361
+ console.log(t.questionsAskedOneByOne);
362
+ console.log(` /${slashIn("k3kontext", sprache())}`); // kein-anzeigetext: Slash-Befehl aus SLASH_PAARE
380
363
  }
381
364
  return 0;
382
365
  }
@@ -1,4 +1,7 @@
1
1
  import { zerlege } from "../argumente.js";
2
+ import { sprache } from "../sprache.js";
3
+ import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
4
+ import { PROJEKTE_TEXTE } from "../texte/projekte.js";
2
5
  import { feld, ruf } from "../api.js";
3
6
  import { schlussFehler } from "../fehler.js";
4
7
  import { firmenIdZeilen, firmenZeilen } from "../firmen-liste.js";
@@ -17,29 +20,21 @@ const ERLAUBT = ["firma", "firma-id"];
17
20
  * Ueberschrift dazudruckt, macht die Ausgabe als JSON unlesbar — alles fuer
18
21
  * Menschen gehoert nach stderr (`console.error`).
19
22
  */
20
- const HILFE = `
21
- kimal3 firma daten [--firma "Name" | --firma-id "…"]
22
-
23
- Holt die Daten deiner Firma als JSON: Kennzahlen, Menschen, Projekte.
24
- Die Auswertung macht Claude daraus selbst.
25
-
26
- --firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst.
27
- Die Absage nennt dir die Namen als fertige Befehle.
28
-
29
- --firma-id "…" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
30
- Auch diese Absage nennt dir fertige Befehle -- du musst
31
- nichts umbenennen und nichts nachschlagen.
32
- `;
23
+ export function hilfe(s) {
24
+ return PROJEKTE_TEXTE[s].dataHelp;
25
+ }
33
26
  export async function firmaDaten(argv) {
27
+ let s = sprache();
28
+ let a = ALLGEMEIN_TEXTE[s];
34
29
  // ⚠️ VOR `zerlege`, genau wie bei `projekt neu`: Ein Schalter ist keine
35
30
  // Option-mit-Wert, `--help` scheiterte dort sonst mit "fehlt ihr Wert".
36
31
  // Die Terminal-Hilfe wirbt mit "Mehr zu einem Befehl: … --help" -- ein
37
32
  // Befehl, der darauf mit einem Fehler antwortet, macht die Werbung zur Luege.
38
33
  if (argv.includes("--help") || argv.includes("-h")) {
39
- console.log(HILFE);
34
+ console.log(hilfe(s));
40
35
  return 0;
41
36
  }
42
- const zerlegt = zerlege(argv, ERLAUBT);
37
+ const zerlegt = zerlege(argv, ERLAUBT, s);
43
38
  if (!zerlegt.ok) {
44
39
  console.error(zerlegt.meldung);
45
40
  return 1;
@@ -65,8 +60,11 @@ export async function firmaDaten(argv) {
65
60
  suche.set("firma", wunsch);
66
61
  if (wunschId !== undefined)
67
62
  suche.set("firma-id", wunschId);
68
- const pfad = suche.toString() ? `/api/cli/firma?${suche.toString()}` : "/api/cli/firma";
69
- const antwort = await ruf(pfad, { methode: "GET", mitAusweis: true });
63
+ const pfad = suche.toString() ? `/api/cli/firma?${suche.toString()}` : "/api/cli/firma"; // kein-anzeigetext: Serverpfad
64
+ const antwort = await ruf(pfad, { methode: "GET", mitAusweis: true }); // kein-anzeigetext: HTTP-Methode
65
+ // 🌐 Die Antwort kann die Kontosprache umgestellt haben (Spec 3.2, Schritt 3): Was ab hier gedruckt wird, in der neuen.
66
+ s = sprache();
67
+ a = ALLGEMEIN_TEXTE[s];
70
68
  if (antwort.status === 200) {
71
69
  // 🔴 Ein `200` mit unlesbarem Rumpf ist KEIN Erfolg (Review-Befund Runde 1):
72
70
  // `ruf` verschluckt Parse-Fehler zu `null`. Ein Zwischending im Netz --
@@ -75,13 +73,13 @@ export async function firmaDaten(argv) {
75
73
  // Und die 0 ist die Schnittstelle: Claude liest sie als "erledigt" und
76
74
  // haelt `null` fuer die Daten der Firma.
77
75
  if (antwort.koerper === null || typeof antwort.koerper !== "object") {
78
- console.error("Die Antwort war nicht lesbar. Bitte gleich noch einmal.");
76
+ console.error(a.unreadableAnswer);
79
77
  return 3;
80
78
  }
81
79
  console.log(JSON.stringify(antwort.koerper, null, 2));
82
80
  return 0;
83
81
  }
84
- const meldung = feld(antwort.koerper, "meldung") ?? "Das hat nicht geklappt.";
82
+ const meldung = feld(antwort.koerper, "meldung") ?? a.genericFailure;
85
83
  // 402 VOR 401: Beide heissen "kommst hier nicht rein", verlangen aber
86
84
  // Gegenteiliges. Wer sie zusammenwirft, raet dem Menschen `kimal3 login` --
87
85
  // und der meldet sich dann erfolgreich an, ohne dass es etwas aendert.
@@ -90,7 +88,7 @@ export async function firmaDaten(argv) {
90
88
  return 4;
91
89
  }
92
90
  if (antwort.status === 401) {
93
- console.error(`${meldung} Bitte \`kimal3 login\` ausführen.`);
91
+ console.error(`${meldung}${a.pleaseSignIn}`);
94
92
  return 2;
95
93
  }
96
94
  if (antwort.status === 409) {
@@ -112,8 +110,8 @@ export async function firmaDaten(argv) {
112
110
  typeof x.name === "string")
113
111
  : [];
114
112
  if (auswahl.length > 0) {
115
- console.error("\n Diese beiden heissen gleich — nimm die ID:");
116
- firmenIdZeilen(auswahl).forEach((z) => console.error(z));
113
+ console.error(a.sameNameUseId);
114
+ firmenIdZeilen(auswahl, s).forEach((z) => console.error(z));
117
115
  }
118
116
  else {
119
117
  // Die Namen als FERTIGE Befehle ausgeben, nicht als blosse Liste: Der
@@ -123,14 +121,14 @@ export async function firmaDaten(argv) {
123
121
  // ⚠️ Welche Namen einen Befehl bekommen, entscheidet `firmenZeilen` (B-5):
124
122
  // Ein doppelt vergebener Name bekommt KEINEN -- er waere ein Vorschlag,
125
123
  // der garantiert wieder in derselben Absage endet.
126
- const zeilen = firmenZeilen(namen);
124
+ const zeilen = firmenZeilen(namen, s);
127
125
  if (zeilen.length > 0) {
128
- console.error("\n Deine Firmen:");
126
+ console.error(a.yourCompanies);
129
127
  zeilen.forEach((z) => console.error(z));
130
128
  }
131
129
  }
132
130
  if (grund === "keine-firma") {
133
- console.error(`\n Eine Firma anlegen: ${BASIS_URL}/firma/neu`);
131
+ console.error(a.createCompany(BASIS_URL));
134
132
  }
135
133
  return 1;
136
134
  }
@@ -141,7 +139,7 @@ export async function firmaDaten(argv) {
141
139
  // 🔑 Den Rest entscheidet `schlussFehler` fuer alle Befehle gemeinsam --
142
140
  // insbesondere die `404` (B-7), die hier frueher auf die `1` fiel und damit
143
141
  // "Eingabe passt nicht" meldete, obwohl die Eingabe stimmte.
144
- const schluss = schlussFehler(antwort.status, meldung);
142
+ const schluss = schlussFehler(antwort.status, meldung, s);
145
143
  console.error(schluss.text);
146
144
  return schluss.wert;
147
145
  }
@@ -1,4 +1,7 @@
1
- import { FIRMEN_TEXTE } from "../kontext-stand.js";
1
+ import { ORDNER_NAMEN, firmenTexte } from "../kontext-stand.js";
2
+ import { sprache } from "../sprache.js";
3
+ import { KONTEXT_TEXTE } from "../texte/kontext.js";
4
+ import { KONTEXT_HOCH_TEXTE } from "../texte/kontext-hoch.js";
2
5
  import { fragenAus, traegtFragenVon } from "../fragen-lesen.js";
3
6
  import { leseEineDatei } from "../kontext-dateien.js";
4
7
  import { arbeitsplatzOderAbsage, fremdeDateiAbsage } from "./kontext-gemeinsam.js";
@@ -17,7 +20,7 @@ import { arbeitsplatzOderAbsage, fremdeDateiAbsage } from "./kontext-gemeinsam.j
17
20
  * wenn der Ausweis abgelaufen ist, und deshalb kann er nichts kaputt machen:
18
21
  * **er liest, er schreibt nirgends.**
19
22
  *
20
- * ⚠️ **Die Reihenfolge ist die von `FIRMEN_TEXTE`** -- dieselbe wie die der
23
+ * ⚠️ **Die Reihenfolge ist die von `firmenTexte`** -- dieselbe wie die der
21
24
  * Felder im Formular auf `/firma/kontext`. Wer beides nebeneinander hat, findet
22
25
  * dieselbe Frage an derselben Stelle.
23
26
  */
@@ -28,20 +31,9 @@ import { arbeitsplatzOderAbsage, fremdeDateiAbsage } from "./kontext-gemeinsam.j
28
31
  Erfolgsausgabe derselben Datei Umlaute setzte: *eine Datei, zwei Schreibweisen,
29
32
  und der Unterschied war nicht zu begruenden.*
30
33
  */
31
- const HILFE = `
32
- kimal3 kontext fragen
33
-
34
- Zeigt, welche Fragen über deine Firma in kontext/ noch unbeantwortet sind —
35
- mit Datei, Anker, Frage und Hinweis.
36
-
37
- Der Befehl liest nur. Er braucht weder Netz noch Anmeldung und ändert nichts.
38
-
39
- Antworten trägst du in die genannte Datei unter ihre Frage ein; danach
40
- schickt "kimal3 kontext hoch" sie an die Firma (nur als Admin).
41
- `;
42
- /** `[optional]` hinter einer Frage, die keine Pflicht ist -- sonst nichts. */
43
- function zusatz(frage) {
44
- return frage.pflicht ? "" : " [optional]";
34
+ /** Die Hilfe von `kontext fragen` / `context questions` in der Sprache des Menschen. */
35
+ export function hilfe(s) {
36
+ return KONTEXT_HOCH_TEXTE[s].questionsHelp;
45
37
  }
46
38
  /*
47
39
  ⚠️ **Bewusst NICHT `async`, anders als die Nachbarn `kontextRunter` und
@@ -51,8 +43,10 @@ function zusatz(frage) {
51
43
  Der Verteiler in `kimal3.ts` ist selbst `async` und nimmt beides.
52
44
  */
53
45
  export function kontextFragen(argv) {
46
+ const s = sprache();
47
+ const t = KONTEXT_HOCH_TEXTE[s];
54
48
  if (argv.includes("--help") || argv.includes("-h")) {
55
- console.log(HILFE);
49
+ console.log(hilfe(s));
56
50
  return 0;
57
51
  }
58
52
  /*
@@ -63,12 +57,12 @@ export function kontextFragen(argv) {
63
57
  🔴 Geprueft wird auf JEDES Argument, nicht nur auf solche mit `-` davor. Ein
64
58
  stillschweigend geschlucktes `--firma "Nordwind"` waere schlimmer als eine
65
59
  Absage -- der Mensch glaubte dann, er habe eine Firma ausgewaehlt -- und ein
66
- verschlucktes `marke` genauso: Er saehe alle 17 Fragen und hielte sie fuer
60
+ verschlucktes `marke` genauso: Er saehe alle Fragen und hielte sie fuer
67
61
  die einer Datei.
68
62
  */
69
63
  if (argv.length > 0) {
70
- console.error(`„${argv[0]}" kenne ich hier nicht — dieser Befehl nimmt keine Angaben.`);
71
- console.error(" Er liest die fünf Dateien in kontext/ neben dir und gibt aus, was offen ist.");
64
+ console.error(t.noArguments(argv[0]));
65
+ console.error(t.readsFiles);
72
66
  return 1;
73
67
  }
74
68
  const ordner = arbeitsplatzOderAbsage();
@@ -82,15 +76,16 @@ export function kontextFragen(argv) {
82
76
  Abbruch, der sagt warum, als ein halber Stand, den niemand benennen kann.
83
77
  */
84
78
  const gelesen = [];
85
- for (const datei of FIRMEN_TEXTE) {
86
- const antwort = leseEineDatei(ordner, datei.pfad);
79
+ const texte = firmenTexte(ordner.sprache);
80
+ for (const datei of texte) {
81
+ const antwort = leseEineDatei(ordner.pfad, datei.pfad);
87
82
  if (!antwort.ok) {
88
83
  console.error(antwort.meldung);
89
84
  return 1;
90
85
  }
91
86
  if (antwort.inhalt === null) {
92
- console.error(`„${datei.pfad}" gibt es hier nicht — dann fehlen mir ihre Fragen.`);
93
- console.error(" `kimal3 kontext runter` holt die Datei; danach noch einmal.");
87
+ console.error(t.questionsFileMissing(datei.pfad));
88
+ console.error(t.questionsFetchFirst);
94
89
  return 1;
95
90
  }
96
91
  /*
@@ -108,11 +103,24 @@ export function kontextFragen(argv) {
108
103
  benennen, sind zwei Wahrheiten.
109
104
  */
110
105
  if (!traegtFragenVon(antwort.inhalt, datei.ankerPraefix)) {
111
- fremdeDateiAbsage(datei.pfad, datei.titel, datei.ankerPraefix, "Ich kann ihre Fragen deshalb nicht zählen — und zähle dann gar nicht.");
106
+ fremdeDateiAbsage(datei.pfad, datei.feld, datei.ankerPraefix, t.cannotCount);
112
107
  return 1;
113
108
  }
114
- gelesen.push({ pfad: datei.pfad, titel: datei.titel, fragen: fragenAus(antwort.inhalt) });
109
+ gelesen.push({ pfad: datei.pfad, feld: datei.feld, fragen: fragenAus(antwort.inhalt) });
115
110
  }
111
+ fragenZeilen(gelesen, ordner, s).forEach((z) => console.log(z));
112
+ return 0;
113
+ }
114
+ /**
115
+ * Was `kontext fragen` ausgibt -- als reine Rechnung mit der Sprache des Menschen als Pflichtwert, damit
116
+ * `check:cli` beide Sprachen und beide Zweige (alles beantwortet / noch offen) misst (Teil 3, Task 11).
117
+ * Jedes Element ist EINE `console.log`-Zeile (auch eines mit eingebautem Zeilenumbruch am Ende).
118
+ * ⚠️ Die Fragen, Ueberschriften und Hinweise stehen in den Dateien und kommen unveraendert durch.
119
+ */
120
+ export function fragenZeilen(gelesen, ordner, s) {
121
+ const t = KONTEXT_HOCH_TEXTE[s];
122
+ const texte = firmenTexte(ordner.sprache);
123
+ const zeilen = [];
116
124
  const gesamt = gelesen.reduce((summe, d) => summe + d.fragen.length, 0);
117
125
  const offen = gelesen.reduce((summe, d) => summe + d.fragen.filter((f) => f.antwort === "").length, 0);
118
126
  /*
@@ -127,25 +135,35 @@ export function kontextFragen(argv) {
127
135
  deckt.*
128
136
  */
129
137
  if (offen === 0) {
130
- console.log(`✓ Alle ${gesamt} Fragen, die in deinen Dateien stehen, sind beantwortet.`);
138
+ zeilen.push(t.allAnswered(gesamt));
131
139
  /*
132
140
  🔴 **DIE ZWEITE ZEILE BENENNT DIE LUECKE, STATT SIE ZU VERSTECKEN**
133
141
  (Review-Befund J5). Gezaehlt wird, was in den fuenf Dateien steht. Wer den
134
142
  Ankerkommentar EINER Frage loescht und sie unbeantwortet laesst, hat sie
135
143
  hier nicht mehr -- `fragenAus` erkennt eine Frage am Anker (Ruling K-14),
136
- die App zusaetzlich am Wortlaut. Dann stuende hier `Alle 16 …`, waehrend
137
- die App weiter „16 von 17" fuehrt.
144
+ die App zusaetzlich am Wortlaut. Dann stuende hier eine Frage weniger,
145
+ als die App fuehrt -- und die App nennte sie weiter offen.
138
146
 
139
147
  🔑 Der Ausweg gehoert in dieselbe Ausgabe wie die Zahl. *Eine Zahl, deren
140
148
  Grenze man kennt, gehoert nicht ohne ihre Grenze hingeschrieben* -- sonst
141
149
  liest ein Mensch „fertig" und die App widerspricht ihm morgen.
142
150
  */
143
- console.log(" Gezählt ist, was in kontext/ steht. Nennt die App unter Firma → Kontext");
144
- console.log(" eine höhere Zahl, fehlt in einer Datei eine Frage — `kimal3 kontext runter`");
145
- console.log(" holt sie zurück.");
146
- return 0;
151
+ zeilen.push(t.countedIs(ORDNER_NAMEN[ordner.sprache].kontext));
152
+ zeilen.push(t.countedHigher);
153
+ // 🔴 Hier stand „`kimal3 kontext runter` holt sie zurück" -- stimmt nur fuer
154
+ // eine FEHLENDE Datei. Eine selbst bearbeitete fasst `runter` nicht an
155
+ // (`lageFuerDatei` -> `lokal-geaendert`), Schlusspruefung 25.09.
156
+ // Und er NENNT die Datei nicht -- dieser Befehl weiss nicht, welche es ist
157
+ // (er zaehlt nur, was da ist). Deshalb der Weg, sie zu finden (Review 25.09.).
158
+ zeilen.push(t.whichFile);
159
+ // Teil 2: das Beispiel mit dem Dateinamen dieses Ordners (`firma.md` oder `company.md`).
160
+ const beispiel = texte[1].pfad.split("/")[1].replace(/\.md$/, "");
161
+ zeilen.push(t.renameIt(beispiel));
162
+ zeilen.push(t.renameThenFresh);
163
+ zeilen.push(t.renameCopyBack);
164
+ return zeilen;
147
165
  }
148
- console.log(`${offen} von ${gesamt} Fragen über deine Firma sind noch offen.\n`);
166
+ zeilen.push(t.openCount(offen, gesamt));
149
167
  /*
150
168
  🔴 **WOHER die Pfade zählen** (Review-Befund 2, 2026-08-28). Die Zeilen
151
169
  darunter nennen `kontext/marke.md` -- einen Pfad RELATIV zu dem Ordner, den
@@ -161,22 +179,22 @@ export function kontextFragen(argv) {
161
179
  `kontext hoch` liest weiter die echten und schickt leere Antworten hoch.
162
180
  *Ein Datenverlust, den niemand bemerkt, weil beide Seiten Erfolg melden.*
163
181
  */
164
- console.log(`Arbeitsplatz: ${ordner}`);
165
- console.log("Die Pfade unten zählen ab hier.\n");
182
+ zeilen.push(t.workplaceLine(ordner.pfad));
183
+ zeilen.push(t.pathsCountFromHere);
166
184
  for (const datei of gelesen) {
167
185
  const offeneDieserDatei = datei.fragen.filter((f) => f.antwort === "");
168
186
  if (offeneDieserDatei.length === 0)
169
187
  continue;
170
- console.log(`${datei.pfad} — ${datei.titel}`);
188
+ zeilen.push(t.fileHeading(datei.pfad, KONTEXT_TEXTE[s].dateiTitel[datei.feld]));
171
189
  for (const frage of offeneDieserDatei) {
172
- console.log(` <!-- k3:${frage.anker} -->${zusatz(frage)}`);
173
- console.log(` ## ${frage.ueberschrift}`);
190
+ zeilen.push(t.anchorLine(frage.anker, !frage.pflicht));
191
+ zeilen.push(t.questionHeading(frage.ueberschrift));
174
192
  if (frage.hinweis !== "")
175
- console.log(` ${frage.hinweis}`);
176
- console.log("");
193
+ zeilen.push(t.questionHint(frage.hinweis));
194
+ zeilen.push("");
177
195
  }
178
196
  }
179
- console.log(" Die Antwort kommt in der genannten Datei unter ihre Überschrift.");
180
- console.log(" Danach als Admin: kimal3 kontext hoch");
181
- return 0;
197
+ zeilen.push(t.answerBelowHeading);
198
+ zeilen.push(t.thenAsAdmin);
199
+ return zeilen;
182
200
  }