kimal3 0.1.6 → 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.
- package/README.md +3 -0
- package/dist/api.js +112 -0
- package/dist/arbeitsplatz-dateien.js +84 -22
- package/dist/argumente.js +150 -0
- package/dist/befehle/arbeitsplatz.js +139 -17
- package/dist/befehle/kontext-fragen.js +182 -0
- package/dist/befehle/kontext-gemeinsam.js +185 -0
- package/dist/befehle/kontext-hoch.js +236 -0
- package/dist/befehle/kontext-runter.js +305 -0
- package/dist/befehle/skill-install.js +51 -17
- package/dist/befehle/werkbank.js +128 -17
- package/dist/befehle-katalog.js +58 -2
- package/dist/firmen-liste.js +4 -2
- package/dist/fragen-lesen.js +178 -0
- package/dist/kimal3.js +29 -2
- package/dist/kontext-dateien.js +248 -0
- package/dist/kontext-stand.js +870 -0
- package/dist/nachricht.js +137 -2
- package/package.json +1 -1
- package/skill/SKILL.md +106 -11
- package/skill/commands/k3.md +59 -18
- package/skill/commands/k3kontext.md +89 -0
- /package/skill/commands/{k3firma.md → k3analyse.md} +0 -0
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/*
|
|
3
|
+
Die reine Rechnung hinter `kimal3 kontext runter` -- und die einzige Stelle,
|
|
4
|
+
an der Server und CLI sich einig sein muessen.
|
|
5
|
+
|
|
6
|
+
🔁 WARUM SIE IN `cli/src/` WOHNT UND NICHT IN `src/lib/arbeitsplatz/`:
|
|
7
|
+
Beide Seiten brauchen sie. Der Server schreibt den Merkzettel beim Ausliefern,
|
|
8
|
+
die CLI rechnet die Pruefsummen auf der Platte des Kunden nach. Die CLI kann
|
|
9
|
+
aber nichts aus `src/` importieren -- eigenes Paket, eigenes tsconfig. An der
|
|
10
|
+
anderen Stelle entstuende also zwangslaeufig eine zweite Kopie, und dann
|
|
11
|
+
hiesse derselbe Ordner je nach Weg "geaendert" oder "unveraendert".
|
|
12
|
+
Dasselbe Muster wie bei `werkbank-name.ts`, aus demselben Grund.
|
|
13
|
+
|
|
14
|
+
⚠️ DIESE DATEI IMPORTIERT NUR `node:crypto`. Kommt hier je ein Import aus
|
|
15
|
+
`./api.js` oder `./anmeldung.js` hinzu, ist sie aus der App nicht mehr lesbar
|
|
16
|
+
-- und die zweite Kopie ist wieder da.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Die Dateien, die der Firma gehoeren und von `kimal3 kontext runter`
|
|
20
|
+
* nachgezogen werden -- die fuenf Fragedateien in `kontext/` und die
|
|
21
|
+
* Firmen-`CLAUDE.md`.
|
|
22
|
+
*
|
|
23
|
+
* 🔑 `CLAUDE.md` ist bewusst dabei (Spec §7c): Aendert die Firma ihre drei
|
|
24
|
+
* Saetze, erreichten sie einen bestehenden Ordner sonst NIE. Und der
|
|
25
|
+
* Nebengewinn ist groesser als der Anlass -- Verbesserungen an unserer eigenen
|
|
26
|
+
* Vorlage erreichen damit auch Ordner, die laengst beim Kunden liegen.
|
|
27
|
+
*
|
|
28
|
+
* 🔴 **`kontext/kurzprofil.md` steht seit Ruling K-24 (Task 7) hier -- und ohne
|
|
29
|
+
* sie war ein Satz unwahr, den wir bereits an Kunden ausliefern.** Die Datei
|
|
30
|
+
* wurde beim ERSTEN Ausliefern gefuellt, ein spaeteres `kimal3 kontext runter`
|
|
31
|
+
* frischte sie aber nie auf. Wer die drei Saetze in der App aenderte und danach
|
|
32
|
+
* den Ordner abglich, bekam still den alten Stand -- waehrend die Datei selbst
|
|
33
|
+
* ihm zusicherte, sie werde „zentral in der App gepflegt". *Ein Abgleich, der
|
|
34
|
+
* eine Datei auslaesst, meldet trotzdem Erfolg.*
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ Ein Merkzettel, der VOR dieser Aenderung geschrieben wurde, fuehrt fuenf
|
|
37
|
+
* Eintraege bei sechs verwalteten Dateien. Der fehlende Eintrag ist KEIN
|
|
38
|
+
* Konflikt: `lageFuerDatei` liest `eintrag: null` als "wir wissen nichts" und
|
|
39
|
+
* sichert, bevor sie ueberschreibt (s. dort). Ist die lokale Datei ohnehin
|
|
40
|
+
* unveraendert, greift die Abkuerzung ganz oben und es passiert gar nichts.
|
|
41
|
+
*
|
|
42
|
+
* ⛔ Die Werkbank-`CLAUDE.md` steht NICHT hier. Sie traegt einen Namen im
|
|
43
|
+
* Ordner und gehoert einem Menschen (Spec §4).
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Die oberste `CLAUDE.md` des Firmen-Ordners -- die einzige verwaltete Datei,
|
|
47
|
+
* die den Firmennamen im Klartext traegt (seit A-11) UND den Weg zum zweiten
|
|
48
|
+
* Menschen mitgeht (sie steht in der Positiv-Liste der `.gitignore`).
|
|
49
|
+
* Deshalb ist sie das Erkennungszeichen in `firmennameAusClaudeMd`.
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ Sie ist zugleich der erste Eintrag von `VERWALTETE_DATEIEN` darunter --
|
|
52
|
+
* dass beide dieselbe Datei meinen, prueft `check:cli`. Dort haengt auch die
|
|
53
|
+
* Pruefung, dass die ECHTE Vorlage ihre Ueberschrift noch so schreibt, wie
|
|
54
|
+
* `firmennameAusClaudeMd` sie liest.
|
|
55
|
+
*/
|
|
56
|
+
export const FIRMEN_CLAUDE_MD = "CLAUDE.md";
|
|
57
|
+
export const VERWALTETE_DATEIEN = [
|
|
58
|
+
"CLAUDE.md",
|
|
59
|
+
"kontext/kurzprofil.md",
|
|
60
|
+
"kontext/firma.md",
|
|
61
|
+
"kontext/marke.md",
|
|
62
|
+
"kontext/systeme.md",
|
|
63
|
+
"kontext/sicherheit.md",
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Die FUENF Texte, die eine Firma selbst pflegt -- welche Datei welches Feld
|
|
67
|
+
* der App fuellt, wie sie dort heisst, und woran man sie wiedererkennt.
|
|
68
|
+
*
|
|
69
|
+
* 🔑 **Warum es diese Tabelle gibt.** `kimal3 kontext hoch` muss jede Datei dem
|
|
70
|
+
* Feld zuordnen, das `/api/cli/kontext` (POST) erwartet. Bis Task 7 leitete
|
|
71
|
+
* eine Funktion den Feldnamen aus dem Dateinamen ab (`marke.md` -> `marke_md`)
|
|
72
|
+
* -- fuer vier Dateien richtig und fuer die fuenfte falsch: `kurzprofil.md`
|
|
73
|
+
* fuellt `drei_saetze`. Eine Ableitung, die einen Sonderfall braucht, ist
|
|
74
|
+
* keine Ableitung mehr, sondern eine Tabelle mit einem Loch.
|
|
75
|
+
*
|
|
76
|
+
* 🔴 **Sie ist NICHT der Fragenkatalog.** Hier stehen fuenf Dateinamen, fuenf
|
|
77
|
+
* Feldnamen, fuenf Anzeigenamen und fuenf Praefixe -- kein einziger Fragetext.
|
|
78
|
+
* Die 17 Fragen wohnen genau einmal, in `arbeitsplatz/vorlage/`, und erreichen
|
|
79
|
+
* die CLI ueber die ausgelieferte Datei selbst (s. `fragen-lesen.ts`).
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ **`titel` ist ANZEIGETEXT und traegt deshalb echte Umlaute** -- ein Mensch
|
|
82
|
+
* liest ihn im Abbruch von `kontext hoch` („Diese Datei sieht nicht aus wie
|
|
83
|
+
* …"). Die ASCII-Umschrift dieser Datei gilt nur fuer Kommentare.
|
|
84
|
+
*
|
|
85
|
+
* 🔴 **`ankerPraefix` ist keine Verabredung, sondern eine gemessene Zusage.**
|
|
86
|
+
* `check:kontext` haelt jeden Anker aus `FRAGEN` (der App) gegen den Praefix
|
|
87
|
+
* seiner Datei und prueft ausserdem, dass kein Praefix im Anfang eines anderen
|
|
88
|
+
* steckt. Wer eine 18. Frage `firma_neu` in `marke.md` schreibt, wird dort rot
|
|
89
|
+
* -- nicht erst beim Kunden, dem `kontext hoch` seine Marke verweigert.
|
|
90
|
+
*
|
|
91
|
+
* ⚠️ **`FELDER` (`src/app/firma/kontext-felder.ts`) fuehrt dieselben fuenf
|
|
92
|
+
* Zuordnungen.** Die App darf diese Datei lesen, die CLI aber nicht die
|
|
93
|
+
* App -- deshalb steht die Tabelle hier und die Uebereinstimmung ist eine
|
|
94
|
+
* Pruefung in `check:kontext`, keine Hoffnung.
|
|
95
|
+
*/
|
|
96
|
+
export const FIRMEN_TEXTE = [
|
|
97
|
+
{ pfad: "kontext/kurzprofil.md", feld: "drei_saetze", titel: "Die Firma in drei Sätzen", ankerPraefix: "kurz_" },
|
|
98
|
+
{ pfad: "kontext/firma.md", feld: "firma_md", titel: "Die Firma", ankerPraefix: "firma_" },
|
|
99
|
+
{ pfad: "kontext/marke.md", feld: "marke_md", titel: "Die Marke", ankerPraefix: "marke_" },
|
|
100
|
+
{ pfad: "kontext/systeme.md", feld: "systeme_md", titel: "Die Systeme", ankerPraefix: "systeme_" },
|
|
101
|
+
{ pfad: "kontext/sicherheit.md", feld: "sicherheit_md", titel: "Sicherheit", ankerPraefix: "sicher_" },
|
|
102
|
+
];
|
|
103
|
+
/**
|
|
104
|
+
* Der Merkzettel liegt im WURZELVERZEICHNIS des Arbeitsplatzes, eine Ebene
|
|
105
|
+
* ueber `kontext/`.
|
|
106
|
+
*
|
|
107
|
+
* ⚠️ Das ist kein Schoenheitsfehler: Er fuehrt alle verwalteten Dateien, und
|
|
108
|
+
* eine davon -- die Firmen-`CLAUDE.md` -- liegt gar nicht in `kontext/`. Ein
|
|
109
|
+
* Merkzettel in `kontext/`, der eine Datei ausserhalb verwaltet, waere die Sorte
|
|
110
|
+
* stiller Falschaussage, die niemand bemerkt, bis sie schadet.
|
|
111
|
+
*/
|
|
112
|
+
export const MERKZETTEL_PFAD = ".kimal3-stand.json";
|
|
113
|
+
/** Wo die Sicherungen hinkommen. Siehe die Frage am Kopf des Bauplans. */
|
|
114
|
+
export const SICHERUNG_BASIS = "kontext";
|
|
115
|
+
/**
|
|
116
|
+
* Der Stand einer Firma, die nichts gepflegt hat.
|
|
117
|
+
*
|
|
118
|
+
* 🔴 Ohne diesen Wert saehe JEDE frische Installation aus wie "lokal
|
|
119
|
+
* geaendert" -- und das ist der haeufigste Fall ueberhaupt.
|
|
120
|
+
*/
|
|
121
|
+
export const LEER = "leer";
|
|
122
|
+
/**
|
|
123
|
+
* Die aktuelle Fassung des MERKZETTEL-FORMATS selbst (Task 2, Spec US-B4) --
|
|
124
|
+
* NICHT die Fassung eines einzelnen Vordrucks (das ist `MerkEintrag.vorlage`).
|
|
125
|
+
* Sie steht hier als eigene, benannte Zahl, damit ein kuenftiger Umbau des
|
|
126
|
+
* Merkzettel-AUFBAUS (nicht seines Inhalts) einen Ankerpunkt hat, an dem er
|
|
127
|
+
* unterscheiden kann, was er vor sich hat.
|
|
128
|
+
*/
|
|
129
|
+
export const FORMAT_AKTUELL = 1;
|
|
130
|
+
/** Der leere Merkzettel -- weder Datei-Eintraege noch eine bekannte Firma. */
|
|
131
|
+
const LEERER_MERKZETTEL = { firma_id: null, dateien: {} };
|
|
132
|
+
/**
|
|
133
|
+
* Den Firmennamen aus der obersten `CLAUDE.md` lesen -- ihre erste Zeile
|
|
134
|
+
* lautet `# CLAUDE.md — <Firma> · KI-Arbeitsplatz`.
|
|
135
|
+
*
|
|
136
|
+
* 🔴 **Warum es diese zweite Quelle ueberhaupt braucht** (Befund 2 der DRITTEN
|
|
137
|
+
* Review-Runde, 2026-08-27, und er trifft die Reparatur der zweiten):
|
|
138
|
+
* `firmenAbgleich` verglich zuerst nur die IDs aus den Merkzetteln. Der
|
|
139
|
+
* Merkzettel steht aber in der `.gitignore` der Firmenvorlage -- bewusst, er
|
|
140
|
+
* ist Maschinenzustand. **Genau der Fall, fuer den die Wache gebaut wurde, hat
|
|
141
|
+
* ihn deshalb nie:** Wer den Firmenordner als Kopie oder ueber das Repo der
|
|
142
|
+
* Firma bekommt -- also der zweite Mensch, fuer den `werkbank neu` da ist --
|
|
143
|
+
* hat kein `.kimal3-stand.json`. `firma_id` war `null`, die Antwort lautete
|
|
144
|
+
* `unbekannt`, und die Wache liess durch.
|
|
145
|
+
* *Eine Wache, die ihr Erkennungszeichen aus einer Datei liest, die den Weg
|
|
146
|
+
* zum Kunden gar nicht mitgeht, leuchtet fuer immer gruen.*
|
|
147
|
+
*
|
|
148
|
+
* 🔑 Die `CLAUDE.md` geht den Weg mit: Sie steht ausdruecklich in der
|
|
149
|
+
* Positiv-Liste der `.gitignore` (`!/CLAUDE.md`), und seit A-11 traegt sie den
|
|
150
|
+
* echten Firmennamen statt eines Platzhalters.
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ Ein unersetzter `[FIRMA]`-Platzhalter zaehlt als **kein** Name: Er steht
|
|
153
|
+
* genau dann da, wenn der Server die Firma nicht kannte -- also im
|
|
154
|
+
* Vordruck-Fall, in dem es nichts zu vergleichen gibt.
|
|
155
|
+
*/
|
|
156
|
+
export function firmennameAusClaudeMd(inhalt) {
|
|
157
|
+
if (inhalt === null)
|
|
158
|
+
return null;
|
|
159
|
+
const treffer = /^#\s*CLAUDE\.md\s*—\s*(.+?)\s*·\s*KI-Arbeitsplatz\s*$/m.exec(inhalt);
|
|
160
|
+
if (treffer === null)
|
|
161
|
+
return null;
|
|
162
|
+
const name = treffer[1].trim();
|
|
163
|
+
return name === "" || name === "[FIRMA]" ? null : name;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 🔑 **ZWEI Erkennungszeichen, in dieser Reihenfolge -- die ID schlaegt den
|
|
167
|
+
* Namen.** Zwei Firmen duerfen gleich heissen (dafuer gibt es `--firma-id`),
|
|
168
|
+
* aber nie dieselbe ID haben. Wo beide Seiten eine ID kennen, ist die Antwort
|
|
169
|
+
* also sicher; der Name ist der Rueckfall fuer den geklonten Ordner, der
|
|
170
|
+
* keinen Merkzettel hat.
|
|
171
|
+
*
|
|
172
|
+
* ⚠️ **Der Name ist die schwaechere Auskunft und wird nur gefragt, wenn die
|
|
173
|
+
* ID schweigt** -- nicht zusaetzlich. Sonst widersprechen sich zwei Quellen
|
|
174
|
+
* bei zwei gleichnamigen Firmen, und die staerkere verloere.
|
|
175
|
+
*/
|
|
176
|
+
export function firmenAbgleich(lokal, geliefert) {
|
|
177
|
+
if (lokal.id !== null && geliefert.id !== null) {
|
|
178
|
+
return lokal.id === geliefert.id ? { art: "passt" } : { art: "andere-firma" };
|
|
179
|
+
}
|
|
180
|
+
if (lokal.name !== null && geliefert.name !== null) {
|
|
181
|
+
if (lokal.name === geliefert.name)
|
|
182
|
+
return { art: "passt" };
|
|
183
|
+
/*
|
|
184
|
+
🔴 **EIN VERSCHIEDENER NAME IST EIN VERDACHT, KEIN BEWEIS** (Befund 1
|
|
185
|
+
der vierten Review-Runde, 2026-08-27 -- und er trifft die Reparatur der
|
|
186
|
+
dritten).
|
|
187
|
+
|
|
188
|
+
Die `CLAUDE.md` ist ausdruecklich eine Datei, die der Kunde AENDERN
|
|
189
|
+
darf: `kontext runter` sagt ihm woertlich „Ich fasse nichts an -- du
|
|
190
|
+
hast lokal geaendert". Schreibt eine Admin in die erste Zeile den vollen
|
|
191
|
+
Firmennamen („Nordwind Handel GmbH"), waehrend die App noch „Nordwind"
|
|
192
|
+
fuehrt, und legt den Ordner ins Firmen-Repo, dann liest ihr Kollege beim
|
|
193
|
+
Klonen einen Namen, der nicht zum gelieferten passt.
|
|
194
|
+
|
|
195
|
+
Mit `andere-firma` waere er damit HART GESPERRT worden -- mit einem Satz,
|
|
196
|
+
der falsch ist („eine andere Firma") und keinen gangbaren Ausweg nennt.
|
|
197
|
+
*Und gesperrt worden waere genau der Mensch, fuer den `werkbank neu`
|
|
198
|
+
ueberhaupt gebaut ist.*
|
|
199
|
+
|
|
200
|
+
🔑 Deshalb `zweifel`: Der Name ist das SCHWAECHERE Erkennungszeichen und
|
|
201
|
+
bekommt die schwaechere Folge. Sperren darf nur, was sicher ist -- die
|
|
202
|
+
ID. *Wer auf einen Verdacht hin zusperrt, schadet oefter dem Unschuldigen
|
|
203
|
+
als dem Fall, den er treffen wollte.*
|
|
204
|
+
*/
|
|
205
|
+
return { art: "zweifel", hier: lokal.name, gewaehlt: geliefert.name };
|
|
206
|
+
}
|
|
207
|
+
return { art: "unbekannt" };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Kurz-Pruefsumme des Inhalts. Zwoelf Hexzeichen aus SHA-256.
|
|
211
|
+
*
|
|
212
|
+
* 🔑 Warum gekuerzt: Der Merkzettel wird von Menschen aufgemacht, wenn etwas
|
|
213
|
+
* schiefgeht. Sechzig Zeichen pro Zeile machen ihn unlesbar, und 48 Bit reichen
|
|
214
|
+
* fuer eine Handvoll Dateien um Groessenordnungen.
|
|
215
|
+
*
|
|
216
|
+
* ⚠️ Der Text wird NICHT normalisiert -- kein `trim`, keine Zeilenenden-
|
|
217
|
+
* Angleichung. Ein Leerzeichen am Zeilenende IST eine Aenderung; wer sie
|
|
218
|
+
* wegrechnet, ueberschreibt eines Tages die Arbeit eines Menschen und meldet
|
|
219
|
+
* dabei "unveraendert".
|
|
220
|
+
*/
|
|
221
|
+
export function pruefsumme(inhalt) {
|
|
222
|
+
return createHash("sha256").update(inhalt, "utf8").digest("hex").slice(0, 12);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Ist `wert` sicher genug fuer einen HTTP-Kopf -- druckbares ASCII, keine
|
|
226
|
+
* Steuerzeichen? (Task 10, Fix-Runde 1, Fund 1.)
|
|
227
|
+
*
|
|
228
|
+
* 🔴 DER MERKZETTEL IST EINE DATEI, DIE EIN MENSCH VON HAND OEFFNEN UND
|
|
229
|
+
* BEARBEITEN KANN -- das ist ausdruecklich ihr Zweck (s. Kopf-Kommentar oben:
|
|
230
|
+
* "wird von Menschen aufgemacht, wenn etwas schiefgeht"). `leseMerkzettel`
|
|
231
|
+
* prueft `firma_id` und jeden `stand` nur auf `typeof === "string"`, NIE auf
|
|
232
|
+
* den Inhalt. Ein einziges Nicht-ASCII-Zeichen darin -- von Hand eingetippt,
|
|
233
|
+
* aus einem anderen Editor hereinkopiert -- reicht, damit `headers.set(...)`
|
|
234
|
+
* in `ruf()` (`api.ts`) WIRFT ("Cannot convert argument to a ByteString"),
|
|
235
|
+
* und zwar fuer JEDEN folgenden CLI-Befehl, nicht nur fuer `kontext`-Befehle.
|
|
236
|
+
* Der Kunde saehe "kimal3 ist gerade nicht erreichbar" und suchte den Fehler
|
|
237
|
+
* im Netz, waehrend er auf seiner eigenen Platte sitzt.
|
|
238
|
+
*
|
|
239
|
+
* ⚠️ ECHTES DRUCKBARES ASCII (0x20-0x7E), NICHT NUR "unter 256": Der
|
|
240
|
+
* ByteString-Fehler selbst kippt erst jenseits von Codepunkt 255 -- aber
|
|
241
|
+
* dieselbe Grenze wuerde "ä" (0xE4 = 228) noch durchlassen, und CLAUDE.md
|
|
242
|
+
* zieht sie bewusst enger ("ein Kopf traegt kein ... ä"). Druckbares ASCII
|
|
243
|
+
* ist ausserdem die einzige Menge, die auch Steuerzeichen (Zeilenumbrueche,
|
|
244
|
+
* Tabs) ausschliesst -- die in einem HTTP-Kopf ohnehin nichts verloren haben.
|
|
245
|
+
*/
|
|
246
|
+
export function istKopfwertSicher(wert) {
|
|
247
|
+
return /^[\x20-\x7E]*$/.test(wert);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Den Merkzettel aus dem rohen Dateiinhalt lesen. Wirft nie.
|
|
251
|
+
*
|
|
252
|
+
* 🔴 Ein Eintrag ohne BEIDE Felder faellt raus. Ohne diese Bedingung truege
|
|
253
|
+
* `eintrag.pruef` spaeter `undefined`, und `undefined !== summe` ist wahr --
|
|
254
|
+
* also hielte die Rechnung jede Datei fuer lokal geaendert und legte bei jedem
|
|
255
|
+
* Lauf eine neue Sicherung an.
|
|
256
|
+
*
|
|
257
|
+
* 🔴 **ZWEI FORMATE, ABSICHTLICH (Task 10a).** Vor dieser Aufgabe war ein
|
|
258
|
+
* Merkzettel eine FLACHE Zuordnung Pfad -> Eintrag, ohne eigenes Feld fuer die
|
|
259
|
+
* Firma -- und genau solche Dateien liegen schon auf Platten von heute
|
|
260
|
+
* Nachmittag, geschrieben vom Ausliefer-Weg, bevor es diese Aufgabe gab. Das
|
|
261
|
+
* neue Format erkennt sich an einem eigenen `dateien`-Feld (Objekt, keine
|
|
262
|
+
* Liste); fehlt es, wird das GANZE Objekt wie bisher als flache
|
|
263
|
+
* Pfad-Zuordnung gelesen und `firma_id` bleibt `null`. *Ein Werkzeug, das an
|
|
264
|
+
* seiner eigenen aelteren Datei abstuerzt, ist schlimmer als eines, das sie
|
|
265
|
+
* ignoriert.*
|
|
266
|
+
*
|
|
267
|
+
* 🔴 **TASK 2, US-B4: `format` UND `vorlage` WERDEN NUR UEBERNOMMEN, NIE
|
|
268
|
+
* ERFUNDEN.** Beide Felder sind auf ihrem Typ optional (s. dort) -- ein
|
|
269
|
+
* Merkzettel von VOR dieser Aufgabe hat sie nicht, und das ist die "alte
|
|
270
|
+
* Fassung" aus dem Acceptance-Kriterium, kein Fehler. Steht `format` nicht
|
|
271
|
+
* oder nicht als Zahl im Objekt, bleibt es im Ergebnis schlicht weg; dasselbe
|
|
272
|
+
* gilt je Eintrag fuer `vorlage`. Ein Wert, der nicht im Text stand, wird hier
|
|
273
|
+
* nicht geraten -- das waere genau die Sorte stiller Falschaussage, die
|
|
274
|
+
* anderswo in dieser Datei schon einmal Schaden angerichtet hat (s.
|
|
275
|
+
* `bekannterStandAus`).
|
|
276
|
+
*/
|
|
277
|
+
export function leseMerkzettel(roh) {
|
|
278
|
+
if (roh === null)
|
|
279
|
+
return LEERER_MERKZETTEL;
|
|
280
|
+
try {
|
|
281
|
+
const daten = JSON.parse(roh);
|
|
282
|
+
if (!daten || typeof daten !== "object" || Array.isArray(daten))
|
|
283
|
+
return LEERER_MERKZETTEL;
|
|
284
|
+
const objekt = daten;
|
|
285
|
+
const hatNeueForm = "dateien" in objekt &&
|
|
286
|
+
typeof objekt.dateien === "object" &&
|
|
287
|
+
objekt.dateien !== null &&
|
|
288
|
+
!Array.isArray(objekt.dateien);
|
|
289
|
+
const dateienRoh = (hatNeueForm ? objekt.dateien : objekt);
|
|
290
|
+
const firmenIdRoh = hatNeueForm ? objekt.firma_id : undefined;
|
|
291
|
+
const formatRoh = hatNeueForm ? objekt.format : undefined;
|
|
292
|
+
const dateien = {};
|
|
293
|
+
for (const [pfad, wert] of Object.entries(dateienRoh)) {
|
|
294
|
+
if (!wert || typeof wert !== "object")
|
|
295
|
+
continue;
|
|
296
|
+
const e = wert;
|
|
297
|
+
if (typeof e.stand !== "string" || typeof e.pruef !== "string")
|
|
298
|
+
continue;
|
|
299
|
+
const eintrag = { stand: e.stand, pruef: e.pruef };
|
|
300
|
+
if (typeof e.vorlage === "string")
|
|
301
|
+
eintrag.vorlage = e.vorlage;
|
|
302
|
+
dateien[pfad] = eintrag;
|
|
303
|
+
}
|
|
304
|
+
// 🔑 `format` steht per Spread VOR `firma_id`, wenn es da ist -- dieselbe
|
|
305
|
+
// Feldreihenfolge wie bei `alsMerkzettel` und `baueMerkzettel`. Reine
|
|
306
|
+
// Kosmetik fuer Menschen, die die Datei aufmachen -- aber genau deshalb
|
|
307
|
+
// WICHTIG fuer `check-kontext.mts`: Die dortige Pruefung vergleicht per
|
|
308
|
+
// `JSON.stringify`, und das ist reihenfolge-empfindlich.
|
|
309
|
+
return {
|
|
310
|
+
...(typeof formatRoh === "number" ? { format: formatRoh } : {}),
|
|
311
|
+
firma_id: typeof firmenIdRoh === "string" ? firmenIdRoh : null,
|
|
312
|
+
dateien,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return LEERER_MERKZETTEL;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** Der Merkzettel als Dateiinhalt -- eingerueckt, weil ihn Menschen aufmachen. */
|
|
320
|
+
export function schreibeMerkzettel(m) {
|
|
321
|
+
return `${JSON.stringify(m, null, 2)}\n`;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Prueft, ob `wert` (aus einer bereits geparsten JSON-ANTWORT der App) ein
|
|
325
|
+
* gueltiger Merkzettel ist -- die Form-Pruefung, die `kontext-runter.ts` und
|
|
326
|
+
* `kontext-hoch.ts` sonst je einmal von Hand nachbauen wuerden (Task 10a).
|
|
327
|
+
*
|
|
328
|
+
* ⚠️ ANDERS STRENG ALS `leseMerkzettel`: Die App liefert ab dieser Aufgabe
|
|
329
|
+
* IMMER das neue Format (`dateien` + `firma_id`) -- Stufe 2 ist nicht
|
|
330
|
+
* veroeffentlicht, es gibt also keine Antwort einer AELTEREN Version, gegen
|
|
331
|
+
* die sich diese Funktion abfedern muesste. Nur das neue Format wird
|
|
332
|
+
* akzeptiert; alles andere ist `null` ("nicht lesbar"), nicht "leer geraten".
|
|
333
|
+
*
|
|
334
|
+
* 🔴 TASK 2: Aus demselben Grund werden jetzt AUCH `format` und je Eintrag
|
|
335
|
+
* `vorlage` verlangt. `baueMerkzettel` (unten) setzt beide seit dieser
|
|
336
|
+
* Aufgabe IMMER -- jede Antwort, die durch diese Rechnung entstanden ist,
|
|
337
|
+
* traegt sie also. Fehlt eines trotzdem, ist das keine aeltere Version,
|
|
338
|
+
* sondern eine kaputte Antwort -- `null`, nicht geraten.
|
|
339
|
+
*/
|
|
340
|
+
export function alsMerkzettel(wert) {
|
|
341
|
+
if (!wert || typeof wert !== "object" || Array.isArray(wert))
|
|
342
|
+
return null;
|
|
343
|
+
const objekt = wert;
|
|
344
|
+
if (typeof objekt.format !== "number")
|
|
345
|
+
return null;
|
|
346
|
+
const dateienRoh = objekt.dateien;
|
|
347
|
+
if (!dateienRoh || typeof dateienRoh !== "object" || Array.isArray(dateienRoh))
|
|
348
|
+
return null;
|
|
349
|
+
const firmenIdRoh = objekt.firma_id;
|
|
350
|
+
if (firmenIdRoh !== null && typeof firmenIdRoh !== "string")
|
|
351
|
+
return null;
|
|
352
|
+
const dateien = {};
|
|
353
|
+
for (const [pfad, eintragRoh] of Object.entries(dateienRoh)) {
|
|
354
|
+
if (!eintragRoh || typeof eintragRoh !== "object")
|
|
355
|
+
continue;
|
|
356
|
+
const e = eintragRoh;
|
|
357
|
+
if (typeof e.stand !== "string" || typeof e.pruef !== "string" || typeof e.vorlage !== "string")
|
|
358
|
+
continue;
|
|
359
|
+
dateien[pfad] = { stand: e.stand, pruef: e.pruef, vorlage: e.vorlage };
|
|
360
|
+
}
|
|
361
|
+
return { format: objekt.format, firma_id: firmenIdRoh, dateien };
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Der lokal bekannte Firmenstand aus einem Merkzettel als EINE Zeichenkette --
|
|
365
|
+
* **Fix-Runde 1, Review-Fund 1 (Important)**, hierher gezogen fuer Task 10.
|
|
366
|
+
*
|
|
367
|
+
* 🔴 **URSPRUENGLICHER FEHLER (bei `kontext hoch`):** Eine Rechnung, die nur
|
|
368
|
+
* die VIER `kontext/*.md`-Eintraege des Merkzettels ansah, uebersah, dass
|
|
369
|
+
* `CLAUDE.md` den Stand des FUeNFTEN Feldes traegt, `drei_saetze` (s.
|
|
370
|
+
* `setzeKontextEin` in `ausliefern.ts`). Eine Firma, die bisher NUR die drei
|
|
371
|
+
* Saetze ueber das Formular auf `/firma` gepflegt hat -- ein ganz normaler
|
|
372
|
+
* erster Schritt --, hatte deshalb alle vier `kontext/*.md`-Eintraege auf
|
|
373
|
+
* `LEER`, waehrend der Server einen echten Stand fuehrte.
|
|
374
|
+
*
|
|
375
|
+
* 🔑 **WARUM „DER EINE NICHT-LEERE STAND GEWINNT" RICHTIG IST:** Die
|
|
376
|
+
* Firmenzeile traegt GENAU EIN `geaendert_am` fuer alle fuenf Felder. Jede
|
|
377
|
+
* Lieferung stempelt dieses EINE `zeitpunkt` auf jedes gepflegte Feld und
|
|
378
|
+
* `LEER` auf jedes ungepflegte -- es gibt also nie zwei verschiedene,
|
|
379
|
+
* gleichzeitig gueltige NICHT-leere Staende in einer einzigen Lieferung.
|
|
380
|
+
*
|
|
381
|
+
* ⚠️ **TROTZDEM, DIE GEGENPROBE:** Der Merkzettel ist eine Datei auf der
|
|
382
|
+
* Platte des Kunden, keine Datenbank mit Integritaetsregel. Findet diese
|
|
383
|
+
* Funktion mehr als einen unterschiedlichen nicht-leeren Stand, liefert sie
|
|
384
|
+
* `LEER` -- derselbe Wert wie "noch nie heruntergeladen". Sie RAET nicht,
|
|
385
|
+
* welcher stimmt.
|
|
386
|
+
*
|
|
387
|
+
* 🔁 **Task 10:** Wohnte bis hierher in `befehle/kontext-hoch.ts` und wird
|
|
388
|
+
* jetzt auch von `api.ts` gebraucht (fuer den Kopf `X-Kimal3-Kontext-Stand`).
|
|
389
|
+
* `kontext-hoch.ts` importiert `ruf` aus `./api.js` -- ein Umzug hierher, in
|
|
390
|
+
* die Datei ohne jeden Import ausser `node:crypto`, ist der einzige Weg, der
|
|
391
|
+
* `api.ts` nicht in einen Kreisimport mit sich selbst zwingt.
|
|
392
|
+
*/
|
|
393
|
+
export function bekannterStandAus(merkzettel) {
|
|
394
|
+
const staende = new Set();
|
|
395
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
396
|
+
const stand = merkzettel.dateien[pfad]?.stand;
|
|
397
|
+
if (stand !== undefined && stand !== LEER)
|
|
398
|
+
staende.add(stand);
|
|
399
|
+
}
|
|
400
|
+
if (staende.size === 1) {
|
|
401
|
+
const [einziger] = staende;
|
|
402
|
+
return einziger;
|
|
403
|
+
}
|
|
404
|
+
return LEER;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Der Merkzettel zu einer Lieferung.
|
|
408
|
+
*
|
|
409
|
+
* 🔴 ER ENTSTEHT MIT DEM INHALT, NICHT DANACH. Wer ihn spaeter schreibt, baut
|
|
410
|
+
* denselben Fehler wie Runde 5 des Update-Kanals: Der Stempel stand am Ende und
|
|
411
|
+
* behauptete taeglich, der Skill sei veraltet.
|
|
412
|
+
*
|
|
413
|
+
* ⚠️ `staende` nennt je Datei den Stand der FIRMA (`geaendert_am`) oder `leer`.
|
|
414
|
+
* Fehlt eine verwaltete Datei in der Lieferung, entsteht kein Eintrag -- ein
|
|
415
|
+
* Eintrag mit `undefined` waere schlimmer als keiner.
|
|
416
|
+
*
|
|
417
|
+
* 🔴 `firmenId` ist ABSICHTLICH PFLICHT, nicht optional (Task 10a). Ohne die
|
|
418
|
+
* ID muesste der Server spaeter raten, welche Firma gemeint ist -- wer zu
|
|
419
|
+
* ZWEI Firmen gehoert, staende im Ordner von A und laese "B hat etwas
|
|
420
|
+
* geaendert". `null` ist ein gueltiger, ausdruecklicher Wert ("keine
|
|
421
|
+
* eindeutige Firma bekannt", z. B. beim Vordruck ohne Kontext) -- ein
|
|
422
|
+
* vergessenes Argument ist das nicht: Wer diese Funktion aufruft, muss sich
|
|
423
|
+
* entscheiden, nicht stillschweigend `null` bekommen.
|
|
424
|
+
*
|
|
425
|
+
* 🔴 TASK 2: `vorlagenPruefsummen` traegt je Datei den Fingerabdruck UNSERER
|
|
426
|
+
* VORLAGE -- nicht den des ausgelieferten Inhalts (das ist `pruef`). Fehlt
|
|
427
|
+
* eine Pruefsumme fuer eine Datei, traegt ihr `vorlage`-Feld `LEER` --
|
|
428
|
+
* derselbe Sicherheitswert wie beim ungepflegten `stand` zwei Zeilen weiter
|
|
429
|
+
* unten, aus demselben Grund: *wer nichts weiss, behauptet nichts.*
|
|
430
|
+
*
|
|
431
|
+
* 🔴 **PUNKT A-1 (2026-08-26): DIESES ARGUMENT IST JETZT PFLICHT.** Es war
|
|
432
|
+
* optional, mit Default `{}` -- und genau deshalb hat es ein halbes Jahr lang
|
|
433
|
+
* niemand uebergeben. `ausliefern.ts` rief die Funktion mit drei Werten auf,
|
|
434
|
+
* jeder ausgelieferte Merkzettel trug bei allen sechs Dateien
|
|
435
|
+
* `"vorlage": "leer"`, und die Lage `vorlage-neuer` unten konnte in der
|
|
436
|
+
* Wirklichkeit nie entstehen: *keine fehlende Zeile, kein Compiler-Fehler,
|
|
437
|
+
* kein roter Waechter.* Ein Pflicht-Argument ist die einzige Absicherung, die
|
|
438
|
+
* auch den erwischt, der diesen Kommentar nie liest.
|
|
439
|
+
*
|
|
440
|
+
* 🔑 `format` wird HIER, beim NEU-Bauen einer Lieferung, immer gesetzt --
|
|
441
|
+
* anders als beim blossen LESEN einer womoeglich alten Datei
|
|
442
|
+
* (`leseMerkzettel` oben). Jede Lieferung, die durch diese Funktion entsteht,
|
|
443
|
+
* ist per Definition die aktuelle Fassung.
|
|
444
|
+
*/
|
|
445
|
+
export function baueMerkzettel(dateien, staende, firmenId, vorlagenPruefsummen) {
|
|
446
|
+
const raus = {};
|
|
447
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
448
|
+
const datei = dateien.find((d) => d.pfad === pfad);
|
|
449
|
+
if (!datei)
|
|
450
|
+
continue;
|
|
451
|
+
raus[pfad] = {
|
|
452
|
+
stand: staende[pfad] ?? LEER,
|
|
453
|
+
pruef: pruefsumme(datei.inhalt),
|
|
454
|
+
vorlage: vorlagenPruefsummen[pfad] ?? LEER,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return { format: FORMAT_AKTUELL, firma_id: firmenId, dateien: raus };
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Der Merkzettel, den `kimal3 kontext hoch` nach einem gelungenen Lauf auf die
|
|
461
|
+
* Platte schreibt.
|
|
462
|
+
*
|
|
463
|
+
* 🔴 **DER FUND, DEN DIE KUNDENSTRECKE AM 2026-08-25 GEFUNDEN HAT.** Vorher
|
|
464
|
+
* wurde die Antwort des Servers eins zu eins hingeschrieben. Der Merkzettel ist
|
|
465
|
+
* aber eine Aussage darueber, WAS AUF DER PLATTE LIEGT -- und `kontext hoch`
|
|
466
|
+
* schreibt keine einzige Datei. Fuer die fuenf gesendeten Texte geht das
|
|
467
|
+
* trotzdem auf: Was der Server zurueckgibt, entsteht aus dem, was die CLI ihm
|
|
468
|
+
* gerade geschickt hat, und dasselbe steht auf der Platte.
|
|
469
|
+
*
|
|
470
|
+
* ⚠️ **Fuer die Firmen-`CLAUDE.md` geht es NICHT auf, seit Ruling K-6.** Sie
|
|
471
|
+
* wird nicht mehr gesendet, aber der Server BAUT sie neu (er setzt die frischen
|
|
472
|
+
* drei Saetze ein). Die Pruefsumme in seiner Antwort gehoert damit zu einem
|
|
473
|
+
* Text, den niemand auf die Platte geschrieben hat.
|
|
474
|
+
*
|
|
475
|
+
* 🔴 **Was das anrichtete, gemessen:** Direkt nach einem erfolgreichen
|
|
476
|
+
* `kontext hoch` meldete `kimal3 kontext runter` „Ich fasse nichts an -- du
|
|
477
|
+
* hast lokal geaendert: CLAUDE.md" und riet: *„Deine Fassung ist die neuere?
|
|
478
|
+
* Dann schick sie hoch: kimal3 kontext hoch"* -- ein Rat auf einen Befehl, der
|
|
479
|
+
* die `CLAUDE.md` gar nicht mehr mitnimmt. Der Admin haette das beliebig oft
|
|
480
|
+
* wiederholen koennen. *Das ist woertlich die Form von Fund F: ein Befehl
|
|
481
|
+
* schickt den Menschen zu einem zweiten, der seine Lage nicht aendern kann,
|
|
482
|
+
* und beide melden Erfolg.*
|
|
483
|
+
*
|
|
484
|
+
* 🔑 **Die Regel: Ueber eine Datei, die wir nicht gesendet haben, uebernehmen
|
|
485
|
+
* wir nichts Neues.** Ihr Eintrag bleibt der lokale von vorher. Der naechste
|
|
486
|
+
* `kontext runter` sieht dann sauber „Server neuer" -- der Stand weicht ab, die
|
|
487
|
+
* Pruefsumme passt zur Platte -- und holt die Datei ohne Sicherung. Genau das
|
|
488
|
+
* soll passieren: Die Firma hat ihre drei Saetze geaendert, also gehoert die
|
|
489
|
+
* abgeleitete Kopie in der `CLAUDE.md` aufgefrischt.
|
|
490
|
+
*
|
|
491
|
+
* ⚠️ Gibt es lokal gar keinen Eintrag, entsteht auch keiner. `null` heisst
|
|
492
|
+
* „wir wissen nichts" und fuehrt beim naechsten Holen zur Sicherung -- der
|
|
493
|
+
* sichere Weg, nicht der bequeme.
|
|
494
|
+
*
|
|
495
|
+
* 🔑 `format` und `firma_id` kommen weiterhin vom SERVER: Beide sagen nichts
|
|
496
|
+
* ueber den Inhalt einer Datei, und bei der Firma ist der Server die Wahrheit.
|
|
497
|
+
*/
|
|
498
|
+
export function merkzettelNachHoch(vomServer, lokal, gesendet) {
|
|
499
|
+
const dateien = {};
|
|
500
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
501
|
+
const eintrag = gesendet.includes(pfad) ? vomServer.dateien[pfad] : lokal.dateien[pfad];
|
|
502
|
+
if (eintrag)
|
|
503
|
+
dateien[pfad] = eintrag;
|
|
504
|
+
}
|
|
505
|
+
return { ...vomServer, dateien };
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Die Entscheidungstabelle aus Spec §7b -- fuer EINE Datei.
|
|
509
|
+
*
|
|
510
|
+
* 🔑 DIE ABKUERZUNG STEHT ZUERST, und sie nimmt den haeufigsten Aerger weg: Ist
|
|
511
|
+
* der lokale Text identisch mit dem, was der Server liefert, lautet die Antwort
|
|
512
|
+
* "schon aktuell" -- egal was der Merkzettel sagt oder ob es ihn gibt. Wer
|
|
513
|
+
* frisch klont, hat die Dateien aus dem Repo, aber keinen Merkzettel; ohne diese
|
|
514
|
+
* Zeile bekaeme er eine sinnlose Sicherung seiner eigenen, unveraenderten Dateien.
|
|
515
|
+
*
|
|
516
|
+
* 🔴 "SERVER NEUER" HEISST HIER **ANDERS**, NICHT **SPAETER** (Bauplan D-2). Ein
|
|
517
|
+
* echter Zeitvergleich haette zwei Uhren und einen Sonderwert (`leer`) zu
|
|
518
|
+
* bewaeltigen. Weil laut E-4 ohnehin die Firma gewinnt, reicht die Ungleichheit
|
|
519
|
+
* -- und ein Zurueckrollen in der App erreicht den Kunden damit ebenfalls.
|
|
520
|
+
*
|
|
521
|
+
* 🔑 KEIN MERKZETTEL = BEIDES UNBEKANNT = der sichere Fall. Eine Datei ohne
|
|
522
|
+
* Eintrag kann von Hand angelegt, aus einer alten Fassung kopiert oder muehsam
|
|
523
|
+
* ausgefuellt worden sein. *Wer nichts weiss, loescht nichts* -- also wird
|
|
524
|
+
* gesichert, bevor ueberschrieben wird.
|
|
525
|
+
*
|
|
526
|
+
* 🔴 **TASK 2 (Spec US-B1/US-B4): DIE FUeNFTE LAGE, `vorlage-neuer`.** Das ist
|
|
527
|
+
* die Reparatur des schwersten Befundes des ganzen Umbaus: Bis hierher
|
|
528
|
+
* verglich diese Funktion nur, OB sich der ausgelieferte Text geaendert hat --
|
|
529
|
+
* nie, WESHALB. Verbessern wir unseren eigenen Vordruck, aendert sich der
|
|
530
|
+
* ausgelieferte Text bei jeder Firma, die ihn nicht selbst gepflegt hat, genau
|
|
531
|
+
* so wie bei einer echten Firmen-Aenderung -- und `serverStand` (der Stand DER
|
|
532
|
+
* FIRMA) bleibt dabei unveraendert `leer`. Ohne die dritte Dimension gab es
|
|
533
|
+
* daher zwei falsche Wege: Entweder mass die Funktion den Inhalt direkt (dann
|
|
534
|
+
* riss die Abkuerzung ganz oben nie, und ein reiner Vordruck-Wechsel landete
|
|
535
|
+
* unter "schon-aktuell" -- die Firma saehe die Verbesserung NIE) oder sie
|
|
536
|
+
* ignorierte den Vordruck-Stand komplett (dann waere jede Vordruck-Aenderung
|
|
537
|
+
* unsichtbar). `aktuelleVorlage` traegt deshalb die Pruefsumme des Vordrucks
|
|
538
|
+
* HEUTE herein; verglichen wird sie gegen `eintrag.vorlage` -- die Pruefsumme
|
|
539
|
+
* des Vordrucks, aus dem die Datei beim letzten Holen stammte.
|
|
540
|
+
*
|
|
541
|
+
* 🔑 DIE PRIORITAET, WENN MEHRERES ZUTRIFFT: Hat die FIRMA inzwischen etwas
|
|
542
|
+
* gesagt (`serverNeuer`), gewinnt das IMMER -- ob sich der Vordruck ebenfalls
|
|
543
|
+
* geaendert hat, spielt dann keine Rolle mehr; ihr Text ersetzt ohnehin den
|
|
544
|
+
* Vordruck. Hat dagegen der KUNDE lokal geaendert (`lokalGeaendert`), zaehlt
|
|
545
|
+
* eine geaenderte Vorlage genauso als Grund zu sichern wie eine geaenderte
|
|
546
|
+
* Firmen-Zeile -- in beiden Faellen wuerde ein sang- und klangloses
|
|
547
|
+
* Ueberschreiben echte Arbeit verlieren, also wird "beides".
|
|
548
|
+
*
|
|
549
|
+
* 🔴 **PUNKT A-1 (2026-08-26): `aktuelleVorlage` IST JETZT PFLICHT.** Es war
|
|
550
|
+
* optional -- mit der Begruendung, das Verdrahten gehoere zu einer spaeteren
|
|
551
|
+
* Aufgabe. Zurueckgekommen ist danach nie jemand: `kontext-runter.ts` rief
|
|
552
|
+
* diese Funktion ohne den Wert auf, `vorlage-neuer` konnte in der
|
|
553
|
+
* Wirklichkeit nicht entstehen, und der Kunde las weiter „Dein Firmenkontext
|
|
554
|
+
* ist aktuell". *Die Rechnung war gebaut, geprueft, dokumentiert -- und tot.*
|
|
555
|
+
* Wer den Wert jetzt weglaesst, bekommt einen Kompilierfehler.
|
|
556
|
+
*
|
|
557
|
+
* ⚠️ **Ein Merkzettel-Eintrag OHNE `vorlage` gilt als „unbekannt", und
|
|
558
|
+
* unbekannt zaehlt als GEAeNDERT.** Solche Eintraege liegen echt auf Platten:
|
|
559
|
+
* Sie stammen von einer Auslieferung, bevor es dieses Feld gab. Der Vergleich
|
|
560
|
+
* `undefined !== aktuelleVorlage` ist wahr, es entsteht also einmalig
|
|
561
|
+
* `vorlage-neuer` bzw. `beides` -- dieselbe Antwort wie bei einem fehlenden
|
|
562
|
+
* Eintrag, aus demselben Grund: *wer nichts weiss, sichert, bevor er
|
|
563
|
+
* ueberschreibt.* Die Gegenrichtung waere schlimmer: Wuerde „unbekannt" als
|
|
564
|
+
* „unveraendert" gelesen, bekaeme der Kunde die Verbesserung nicht -- und der
|
|
565
|
+
* naechste Lauf haette obendrein einen Merkzettel, der den Server-Text
|
|
566
|
+
* beschreibt, waehrend die alte Datei danebenliegt, und meldete faelschlich
|
|
567
|
+
* „du hast lokal geaendert".
|
|
568
|
+
*/
|
|
569
|
+
export function lageFuerDatei(a) {
|
|
570
|
+
if (a.lokalerInhalt === a.serverInhalt)
|
|
571
|
+
return "schon-aktuell";
|
|
572
|
+
// Es gibt nichts zu verlieren -- also holen, ohne zu sichern.
|
|
573
|
+
if (a.lokalerInhalt === null)
|
|
574
|
+
return "server-neuer";
|
|
575
|
+
const lokalGeaendert = a.eintrag === null ? true : pruefsumme(a.lokalerInhalt) !== a.eintrag.pruef;
|
|
576
|
+
const serverNeuer = a.eintrag === null ? true : a.eintrag.stand !== a.serverStand;
|
|
577
|
+
const vorlageNeuer = a.eintrag === null ? true : a.eintrag.vorlage !== a.aktuelleVorlage;
|
|
578
|
+
/*
|
|
579
|
+
🔴 **`vorlageNeuer` zaehlt hier BEWUSST nicht mit** (Review-Befund 1 zu
|
|
580
|
+
Punkt A-1, 2026-08-26 -- gemessen, nicht vermutet).
|
|
581
|
+
|
|
582
|
+
Hier stand einen Nachmittag lang `serverNeuer || vorlageNeuer`. Die Folge:
|
|
583
|
+
Wer seine Datei lokal geaendert hatte, um sie spaeter mit `kimal3 kontext
|
|
584
|
+
hoch` zu schicken, verlor beim naechsten `runter` seinen Arbeitsstand,
|
|
585
|
+
sobald WIR irgendeine Frage umformuliert hatten -- gesichert zwar, aber
|
|
586
|
+
aus der Datei verschwunden, und ohne dass er danach gefragt haette.
|
|
587
|
+
*Eine Verbesserung an unserem Text darf nicht die Arbeit von jemandem
|
|
588
|
+
wegraeumen, der gerade an seiner sitzt.*
|
|
589
|
+
|
|
590
|
+
🔑 Der richtige Weg ist der, den `lokal-geaendert` ohnehin nennt: erst
|
|
591
|
+
hochschicken, dann herunterladen. Dann liegen seine Antworten auf dem
|
|
592
|
+
Server, der Stand aendert sich, und der naechste `runter` bringt seine
|
|
593
|
+
Antworten UND unsere neuen Fragen -- zusammengefuehrt statt eines gegen
|
|
594
|
+
das andere. Wer nie hochschickt, behaelt seine Datei; sie gehoert ihm.
|
|
595
|
+
|
|
596
|
+
⚠️ `vorlage-neuer` unten bleibt davon unberuehrt. Genau dafuer wurde A-1
|
|
597
|
+
gebaut, und es ist der haeufige Fall: eine Datei, die unangetastet so
|
|
598
|
+
dasteht, wie wir sie geliefert haben.
|
|
599
|
+
*/
|
|
600
|
+
if (lokalGeaendert)
|
|
601
|
+
return serverNeuer ? "beides" : "lokal-geaendert";
|
|
602
|
+
if (serverNeuer)
|
|
603
|
+
return "server-neuer";
|
|
604
|
+
if (vorlageNeuer)
|
|
605
|
+
return "vorlage-neuer";
|
|
606
|
+
return "schon-aktuell";
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Dieselbe Entscheidung wie {@link lageFuerDatei} -- aber gefuettert aus den
|
|
610
|
+
* ZWEI Merkzetteln, zwischen denen `kimal3 kontext runter` steht: dem auf der
|
|
611
|
+
* Platte und dem aus der Antwort des Servers.
|
|
612
|
+
*
|
|
613
|
+
* 🔴 **WARUM ES DIESE FUNKTION GIBT (Punkt A-1, 2026-08-26).** Der eigentliche
|
|
614
|
+
* Fehler war nicht die Rechnung -- die stimmte. Er war, WOHER ihre Eingaben
|
|
615
|
+
* kommen: `kontext-runter.ts` gab ihr den Vordruck-Abdruck gar nicht erst mit.
|
|
616
|
+
* Genau dieser Schritt ("aus welchem der beiden Merkzettel kommt welcher
|
|
617
|
+
* Wert?") liess sich von keiner Wache sehen, weil er mitten im Befehl wohnte
|
|
618
|
+
* -- zwischen `fetch` und Dateisystem, wo `check:kontext` nie hinkommt.
|
|
619
|
+
*
|
|
620
|
+
* 🔴 **DIE ZUORDNUNG, DIE HIER ABGESICHERT WIRD -- sie ist nicht offensichtlich
|
|
621
|
+
* und in beide Richtungen falsch machbar:**
|
|
622
|
+
* - `eintrag` kommt aus dem LOKALEN Merkzettel. Er sagt, was beim letzten
|
|
623
|
+
* Holen galt: die eine Seite des Vergleichs.
|
|
624
|
+
* - `serverStand` UND `aktuelleVorlage` kommen aus dem Merkzettel DES SERVERS.
|
|
625
|
+
* Sie sagen, was heute gilt: die andere Seite.
|
|
626
|
+
*
|
|
627
|
+
* ⚠️ **Nimmt man beide Seiten aus derselben Datei, vergleicht die Rechnung
|
|
628
|
+
* eine Zahl mit sich selbst und findet NIE einen Unterschied** -- kein Fehler,
|
|
629
|
+
* keine Meldung, der Kunde liest „Dein Firmenkontext ist aktuell". Gemessen:
|
|
630
|
+
* Mit `aktuelleVorlage` aus dem lokalen Merkzettel blieb die verbesserte
|
|
631
|
+
* Datei liegen, und der naechste Lauf behauptete obendrein „du hast lokal
|
|
632
|
+
* geaendert". *Das ist derselbe Schaden wie vorher, nur eine Zeile weiter.*
|
|
633
|
+
*
|
|
634
|
+
* 🔑 Fehlt die Datei im Merkzettel des Servers, gilt fuer beide Werte `LEER`
|
|
635
|
+
* -- „wir wissen nichts", derselbe Rueckfall wie ueberall in dieser Datei.
|
|
636
|
+
*/
|
|
637
|
+
export function lageAusMerkzetteln(a) {
|
|
638
|
+
const vomServer = a.vomServer.dateien[a.pfad] ?? null;
|
|
639
|
+
return lageFuerDatei({
|
|
640
|
+
lokalerInhalt: a.lokalerInhalt,
|
|
641
|
+
serverInhalt: a.serverInhalt,
|
|
642
|
+
eintrag: a.lokal.dateien[a.pfad] ?? null,
|
|
643
|
+
serverStand: vomServer?.stand ?? LEER,
|
|
644
|
+
aktuelleVorlage: vomServer?.vorlage ?? LEER,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
/** `kontext/vorher-2026-08-21` */
|
|
648
|
+
export function sicherungsOrdner(datum) {
|
|
649
|
+
return `${SICHERUNG_BASIS}/vorher-${datum}`;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Wohin die lokale Fassung gesichert wird.
|
|
653
|
+
*
|
|
654
|
+
* ⚠️ FLACH, mit dem Basisnamen -- `CLAUDE.md` und `kontext/systeme.md` landen
|
|
655
|
+
* nebeneinander. Dass sich dabei nichts ueberschreiben kann, sichert der
|
|
656
|
+
* Waechter: Er prueft, dass alle verwalteten Dateien verschiedene
|
|
657
|
+
* Basisnamen haben. Eine kuenftige sechste faellt damit auf, bevor sie eine
|
|
658
|
+
* Sicherung ueberschreibt.
|
|
659
|
+
*/
|
|
660
|
+
export function sicherungsZiel(datum, pfad) {
|
|
661
|
+
const name = pfad.split("/").pop() ?? pfad;
|
|
662
|
+
return `${sicherungsOrdner(datum)}/${name}`;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Von welchem Ordner aus `kimal3 kontext runter` ueberhaupt wissen kann,
|
|
666
|
+
* welche Firma gemeint ist -- **ohne** das Dateisystem selbst anzufassen.
|
|
667
|
+
*
|
|
668
|
+
* 🔑 WARUM DIESE FUNKTION HIER UND NICHT ERST IN EINER SPAETEREN AUFGABE
|
|
669
|
+
* WOHNT: Diese Datei importiert bewusst nur `node:crypto` (s. Kopf). Die
|
|
670
|
+
* Fundregel selbst braucht kein Dateisystem -- nur eine Liste von Kandidaten,
|
|
671
|
+
* die der Aufrufer (spaeter) mit `readdirSync`/`existsSync` gefuellt hat.
|
|
672
|
+
* Die eigentliche Suche gehoert deshalb woanders hin (zieht Umgebung und
|
|
673
|
+
* Heimverzeichnis hoch); ein Waechter fuer eine reine Pfadregel soll das
|
|
674
|
+
* nicht mit auslösen.
|
|
675
|
+
*
|
|
676
|
+
* Fundregel: Vom aktuellen Verzeichnis aufwaerts suchen, bis ein Ordner
|
|
677
|
+
* gefunden ist, der `kontext/` UND `CLAUDE.md` enthaelt.
|
|
678
|
+
*
|
|
679
|
+
* 🔴 DER ERSTE TREFFER GEWINNT, NICHT DER OBERSTE. Die Kandidaten kommen von
|
|
680
|
+
* UNTEN NACH OBEN (aktuelles Verzeichnis zuerst) -- wer in
|
|
681
|
+
* `Kunden/ARNO/KI-Arbeitsplatz` steht, meint ARNO, nicht `Kunden/`. Wuerde
|
|
682
|
+
* stattdessen der oberste Treffer gewinnen, faende der Befehl bei jedem
|
|
683
|
+
* verschachtelten Arbeitsplatz den falschen.
|
|
684
|
+
*
|
|
685
|
+
* 🔑 BEIDES MUSS STIMMEN. Ein Ordner mit `kontext/`, aber ohne `CLAUDE.md`,
|
|
686
|
+
* ist kein Arbeitsplatz -- sonst traefe der Befehl irgendeinen Ordner, in dem
|
|
687
|
+
* jemand zufaellig etwas "kontext" genannt hat.
|
|
688
|
+
*
|
|
689
|
+
* Kein Treffer -> `null`.
|
|
690
|
+
*/
|
|
691
|
+
export function arbeitsplatzAus(kandidaten) {
|
|
692
|
+
for (const k of kandidaten) {
|
|
693
|
+
if (k.hatKontext && k.hatClaude)
|
|
694
|
+
return k.pfad;
|
|
695
|
+
}
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
/*
|
|
699
|
+
🪦 **Hier wohnten `DREI_SAETZE_UEBERSCHRIFT`, der Typ `DreiSaetzeSchnitt` und
|
|
700
|
+
`dreiSaetzeAus` -- ersatzlos gestrichen mit Ruling K-6 (Task 7).**
|
|
701
|
+
|
|
702
|
+
Was sie taten: Sie schnitten den Abschnitt „Die Firma in drei Saetzen" aus
|
|
703
|
+
dem Text der Firmen-`CLAUDE.md`, damit `kimal3 kontext hoch` ihn als
|
|
704
|
+
`drei_saetze` mitschicken konnte. Sie waren die Antwort auf Fund F -- der
|
|
705
|
+
Befehl liess dieses eine Feld aus und meldete trotzdem einen Haken.
|
|
706
|
+
|
|
707
|
+
🔴 **Warum ihr Fall nicht mehr entstehen kann:** Seit Ruling K-18 hat „Die
|
|
708
|
+
Firma in drei Saetzen" eine EIGENE Datei, `kontext/kurzprofil.md`, und seit
|
|
709
|
+
Task 6 stehen in der `CLAUDE.md` nur noch die drei ANTWORTEN -- ohne Anker,
|
|
710
|
+
ohne Ueberschriften. Der Umweg schnitt also ab Task 6 einen Text heraus, den
|
|
711
|
+
die App nicht mehr als beantwortete Fragen lesen kann: EIN `kontext hoch`
|
|
712
|
+
haette drei ankerlose Zeilen in die Spalte geschrieben, `alsStand` haette sie
|
|
713
|
+
als ungepflegt gelesen, und das Formular haette wieder das leere
|
|
714
|
+
Fragengeruest gezeigt -- mit den Saetzen der Firma unsichtbar dahinter.
|
|
715
|
+
|
|
716
|
+
🔑 **Was ihre Aufgabe uebernommen hat:** `kontext/kurzprofil.md` ist seither
|
|
717
|
+
eine Datei wie die vier anderen. Sie steht in {@link FIRMEN_TEXTE} und wird
|
|
718
|
+
gelesen, hochgeschickt und nachgezogen wie `marke.md` auch. *Fuenf
|
|
719
|
+
gleichrangige Dateien, kein Sonderfall, keine Extra-Meldung.*
|
|
720
|
+
|
|
721
|
+
⚠️ Die Ueberschrift steht weiterhin in der Vorlage -- sie ist jetzt aber
|
|
722
|
+
blosser Fliesstext. Kein Code sucht sie mehr; `ausliefern.ts` setzt die drei
|
|
723
|
+
Antworten an der Stelle von `EINSETZ_MARKE_DREI_SAETZE` ein, und dass DIESE
|
|
724
|
+
Marke woertlich in der Vorlage steht, prueft `check:arbeitsplatz`.
|
|
725
|
+
*/
|
|
726
|
+
/**
|
|
727
|
+
* Die ARTEN von Rat, die es gibt -- **in der Reihenfolge, in der sie im
|
|
728
|
+
* Terminal stehen.**
|
|
729
|
+
*
|
|
730
|
+
* 🔑 Diese Liste ist zugleich die Quelle des Typs {@link RatArt}. Wer eine
|
|
731
|
+
* dritte Art erfindet, muss sie hier eintragen -- und bekommt danach vom
|
|
732
|
+
* Compiler gesagt, dass {@link RAT_TEXT} noch keinen Text dafuer hat. *Eine
|
|
733
|
+
* Reihenfolge, die nebenher den Typ traegt, kann nicht unvollstaendig sein.*
|
|
734
|
+
*
|
|
735
|
+
* ⚠️ Die Reihenfolge ist nicht Geschmack: `kimal3 kontext runter` druckt seit
|
|
736
|
+
* Befund F-3 erst den Rat fuer die Firmen-Texte und darunter den fuer unsere
|
|
737
|
+
* Vorlage. Wer sie dreht, dreht die Ausgabe beim Kunden.
|
|
738
|
+
*/
|
|
739
|
+
const RAT_REIHENFOLGE = ["hochschickbar", "nicht-hochschickbar"];
|
|
740
|
+
/**
|
|
741
|
+
* 🔴 **PUNKT A-7 (2026-08-26): DIESE TABELLE IST DIE ABSICHERUNG, NICHT NUR
|
|
742
|
+
* EINE AUFRAEUMAKTION.**
|
|
743
|
+
*
|
|
744
|
+
* Bis hierher zaehlte {@link ratNachUnberuehrt} ihre Faelle auf: ein `if` fuer
|
|
745
|
+
* „irgendetwas aus {@link FIRMEN_TEXTE}" und ein zweites fuer den woertlichen
|
|
746
|
+
* Dateinamen `"CLAUDE.md"`. {@link VERWALTETE_DATEIEN} hat aber SECHS
|
|
747
|
+
* Eintraege, und eine kuenftige SIEBTE waere durch beide `if`s gefallen --
|
|
748
|
+
* ohne Fehler, ohne Meldung, ohne Rat. *Der Mensch haette gelesen, dass seine
|
|
749
|
+
* Datei in Ruhe gelassen wurde, und nirgends, was er jetzt tun soll.*
|
|
750
|
+
*
|
|
751
|
+
* 🔑 **Warum ein `Record` und keine zweite Liste:** Der Schluesseltyp ist die
|
|
752
|
+
* Vereinigung aller Pfade aus {@link VERWALTETE_DATEIEN}. Eine siebte Datei
|
|
753
|
+
* dort weitet ihn -- und dieses Objekt hat dann einen Schluessel zu wenig,
|
|
754
|
+
* *also uebersetzt die CLI nicht mehr*. Das ist die einzige Absicherung, die
|
|
755
|
+
* auch den erwischt, der diesen Kommentar nie liest.
|
|
756
|
+
*
|
|
757
|
+
* ⚠️ **Warum die Zuordnung nicht IN {@link VERWALTETE_DATEIEN} steht**, wo sie
|
|
758
|
+
* inhaltlich hingehoerte: Diese Liste ist an sechs Stellen ausserhalb dieser
|
|
759
|
+
* Datei eine reine Pfadliste (`ausliefern.ts`, `route.ts`,
|
|
760
|
+
* `kontext-runter.ts`, `kontext-dateien.ts`, zwei Waechter). Aus ihr eine
|
|
761
|
+
* Liste von Objekten zu machen, haette all das angefasst -- fuer denselben
|
|
762
|
+
* Schutz, den der `Record` hier ohne einen einzigen fremden Eingriff gibt.
|
|
763
|
+
*
|
|
764
|
+
* ⚠️ **Sie sagt dasselbe wie {@link FIRMEN_TEXTE}, und das ist eine
|
|
765
|
+
* Doppelung** -- gehalten wird sie von einer Pruefung in `check:kontext`
|
|
766
|
+
* („der `hoch`-Rat geht an genau die Dateien, die `kontext hoch` wirklich
|
|
767
|
+
* traegt"). Die konnte vor diesem Umbau gar nicht rot werden, weil der Rat
|
|
768
|
+
* SELBST aus {@link FIRMEN_TEXTE} entstand; jetzt sind es zwei Tabellen, und
|
|
769
|
+
* ihr Auseinanderlaufen ist messbar.
|
|
770
|
+
*/
|
|
771
|
+
const RAT_ART_JE_DATEI = {
|
|
772
|
+
"CLAUDE.md": "nicht-hochschickbar",
|
|
773
|
+
"kontext/kurzprofil.md": "hochschickbar",
|
|
774
|
+
"kontext/firma.md": "hochschickbar",
|
|
775
|
+
"kontext/marke.md": "hochschickbar",
|
|
776
|
+
"kontext/systeme.md": "hochschickbar",
|
|
777
|
+
"kontext/sicherheit.md": "hochschickbar",
|
|
778
|
+
};
|
|
779
|
+
/**
|
|
780
|
+
* Der Wortlaut je Art und Rolle.
|
|
781
|
+
*
|
|
782
|
+
* ⚠️ ECHTE UMLAUTE -- das liest ein Kunde. Die ASCII-Umschrift dieser Datei
|
|
783
|
+
* gilt nur fuer Kommentare.
|
|
784
|
+
*
|
|
785
|
+
* 🔑 `wer` ist die fertige Anrede (` an Lena` oder ` an euren Firmen-Admin`),
|
|
786
|
+
* damit der Rueckfall auf den Sammelbegriff an EINER Stelle steht und nicht in
|
|
787
|
+
* jedem Zweig neu. Ruling T7-1: nie `null` im Klartext.
|
|
788
|
+
*/
|
|
789
|
+
const RAT_TEXT = {
|
|
790
|
+
hochschickbar: (rolle, wer) => rolle === "admin"
|
|
791
|
+
? " Deine Fassung ist die neuere? Dann schick sie hoch: kimal3 kontext hoch"
|
|
792
|
+
: " Deine Fassung bleibt stehen — hochschicken darf nur der Firmen-Admin.\n" +
|
|
793
|
+
` Gib deine Ergänzung${wer} weiter; von dort geht sie in der App unter\n` +
|
|
794
|
+
" Firma → Kontext oder mit `kimal3 kontext hoch` an alle.",
|
|
795
|
+
/*
|
|
796
|
+
🔴 **Auch dieser Zweig kennt die ROLLE -- Punkt A-5, 2026-08-26.** Er tat
|
|
797
|
+
es bis dahin nicht und riet jedem dasselbe: „Aendere sie dort", also im
|
|
798
|
+
Formular unter Firma → Kontext. Ein MITGLIED kann das nicht. `kimal3
|
|
799
|
+
kontext hoch` weist es ab, und den Firmenkontext der Firma darf es in der
|
|
800
|
+
App nicht schreiben. *Ein Rat, der die Lage nicht aendern kann, ist keiner*
|
|
801
|
+
-- er schickt einen Menschen an eine Tuer, vor der er keinen Schluessel
|
|
802
|
+
hat, und laesst ihn den Fehler bei sich suchen.
|
|
803
|
+
|
|
804
|
+
🔑 Was das Mitglied stattdessen liest: WAS es weitergeben soll (die drei
|
|
805
|
+
Saetze aus `kontext/kurzprofil.md`) und AN WEN -- genau die Form, die der
|
|
806
|
+
Zweig fuer die fuenf `kontext/`-Dateien oben schon hat.
|
|
807
|
+
*/
|
|
808
|
+
"nicht-hochschickbar": (rolle, wer) => rolle === "admin"
|
|
809
|
+
? " Die `CLAUDE.md` kann `kimal3 kontext hoch` nicht mitnehmen — der Befehl schickt nur\n" +
|
|
810
|
+
" die fünf Dateien aus `kontext/`. Die drei Sätze in dieser Datei kommen aus\n" +
|
|
811
|
+
" `kontext/kurzprofil.md`, der Firmenname aus der App: Ändere sie dort, dann holt\n" +
|
|
812
|
+
" der nächste `kimal3 kontext runter` sie hierher.\n" +
|
|
813
|
+
" Alles Übrige darin ist unsere Vorlage — dafür gibt es keinen Weg zurück."
|
|
814
|
+
: " Die `CLAUDE.md` kann `kimal3 kontext hoch` nicht mitnehmen — der Befehl schickt nur\n" +
|
|
815
|
+
" die fünf Dateien aus `kontext/`. Deine Änderung bleibt stehen.\n" +
|
|
816
|
+
" Die drei Sätze in dieser Datei kommen aus `kontext/kurzprofil.md`, und die ändert\n" +
|
|
817
|
+
` nur der Firmen-Admin. Gib deinen Wunsch${wer} weiter; von dort geht\n` +
|
|
818
|
+
" `kontext/kurzprofil.md` in der App unter Firma → Kontext oder mit\n" +
|
|
819
|
+
" `kimal3 kontext hoch` an alle.\n" +
|
|
820
|
+
" Alles Übrige darin ist unsere Vorlage — dafür gibt es keinen Weg zurück.",
|
|
821
|
+
};
|
|
822
|
+
/**
|
|
823
|
+
* Was ein Mensch lesen soll, wenn `kimal3 kontext runter` Dateien in Ruhe
|
|
824
|
+
* laesst, weil er sie lokal geaendert hat -- je nach Rolle und je nach DATEI.
|
|
825
|
+
*
|
|
826
|
+
* 🔴 **DAS IST FUND F, ZUM ZWEITEN MAL -- und diesmal die echte Fassung
|
|
827
|
+
* (Review-Befund F-3, 2026-08-25).** Der Rat lautete fuer die ganze Liste auf
|
|
828
|
+
* einmal: „Deine Fassung ist die neuere? Dann schick sie hoch: kimal3 kontext
|
|
829
|
+
* hoch". Fuer `CLAUDE.md` ist dieser Satz eine Sackgasse: Sie steht in
|
|
830
|
+
* {@link VERWALTETE_DATEIEN} und landet deshalb in der Liste -- aber
|
|
831
|
+
* `kontext hoch` liest ausschliesslich die fuenf Dateien aus
|
|
832
|
+
* {@link FIRMEN_TEXTE} und meldet danach Erfolg, ohne sie. *Der Mensch tippt
|
|
833
|
+
* den geratenen Befehl, sieht einen Haken, und seine Aenderung steht weiter
|
|
834
|
+
* allein auf seiner Platte.*
|
|
835
|
+
*
|
|
836
|
+
* 🔑 Die ausgelieferte `CLAUDE.md` laedt diese Bearbeitung foermlich ein: Sie
|
|
837
|
+
* sagt selbst, aendern muesse man sie nur, wenn sich die Firma aendert.
|
|
838
|
+
*
|
|
839
|
+
* 🔴 **Der Rat wohnt hier und nicht im Befehl, damit ihn eine Wache sehen
|
|
840
|
+
* kann.** Die spurious Fassung desselben Fehlers (`merkzettelNachHoch`) war
|
|
841
|
+
* behoben, die echte lag vier Runden lang offen -- *ein Satz ueber das
|
|
842
|
+
* Verhalten eines Befehls wohnte im Befehl selbst, wo keine Vorlagen-Wache je
|
|
843
|
+
* hinsieht.* `check:kontext` prueft jetzt genau diese Zuordnung.
|
|
844
|
+
*
|
|
845
|
+
* 🔴 **PUNKT A-7 (2026-08-26): DER RAT WIRD ERZEUGT, NICHT AUFGEZAEHLT.** Diese
|
|
846
|
+
* Funktion hatte zwei `if`s -- eines fuer „irgendetwas aus
|
|
847
|
+
* {@link FIRMEN_TEXTE}", eines fuer den woertlichen Namen `"CLAUDE.md"`. Eine
|
|
848
|
+
* siebte verwaltete Datei waere durch beide gefallen und haette schweigend gar
|
|
849
|
+
* keinen Rat bekommen. Jetzt laeuft die Schleife ueber
|
|
850
|
+
* {@link VERWALTETE_DATEIEN} selbst und schlaegt die Art in
|
|
851
|
+
* {@link RAT_ART_JE_DATEI} nach -- eine Datei ohne Zuordnung uebersetzt nicht.
|
|
852
|
+
*
|
|
853
|
+
* ⚠️ **Eine ART, ein Satz -- auch wenn drei ihrer Dateien betroffen sind.** Das
|
|
854
|
+
* war vorher so (ein `if` feuerte einmal) und bleibt so: Der Rat spricht ueber
|
|
855
|
+
* den Weg, nicht ueber die einzelne Datei; dreimal derselbe Absatz waere
|
|
856
|
+
* Laerm.
|
|
857
|
+
*/
|
|
858
|
+
export function ratNachUnberuehrt(unberuehrt, rolle, adminName) {
|
|
859
|
+
// Ruling T7-1: kein `null` im Klartext, sondern der Sammelbegriff.
|
|
860
|
+
const wer = adminName ? ` an ${adminName}` : " an euren Firmen-Admin";
|
|
861
|
+
const betroffeneArten = new Set();
|
|
862
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
863
|
+
if (unberuehrt.includes(pfad))
|
|
864
|
+
betroffeneArten.add(RAT_ART_JE_DATEI[pfad]);
|
|
865
|
+
}
|
|
866
|
+
// 🔑 Die Reihenfolge kommt aus RAT_REIHENFOLGE, NICHT aus der Einfuegereihen-
|
|
867
|
+
// folge des `Set` -- sonst haenge die Ausgabe beim Kunden davon ab, in
|
|
868
|
+
// welcher Reihenfolge VERWALTETE_DATEIEN zufaellig steht.
|
|
869
|
+
return RAT_REIHENFOLGE.filter((art) => betroffeneArten.has(art)).map((art) => RAT_TEXT[art](rolle, wer));
|
|
870
|
+
}
|