kimal3 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/api.js +138 -1
- package/dist/arbeitsplatz-dateien.js +268 -0
- package/dist/befehle/arbeitsplatz.js +209 -233
- package/dist/befehle/kontext-fragen.js +165 -0
- package/dist/befehle/kontext-gemeinsam.js +171 -0
- package/dist/befehle/kontext-hoch.js +203 -0
- package/dist/befehle/kontext-runter.js +254 -0
- package/dist/befehle/skill-install.js +110 -4
- package/dist/befehle/werkbank.js +389 -0
- package/dist/befehle-katalog.js +98 -3
- package/dist/fragen-lesen.js +178 -0
- package/dist/gesehen.js +85 -0
- package/dist/kimal3.js +178 -13
- package/dist/kontext-dateien.js +248 -0
- package/dist/kontext-stand.js +616 -0
- package/dist/nachricht.js +249 -0
- package/dist/version.js +37 -0
- package/dist/werkbank-name.js +217 -0
- package/package.json +1 -1
- package/skill/SKILL.md +130 -16
|
@@ -0,0 +1,616 @@
|
|
|
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
|
+
export const VERWALTETE_DATEIEN = [
|
|
46
|
+
"CLAUDE.md",
|
|
47
|
+
"kontext/kurzprofil.md",
|
|
48
|
+
"kontext/firma.md",
|
|
49
|
+
"kontext/marke.md",
|
|
50
|
+
"kontext/systeme.md",
|
|
51
|
+
"kontext/sicherheit.md",
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Die FUENF Texte, die eine Firma selbst pflegt -- welche Datei welches Feld
|
|
55
|
+
* der App fuellt, wie sie dort heisst, und woran man sie wiedererkennt.
|
|
56
|
+
*
|
|
57
|
+
* 🔑 **Warum es diese Tabelle gibt.** `kimal3 kontext hoch` muss jede Datei dem
|
|
58
|
+
* Feld zuordnen, das `/api/cli/kontext` (POST) erwartet. Bis Task 7 leitete
|
|
59
|
+
* eine Funktion den Feldnamen aus dem Dateinamen ab (`marke.md` -> `marke_md`)
|
|
60
|
+
* -- fuer vier Dateien richtig und fuer die fuenfte falsch: `kurzprofil.md`
|
|
61
|
+
* fuellt `drei_saetze`. Eine Ableitung, die einen Sonderfall braucht, ist
|
|
62
|
+
* keine Ableitung mehr, sondern eine Tabelle mit einem Loch.
|
|
63
|
+
*
|
|
64
|
+
* 🔴 **Sie ist NICHT der Fragenkatalog.** Hier stehen fuenf Dateinamen, fuenf
|
|
65
|
+
* Feldnamen, fuenf Anzeigenamen und fuenf Praefixe -- kein einziger Fragetext.
|
|
66
|
+
* Die 17 Fragen wohnen genau einmal, in `arbeitsplatz/vorlage/`, und erreichen
|
|
67
|
+
* die CLI ueber die ausgelieferte Datei selbst (s. `fragen-lesen.ts`).
|
|
68
|
+
*
|
|
69
|
+
* ⚠️ **`titel` ist ANZEIGETEXT und traegt deshalb echte Umlaute** -- ein Mensch
|
|
70
|
+
* liest ihn im Abbruch von `kontext hoch` („Diese Datei sieht nicht aus wie
|
|
71
|
+
* …"). Die ASCII-Umschrift dieser Datei gilt nur fuer Kommentare.
|
|
72
|
+
*
|
|
73
|
+
* 🔴 **`ankerPraefix` ist keine Verabredung, sondern eine gemessene Zusage.**
|
|
74
|
+
* `check:kontext` haelt jeden Anker aus `FRAGEN` (der App) gegen den Praefix
|
|
75
|
+
* seiner Datei und prueft ausserdem, dass kein Praefix im Anfang eines anderen
|
|
76
|
+
* steckt. Wer eine 18. Frage `firma_neu` in `marke.md` schreibt, wird dort rot
|
|
77
|
+
* -- nicht erst beim Kunden, dem `kontext hoch` seine Marke verweigert.
|
|
78
|
+
*
|
|
79
|
+
* ⚠️ **`FELDER` (`src/app/firma/kontext-felder.ts`) fuehrt dieselben fuenf
|
|
80
|
+
* Zuordnungen.** Die App darf diese Datei lesen, die CLI aber nicht die
|
|
81
|
+
* App -- deshalb steht die Tabelle hier und die Uebereinstimmung ist eine
|
|
82
|
+
* Pruefung in `check:kontext`, keine Hoffnung.
|
|
83
|
+
*/
|
|
84
|
+
export const FIRMEN_TEXTE = [
|
|
85
|
+
{ pfad: "kontext/kurzprofil.md", feld: "drei_saetze", titel: "Die Firma in drei Sätzen", ankerPraefix: "kurz_" },
|
|
86
|
+
{ pfad: "kontext/firma.md", feld: "firma_md", titel: "Die Firma", ankerPraefix: "firma_" },
|
|
87
|
+
{ pfad: "kontext/marke.md", feld: "marke_md", titel: "Die Marke", ankerPraefix: "marke_" },
|
|
88
|
+
{ pfad: "kontext/systeme.md", feld: "systeme_md", titel: "Die Systeme", ankerPraefix: "systeme_" },
|
|
89
|
+
{ pfad: "kontext/sicherheit.md", feld: "sicherheit_md", titel: "Sicherheit", ankerPraefix: "sicher_" },
|
|
90
|
+
];
|
|
91
|
+
/**
|
|
92
|
+
* Der Merkzettel liegt im WURZELVERZEICHNIS des Arbeitsplatzes, eine Ebene
|
|
93
|
+
* ueber `kontext/`.
|
|
94
|
+
*
|
|
95
|
+
* ⚠️ Das ist kein Schoenheitsfehler: Er fuehrt alle verwalteten Dateien, und
|
|
96
|
+
* eine davon -- die Firmen-`CLAUDE.md` -- liegt gar nicht in `kontext/`. Ein
|
|
97
|
+
* Merkzettel in `kontext/`, der eine Datei ausserhalb verwaltet, waere die Sorte
|
|
98
|
+
* stiller Falschaussage, die niemand bemerkt, bis sie schadet.
|
|
99
|
+
*/
|
|
100
|
+
export const MERKZETTEL_PFAD = ".kimal3-stand.json";
|
|
101
|
+
/** Wo die Sicherungen hinkommen. Siehe die Frage am Kopf des Bauplans. */
|
|
102
|
+
export const SICHERUNG_BASIS = "kontext";
|
|
103
|
+
/**
|
|
104
|
+
* Der Stand einer Firma, die nichts gepflegt hat.
|
|
105
|
+
*
|
|
106
|
+
* 🔴 Ohne diesen Wert saehe JEDE frische Installation aus wie "lokal
|
|
107
|
+
* geaendert" -- und das ist der haeufigste Fall ueberhaupt.
|
|
108
|
+
*/
|
|
109
|
+
export const LEER = "leer";
|
|
110
|
+
/**
|
|
111
|
+
* Die aktuelle Fassung des MERKZETTEL-FORMATS selbst (Task 2, Spec US-B4) --
|
|
112
|
+
* NICHT die Fassung eines einzelnen Vordrucks (das ist `MerkEintrag.vorlage`).
|
|
113
|
+
* Sie steht hier als eigene, benannte Zahl, damit ein kuenftiger Umbau des
|
|
114
|
+
* Merkzettel-AUFBAUS (nicht seines Inhalts) einen Ankerpunkt hat, an dem er
|
|
115
|
+
* unterscheiden kann, was er vor sich hat.
|
|
116
|
+
*/
|
|
117
|
+
export const FORMAT_AKTUELL = 1;
|
|
118
|
+
/** Der leere Merkzettel -- weder Datei-Eintraege noch eine bekannte Firma. */
|
|
119
|
+
const LEERER_MERKZETTEL = { firma_id: null, dateien: {} };
|
|
120
|
+
/**
|
|
121
|
+
* Kurz-Pruefsumme des Inhalts. Zwoelf Hexzeichen aus SHA-256.
|
|
122
|
+
*
|
|
123
|
+
* 🔑 Warum gekuerzt: Der Merkzettel wird von Menschen aufgemacht, wenn etwas
|
|
124
|
+
* schiefgeht. Sechzig Zeichen pro Zeile machen ihn unlesbar, und 48 Bit reichen
|
|
125
|
+
* fuer eine Handvoll Dateien um Groessenordnungen.
|
|
126
|
+
*
|
|
127
|
+
* ⚠️ Der Text wird NICHT normalisiert -- kein `trim`, keine Zeilenenden-
|
|
128
|
+
* Angleichung. Ein Leerzeichen am Zeilenende IST eine Aenderung; wer sie
|
|
129
|
+
* wegrechnet, ueberschreibt eines Tages die Arbeit eines Menschen und meldet
|
|
130
|
+
* dabei "unveraendert".
|
|
131
|
+
*/
|
|
132
|
+
export function pruefsumme(inhalt) {
|
|
133
|
+
return createHash("sha256").update(inhalt, "utf8").digest("hex").slice(0, 12);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Ist `wert` sicher genug fuer einen HTTP-Kopf -- druckbares ASCII, keine
|
|
137
|
+
* Steuerzeichen? (Task 10, Fix-Runde 1, Fund 1.)
|
|
138
|
+
*
|
|
139
|
+
* 🔴 DER MERKZETTEL IST EINE DATEI, DIE EIN MENSCH VON HAND OEFFNEN UND
|
|
140
|
+
* BEARBEITEN KANN -- das ist ausdruecklich ihr Zweck (s. Kopf-Kommentar oben:
|
|
141
|
+
* "wird von Menschen aufgemacht, wenn etwas schiefgeht"). `leseMerkzettel`
|
|
142
|
+
* prueft `firma_id` und jeden `stand` nur auf `typeof === "string"`, NIE auf
|
|
143
|
+
* den Inhalt. Ein einziges Nicht-ASCII-Zeichen darin -- von Hand eingetippt,
|
|
144
|
+
* aus einem anderen Editor hereinkopiert -- reicht, damit `headers.set(...)`
|
|
145
|
+
* in `ruf()` (`api.ts`) WIRFT ("Cannot convert argument to a ByteString"),
|
|
146
|
+
* und zwar fuer JEDEN folgenden CLI-Befehl, nicht nur fuer `kontext`-Befehle.
|
|
147
|
+
* Der Kunde saehe "kimal3 ist gerade nicht erreichbar" und suchte den Fehler
|
|
148
|
+
* im Netz, waehrend er auf seiner eigenen Platte sitzt.
|
|
149
|
+
*
|
|
150
|
+
* ⚠️ ECHTES DRUCKBARES ASCII (0x20-0x7E), NICHT NUR "unter 256": Der
|
|
151
|
+
* ByteString-Fehler selbst kippt erst jenseits von Codepunkt 255 -- aber
|
|
152
|
+
* dieselbe Grenze wuerde "ä" (0xE4 = 228) noch durchlassen, und CLAUDE.md
|
|
153
|
+
* zieht sie bewusst enger ("ein Kopf traegt kein ... ä"). Druckbares ASCII
|
|
154
|
+
* ist ausserdem die einzige Menge, die auch Steuerzeichen (Zeilenumbrueche,
|
|
155
|
+
* Tabs) ausschliesst -- die in einem HTTP-Kopf ohnehin nichts verloren haben.
|
|
156
|
+
*/
|
|
157
|
+
export function istKopfwertSicher(wert) {
|
|
158
|
+
return /^[\x20-\x7E]*$/.test(wert);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Den Merkzettel aus dem rohen Dateiinhalt lesen. Wirft nie.
|
|
162
|
+
*
|
|
163
|
+
* 🔴 Ein Eintrag ohne BEIDE Felder faellt raus. Ohne diese Bedingung truege
|
|
164
|
+
* `eintrag.pruef` spaeter `undefined`, und `undefined !== summe` ist wahr --
|
|
165
|
+
* also hielte die Rechnung jede Datei fuer lokal geaendert und legte bei jedem
|
|
166
|
+
* Lauf eine neue Sicherung an.
|
|
167
|
+
*
|
|
168
|
+
* 🔴 **ZWEI FORMATE, ABSICHTLICH (Task 10a).** Vor dieser Aufgabe war ein
|
|
169
|
+
* Merkzettel eine FLACHE Zuordnung Pfad -> Eintrag, ohne eigenes Feld fuer die
|
|
170
|
+
* Firma -- und genau solche Dateien liegen schon auf Platten von heute
|
|
171
|
+
* Nachmittag, geschrieben vom Ausliefer-Weg, bevor es diese Aufgabe gab. Das
|
|
172
|
+
* neue Format erkennt sich an einem eigenen `dateien`-Feld (Objekt, keine
|
|
173
|
+
* Liste); fehlt es, wird das GANZE Objekt wie bisher als flache
|
|
174
|
+
* Pfad-Zuordnung gelesen und `firma_id` bleibt `null`. *Ein Werkzeug, das an
|
|
175
|
+
* seiner eigenen aelteren Datei abstuerzt, ist schlimmer als eines, das sie
|
|
176
|
+
* ignoriert.*
|
|
177
|
+
*
|
|
178
|
+
* 🔴 **TASK 2, US-B4: `format` UND `vorlage` WERDEN NUR UEBERNOMMEN, NIE
|
|
179
|
+
* ERFUNDEN.** Beide Felder sind auf ihrem Typ optional (s. dort) -- ein
|
|
180
|
+
* Merkzettel von VOR dieser Aufgabe hat sie nicht, und das ist die "alte
|
|
181
|
+
* Fassung" aus dem Acceptance-Kriterium, kein Fehler. Steht `format` nicht
|
|
182
|
+
* oder nicht als Zahl im Objekt, bleibt es im Ergebnis schlicht weg; dasselbe
|
|
183
|
+
* gilt je Eintrag fuer `vorlage`. Ein Wert, der nicht im Text stand, wird hier
|
|
184
|
+
* nicht geraten -- das waere genau die Sorte stiller Falschaussage, die
|
|
185
|
+
* anderswo in dieser Datei schon einmal Schaden angerichtet hat (s.
|
|
186
|
+
* `bekannterStandAus`).
|
|
187
|
+
*/
|
|
188
|
+
export function leseMerkzettel(roh) {
|
|
189
|
+
if (roh === null)
|
|
190
|
+
return LEERER_MERKZETTEL;
|
|
191
|
+
try {
|
|
192
|
+
const daten = JSON.parse(roh);
|
|
193
|
+
if (!daten || typeof daten !== "object" || Array.isArray(daten))
|
|
194
|
+
return LEERER_MERKZETTEL;
|
|
195
|
+
const objekt = daten;
|
|
196
|
+
const hatNeueForm = "dateien" in objekt &&
|
|
197
|
+
typeof objekt.dateien === "object" &&
|
|
198
|
+
objekt.dateien !== null &&
|
|
199
|
+
!Array.isArray(objekt.dateien);
|
|
200
|
+
const dateienRoh = (hatNeueForm ? objekt.dateien : objekt);
|
|
201
|
+
const firmenIdRoh = hatNeueForm ? objekt.firma_id : undefined;
|
|
202
|
+
const formatRoh = hatNeueForm ? objekt.format : undefined;
|
|
203
|
+
const dateien = {};
|
|
204
|
+
for (const [pfad, wert] of Object.entries(dateienRoh)) {
|
|
205
|
+
if (!wert || typeof wert !== "object")
|
|
206
|
+
continue;
|
|
207
|
+
const e = wert;
|
|
208
|
+
if (typeof e.stand !== "string" || typeof e.pruef !== "string")
|
|
209
|
+
continue;
|
|
210
|
+
const eintrag = { stand: e.stand, pruef: e.pruef };
|
|
211
|
+
if (typeof e.vorlage === "string")
|
|
212
|
+
eintrag.vorlage = e.vorlage;
|
|
213
|
+
dateien[pfad] = eintrag;
|
|
214
|
+
}
|
|
215
|
+
// 🔑 `format` steht per Spread VOR `firma_id`, wenn es da ist -- dieselbe
|
|
216
|
+
// Feldreihenfolge wie bei `alsMerkzettel` und `baueMerkzettel`. Reine
|
|
217
|
+
// Kosmetik fuer Menschen, die die Datei aufmachen -- aber genau deshalb
|
|
218
|
+
// WICHTIG fuer `check-kontext.mts`: Die dortige Pruefung vergleicht per
|
|
219
|
+
// `JSON.stringify`, und das ist reihenfolge-empfindlich.
|
|
220
|
+
return {
|
|
221
|
+
...(typeof formatRoh === "number" ? { format: formatRoh } : {}),
|
|
222
|
+
firma_id: typeof firmenIdRoh === "string" ? firmenIdRoh : null,
|
|
223
|
+
dateien,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return LEERER_MERKZETTEL;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** Der Merkzettel als Dateiinhalt -- eingerueckt, weil ihn Menschen aufmachen. */
|
|
231
|
+
export function schreibeMerkzettel(m) {
|
|
232
|
+
return `${JSON.stringify(m, null, 2)}\n`;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Prueft, ob `wert` (aus einer bereits geparsten JSON-ANTWORT der App) ein
|
|
236
|
+
* gueltiger Merkzettel ist -- die Form-Pruefung, die `kontext-runter.ts` und
|
|
237
|
+
* `kontext-hoch.ts` sonst je einmal von Hand nachbauen wuerden (Task 10a).
|
|
238
|
+
*
|
|
239
|
+
* ⚠️ ANDERS STRENG ALS `leseMerkzettel`: Die App liefert ab dieser Aufgabe
|
|
240
|
+
* IMMER das neue Format (`dateien` + `firma_id`) -- Stufe 2 ist nicht
|
|
241
|
+
* veroeffentlicht, es gibt also keine Antwort einer AELTEREN Version, gegen
|
|
242
|
+
* die sich diese Funktion abfedern muesste. Nur das neue Format wird
|
|
243
|
+
* akzeptiert; alles andere ist `null` ("nicht lesbar"), nicht "leer geraten".
|
|
244
|
+
*
|
|
245
|
+
* 🔴 TASK 2: Aus demselben Grund werden jetzt AUCH `format` und je Eintrag
|
|
246
|
+
* `vorlage` verlangt. `baueMerkzettel` (unten) setzt beide seit dieser
|
|
247
|
+
* Aufgabe IMMER -- jede Antwort, die durch diese Rechnung entstanden ist,
|
|
248
|
+
* traegt sie also. Fehlt eines trotzdem, ist das keine aeltere Version,
|
|
249
|
+
* sondern eine kaputte Antwort -- `null`, nicht geraten.
|
|
250
|
+
*/
|
|
251
|
+
export function alsMerkzettel(wert) {
|
|
252
|
+
if (!wert || typeof wert !== "object" || Array.isArray(wert))
|
|
253
|
+
return null;
|
|
254
|
+
const objekt = wert;
|
|
255
|
+
if (typeof objekt.format !== "number")
|
|
256
|
+
return null;
|
|
257
|
+
const dateienRoh = objekt.dateien;
|
|
258
|
+
if (!dateienRoh || typeof dateienRoh !== "object" || Array.isArray(dateienRoh))
|
|
259
|
+
return null;
|
|
260
|
+
const firmenIdRoh = objekt.firma_id;
|
|
261
|
+
if (firmenIdRoh !== null && typeof firmenIdRoh !== "string")
|
|
262
|
+
return null;
|
|
263
|
+
const dateien = {};
|
|
264
|
+
for (const [pfad, eintragRoh] of Object.entries(dateienRoh)) {
|
|
265
|
+
if (!eintragRoh || typeof eintragRoh !== "object")
|
|
266
|
+
continue;
|
|
267
|
+
const e = eintragRoh;
|
|
268
|
+
if (typeof e.stand !== "string" || typeof e.pruef !== "string" || typeof e.vorlage !== "string")
|
|
269
|
+
continue;
|
|
270
|
+
dateien[pfad] = { stand: e.stand, pruef: e.pruef, vorlage: e.vorlage };
|
|
271
|
+
}
|
|
272
|
+
return { format: objekt.format, firma_id: firmenIdRoh, dateien };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Der lokal bekannte Firmenstand aus einem Merkzettel als EINE Zeichenkette --
|
|
276
|
+
* **Fix-Runde 1, Review-Fund 1 (Important)**, hierher gezogen fuer Task 10.
|
|
277
|
+
*
|
|
278
|
+
* 🔴 **URSPRUENGLICHER FEHLER (bei `kontext hoch`):** Eine Rechnung, die nur
|
|
279
|
+
* die VIER `kontext/*.md`-Eintraege des Merkzettels ansah, uebersah, dass
|
|
280
|
+
* `CLAUDE.md` den Stand des FUeNFTEN Feldes traegt, `drei_saetze` (s.
|
|
281
|
+
* `setzeKontextEin` in `ausliefern.ts`). Eine Firma, die bisher NUR die drei
|
|
282
|
+
* Saetze ueber das Formular auf `/firma` gepflegt hat -- ein ganz normaler
|
|
283
|
+
* erster Schritt --, hatte deshalb alle vier `kontext/*.md`-Eintraege auf
|
|
284
|
+
* `LEER`, waehrend der Server einen echten Stand fuehrte.
|
|
285
|
+
*
|
|
286
|
+
* 🔑 **WARUM „DER EINE NICHT-LEERE STAND GEWINNT" RICHTIG IST:** Die
|
|
287
|
+
* Firmenzeile traegt GENAU EIN `geaendert_am` fuer alle fuenf Felder. Jede
|
|
288
|
+
* Lieferung stempelt dieses EINE `zeitpunkt` auf jedes gepflegte Feld und
|
|
289
|
+
* `LEER` auf jedes ungepflegte -- es gibt also nie zwei verschiedene,
|
|
290
|
+
* gleichzeitig gueltige NICHT-leere Staende in einer einzigen Lieferung.
|
|
291
|
+
*
|
|
292
|
+
* ⚠️ **TROTZDEM, DIE GEGENPROBE:** Der Merkzettel ist eine Datei auf der
|
|
293
|
+
* Platte des Kunden, keine Datenbank mit Integritaetsregel. Findet diese
|
|
294
|
+
* Funktion mehr als einen unterschiedlichen nicht-leeren Stand, liefert sie
|
|
295
|
+
* `LEER` -- derselbe Wert wie "noch nie heruntergeladen". Sie RAET nicht,
|
|
296
|
+
* welcher stimmt.
|
|
297
|
+
*
|
|
298
|
+
* 🔁 **Task 10:** Wohnte bis hierher in `befehle/kontext-hoch.ts` und wird
|
|
299
|
+
* jetzt auch von `api.ts` gebraucht (fuer den Kopf `X-Kimal3-Kontext-Stand`).
|
|
300
|
+
* `kontext-hoch.ts` importiert `ruf` aus `./api.js` -- ein Umzug hierher, in
|
|
301
|
+
* die Datei ohne jeden Import ausser `node:crypto`, ist der einzige Weg, der
|
|
302
|
+
* `api.ts` nicht in einen Kreisimport mit sich selbst zwingt.
|
|
303
|
+
*/
|
|
304
|
+
export function bekannterStandAus(merkzettel) {
|
|
305
|
+
const staende = new Set();
|
|
306
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
307
|
+
const stand = merkzettel.dateien[pfad]?.stand;
|
|
308
|
+
if (stand !== undefined && stand !== LEER)
|
|
309
|
+
staende.add(stand);
|
|
310
|
+
}
|
|
311
|
+
if (staende.size === 1) {
|
|
312
|
+
const [einziger] = staende;
|
|
313
|
+
return einziger;
|
|
314
|
+
}
|
|
315
|
+
return LEER;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Der Merkzettel zu einer Lieferung.
|
|
319
|
+
*
|
|
320
|
+
* 🔴 ER ENTSTEHT MIT DEM INHALT, NICHT DANACH. Wer ihn spaeter schreibt, baut
|
|
321
|
+
* denselben Fehler wie Runde 5 des Update-Kanals: Der Stempel stand am Ende und
|
|
322
|
+
* behauptete taeglich, der Skill sei veraltet.
|
|
323
|
+
*
|
|
324
|
+
* ⚠️ `staende` nennt je Datei den Stand der FIRMA (`geaendert_am`) oder `leer`.
|
|
325
|
+
* Fehlt eine verwaltete Datei in der Lieferung, entsteht kein Eintrag -- ein
|
|
326
|
+
* Eintrag mit `undefined` waere schlimmer als keiner.
|
|
327
|
+
*
|
|
328
|
+
* 🔴 `firmenId` ist ABSICHTLICH PFLICHT, nicht optional (Task 10a). Ohne die
|
|
329
|
+
* ID muesste der Server spaeter raten, welche Firma gemeint ist -- wer zu
|
|
330
|
+
* ZWEI Firmen gehoert, staende im Ordner von A und laese "B hat etwas
|
|
331
|
+
* geaendert". `null` ist ein gueltiger, ausdruecklicher Wert ("keine
|
|
332
|
+
* eindeutige Firma bekannt", z. B. beim Vordruck ohne Kontext) -- ein
|
|
333
|
+
* vergessenes Argument ist das nicht: Wer diese Funktion aufruft, muss sich
|
|
334
|
+
* entscheiden, nicht stillschweigend `null` bekommen.
|
|
335
|
+
*
|
|
336
|
+
* 🔴 TASK 2: `vorlagenPruefsummen` ist das vierte, LETZTE und OPTIONALE
|
|
337
|
+
* Argument (Default `{}`) -- bewusst ans Ende gehaengt, nicht dazwischen. Der
|
|
338
|
+
* einzige heutige Aufrufer (`ausliefern.ts`) reicht die echten
|
|
339
|
+
* Vordruck-Pruefsummen noch nicht durch (das wiederum durchzuziehen ist Sache
|
|
340
|
+
* einer spaeteren Aufgabe -- diese hier baut nur die Rechnung, s. Auftrag);
|
|
341
|
+
* ein PFLICHT-Argument haette genau diesen bestehenden Aufruf sofort zum
|
|
342
|
+
* Kompilierfehler gemacht, und diese Aufgabe darf ausschliesslich in dieser
|
|
343
|
+
* Datei und in `check-kontext.mts` etwas aendern. Fehlt eine Pruefsumme fuer
|
|
344
|
+
* eine Datei, traegt ihr `vorlage`-Feld `LEER` -- derselbe Sicherheitswert
|
|
345
|
+
* wie beim ungepflegten `stand` zwei Zeilen weiter unten, aus demselben
|
|
346
|
+
* Grund: *wer nichts weiss, behauptet nichts.*
|
|
347
|
+
*
|
|
348
|
+
* 🔑 `format` wird HIER, beim NEU-Bauen einer Lieferung, immer gesetzt --
|
|
349
|
+
* anders als beim blossen LESEN einer womoeglich alten Datei
|
|
350
|
+
* (`leseMerkzettel` oben). Jede Lieferung, die durch diese Funktion entsteht,
|
|
351
|
+
* ist per Definition die aktuelle Fassung.
|
|
352
|
+
*/
|
|
353
|
+
export function baueMerkzettel(dateien, staende, firmenId, vorlagenPruefsummen = {}) {
|
|
354
|
+
const raus = {};
|
|
355
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
356
|
+
const datei = dateien.find((d) => d.pfad === pfad);
|
|
357
|
+
if (!datei)
|
|
358
|
+
continue;
|
|
359
|
+
raus[pfad] = {
|
|
360
|
+
stand: staende[pfad] ?? LEER,
|
|
361
|
+
pruef: pruefsumme(datei.inhalt),
|
|
362
|
+
vorlage: vorlagenPruefsummen[pfad] ?? LEER,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return { format: FORMAT_AKTUELL, firma_id: firmenId, dateien: raus };
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Der Merkzettel, den `kimal3 kontext hoch` nach einem gelungenen Lauf auf die
|
|
369
|
+
* Platte schreibt.
|
|
370
|
+
*
|
|
371
|
+
* 🔴 **DER FUND, DEN DIE KUNDENSTRECKE AM 2026-08-25 GEFUNDEN HAT.** Vorher
|
|
372
|
+
* wurde die Antwort des Servers eins zu eins hingeschrieben. Der Merkzettel ist
|
|
373
|
+
* aber eine Aussage darueber, WAS AUF DER PLATTE LIEGT -- und `kontext hoch`
|
|
374
|
+
* schreibt keine einzige Datei. Fuer die fuenf gesendeten Texte geht das
|
|
375
|
+
* trotzdem auf: Was der Server zurueckgibt, entsteht aus dem, was die CLI ihm
|
|
376
|
+
* gerade geschickt hat, und dasselbe steht auf der Platte.
|
|
377
|
+
*
|
|
378
|
+
* ⚠️ **Fuer die Firmen-`CLAUDE.md` geht es NICHT auf, seit Ruling K-6.** Sie
|
|
379
|
+
* wird nicht mehr gesendet, aber der Server BAUT sie neu (er setzt die frischen
|
|
380
|
+
* drei Saetze ein). Die Pruefsumme in seiner Antwort gehoert damit zu einem
|
|
381
|
+
* Text, den niemand auf die Platte geschrieben hat.
|
|
382
|
+
*
|
|
383
|
+
* 🔴 **Was das anrichtete, gemessen:** Direkt nach einem erfolgreichen
|
|
384
|
+
* `kontext hoch` meldete `kimal3 kontext runter` „Ich fasse nichts an -- du
|
|
385
|
+
* hast lokal geaendert: CLAUDE.md" und riet: *„Deine Fassung ist die neuere?
|
|
386
|
+
* Dann schick sie hoch: kimal3 kontext hoch"* -- ein Rat auf einen Befehl, der
|
|
387
|
+
* die `CLAUDE.md` gar nicht mehr mitnimmt. Der Admin haette das beliebig oft
|
|
388
|
+
* wiederholen koennen. *Das ist woertlich die Form von Fund F: ein Befehl
|
|
389
|
+
* schickt den Menschen zu einem zweiten, der seine Lage nicht aendern kann,
|
|
390
|
+
* und beide melden Erfolg.*
|
|
391
|
+
*
|
|
392
|
+
* 🔑 **Die Regel: Ueber eine Datei, die wir nicht gesendet haben, uebernehmen
|
|
393
|
+
* wir nichts Neues.** Ihr Eintrag bleibt der lokale von vorher. Der naechste
|
|
394
|
+
* `kontext runter` sieht dann sauber „Server neuer" -- der Stand weicht ab, die
|
|
395
|
+
* Pruefsumme passt zur Platte -- und holt die Datei ohne Sicherung. Genau das
|
|
396
|
+
* soll passieren: Die Firma hat ihre drei Saetze geaendert, also gehoert die
|
|
397
|
+
* abgeleitete Kopie in der `CLAUDE.md` aufgefrischt.
|
|
398
|
+
*
|
|
399
|
+
* ⚠️ Gibt es lokal gar keinen Eintrag, entsteht auch keiner. `null` heisst
|
|
400
|
+
* „wir wissen nichts" und fuehrt beim naechsten Holen zur Sicherung -- der
|
|
401
|
+
* sichere Weg, nicht der bequeme.
|
|
402
|
+
*
|
|
403
|
+
* 🔑 `format` und `firma_id` kommen weiterhin vom SERVER: Beide sagen nichts
|
|
404
|
+
* ueber den Inhalt einer Datei, und bei der Firma ist der Server die Wahrheit.
|
|
405
|
+
*/
|
|
406
|
+
export function merkzettelNachHoch(vomServer, lokal, gesendet) {
|
|
407
|
+
const dateien = {};
|
|
408
|
+
for (const pfad of VERWALTETE_DATEIEN) {
|
|
409
|
+
const eintrag = gesendet.includes(pfad) ? vomServer.dateien[pfad] : lokal.dateien[pfad];
|
|
410
|
+
if (eintrag)
|
|
411
|
+
dateien[pfad] = eintrag;
|
|
412
|
+
}
|
|
413
|
+
return { ...vomServer, dateien };
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Die Entscheidungstabelle aus Spec §7b -- fuer EINE Datei.
|
|
417
|
+
*
|
|
418
|
+
* 🔑 DIE ABKUERZUNG STEHT ZUERST, und sie nimmt den haeufigsten Aerger weg: Ist
|
|
419
|
+
* der lokale Text identisch mit dem, was der Server liefert, lautet die Antwort
|
|
420
|
+
* "schon aktuell" -- egal was der Merkzettel sagt oder ob es ihn gibt. Wer
|
|
421
|
+
* frisch klont, hat die Dateien aus dem Repo, aber keinen Merkzettel; ohne diese
|
|
422
|
+
* Zeile bekaeme er eine sinnlose Sicherung seiner eigenen, unveraenderten Dateien.
|
|
423
|
+
*
|
|
424
|
+
* 🔴 "SERVER NEUER" HEISST HIER **ANDERS**, NICHT **SPAETER** (Bauplan D-2). Ein
|
|
425
|
+
* echter Zeitvergleich haette zwei Uhren und einen Sonderwert (`leer`) zu
|
|
426
|
+
* bewaeltigen. Weil laut E-4 ohnehin die Firma gewinnt, reicht die Ungleichheit
|
|
427
|
+
* -- und ein Zurueckrollen in der App erreicht den Kunden damit ebenfalls.
|
|
428
|
+
*
|
|
429
|
+
* 🔑 KEIN MERKZETTEL = BEIDES UNBEKANNT = der sichere Fall. Eine Datei ohne
|
|
430
|
+
* Eintrag kann von Hand angelegt, aus einer alten Fassung kopiert oder muehsam
|
|
431
|
+
* ausgefuellt worden sein. *Wer nichts weiss, loescht nichts* -- also wird
|
|
432
|
+
* gesichert, bevor ueberschrieben wird.
|
|
433
|
+
*
|
|
434
|
+
* 🔴 **TASK 2 (Spec US-B1/US-B4): DIE FUeNFTE LAGE, `vorlage-neuer`.** Das ist
|
|
435
|
+
* die Reparatur des schwersten Befundes des ganzen Umbaus: Bis hierher
|
|
436
|
+
* verglich diese Funktion nur, OB sich der ausgelieferte Text geaendert hat --
|
|
437
|
+
* nie, WESHALB. Verbessern wir unseren eigenen Vordruck, aendert sich der
|
|
438
|
+
* ausgelieferte Text bei jeder Firma, die ihn nicht selbst gepflegt hat, genau
|
|
439
|
+
* so wie bei einer echten Firmen-Aenderung -- und `serverStand` (der Stand DER
|
|
440
|
+
* FIRMA) bleibt dabei unveraendert `leer`. Ohne die dritte Dimension gab es
|
|
441
|
+
* daher zwei falsche Wege: Entweder mass die Funktion den Inhalt direkt (dann
|
|
442
|
+
* riss die Abkuerzung ganz oben nie, und ein reiner Vordruck-Wechsel landete
|
|
443
|
+
* unter "schon-aktuell" -- die Firma saehe die Verbesserung NIE) oder sie
|
|
444
|
+
* ignorierte den Vordruck-Stand komplett (dann waere jede Vordruck-Aenderung
|
|
445
|
+
* unsichtbar). `aktuelleVorlage` traegt deshalb die Pruefsumme des Vordrucks
|
|
446
|
+
* HEUTE herein; verglichen wird sie gegen `eintrag.vorlage` -- die Pruefsumme
|
|
447
|
+
* des Vordrucks, aus dem die Datei beim letzten Holen stammte.
|
|
448
|
+
*
|
|
449
|
+
* 🔑 DIE PRIORITAET, WENN MEHRERES ZUTRIFFT: Hat die FIRMA inzwischen etwas
|
|
450
|
+
* gesagt (`serverNeuer`), gewinnt das IMMER -- ob sich der Vordruck ebenfalls
|
|
451
|
+
* geaendert hat, spielt dann keine Rolle mehr; ihr Text ersetzt ohnehin den
|
|
452
|
+
* Vordruck. Hat dagegen der KUNDE lokal geaendert (`lokalGeaendert`), zaehlt
|
|
453
|
+
* eine geaenderte Vorlage genauso als Grund zu sichern wie eine geaenderte
|
|
454
|
+
* Firmen-Zeile -- in beiden Faellen wuerde ein sang- und klangloses
|
|
455
|
+
* Ueberschreiben echte Arbeit verlieren, also wird "beides".
|
|
456
|
+
*
|
|
457
|
+
* 🔴 `aktuelleVorlage` ist OPTIONAL, NICHT weil die dritte Dimension optional
|
|
458
|
+
* waere, sondern weil das Verdrahten mit echten Vordruck-Pruefsummen an den
|
|
459
|
+
* Aufrufstellen (`kontext-runter.ts`) NICHT zu dieser Aufgabe gehoert (s.
|
|
460
|
+
* Auftrag: "Bau die Lage, nicht ihre Ausgabe") -- und diese Aufgabe darf
|
|
461
|
+
* ausschliesslich in dieser Datei und in `check-kontext.mts` etwas aendern.
|
|
462
|
+
* Bleibt das Argument weg, verhaelt sich diese Funktion BUCHSTAeBLICH wie vor
|
|
463
|
+
* dieser Aufgabe: `vorlage-neuer` kann dann gar nicht entstehen. Das ist kein
|
|
464
|
+
* Kompromiss an der Rechnung, sondern die Rechnung UND der bestehende,
|
|
465
|
+
* unveraenderte Aufrufer bleiben beide wahr.
|
|
466
|
+
*/
|
|
467
|
+
export function lageFuerDatei(a) {
|
|
468
|
+
if (a.lokalerInhalt === a.serverInhalt)
|
|
469
|
+
return "schon-aktuell";
|
|
470
|
+
// Es gibt nichts zu verlieren -- also holen, ohne zu sichern.
|
|
471
|
+
if (a.lokalerInhalt === null)
|
|
472
|
+
return "server-neuer";
|
|
473
|
+
const lokalGeaendert = a.eintrag === null ? true : pruefsumme(a.lokalerInhalt) !== a.eintrag.pruef;
|
|
474
|
+
const serverNeuer = a.eintrag === null ? true : a.eintrag.stand !== a.serverStand;
|
|
475
|
+
const vorlageNeuer = a.aktuelleVorlage === undefined
|
|
476
|
+
? false
|
|
477
|
+
: a.eintrag === null
|
|
478
|
+
? true
|
|
479
|
+
: a.eintrag.vorlage !== a.aktuelleVorlage;
|
|
480
|
+
if (lokalGeaendert)
|
|
481
|
+
return serverNeuer || vorlageNeuer ? "beides" : "lokal-geaendert";
|
|
482
|
+
if (serverNeuer)
|
|
483
|
+
return "server-neuer";
|
|
484
|
+
if (vorlageNeuer)
|
|
485
|
+
return "vorlage-neuer";
|
|
486
|
+
return "schon-aktuell";
|
|
487
|
+
}
|
|
488
|
+
/** `kontext/vorher-2026-08-21` */
|
|
489
|
+
export function sicherungsOrdner(datum) {
|
|
490
|
+
return `${SICHERUNG_BASIS}/vorher-${datum}`;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Wohin die lokale Fassung gesichert wird.
|
|
494
|
+
*
|
|
495
|
+
* ⚠️ FLACH, mit dem Basisnamen -- `CLAUDE.md` und `kontext/systeme.md` landen
|
|
496
|
+
* nebeneinander. Dass sich dabei nichts ueberschreiben kann, sichert der
|
|
497
|
+
* Waechter: Er prueft, dass alle verwalteten Dateien verschiedene
|
|
498
|
+
* Basisnamen haben. Eine kuenftige sechste faellt damit auf, bevor sie eine
|
|
499
|
+
* Sicherung ueberschreibt.
|
|
500
|
+
*/
|
|
501
|
+
export function sicherungsZiel(datum, pfad) {
|
|
502
|
+
const name = pfad.split("/").pop() ?? pfad;
|
|
503
|
+
return `${sicherungsOrdner(datum)}/${name}`;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Von welchem Ordner aus `kimal3 kontext runter` ueberhaupt wissen kann,
|
|
507
|
+
* welche Firma gemeint ist -- **ohne** das Dateisystem selbst anzufassen.
|
|
508
|
+
*
|
|
509
|
+
* 🔑 WARUM DIESE FUNKTION HIER UND NICHT ERST IN EINER SPAETEREN AUFGABE
|
|
510
|
+
* WOHNT: Diese Datei importiert bewusst nur `node:crypto` (s. Kopf). Die
|
|
511
|
+
* Fundregel selbst braucht kein Dateisystem -- nur eine Liste von Kandidaten,
|
|
512
|
+
* die der Aufrufer (spaeter) mit `readdirSync`/`existsSync` gefuellt hat.
|
|
513
|
+
* Die eigentliche Suche gehoert deshalb woanders hin (zieht Umgebung und
|
|
514
|
+
* Heimverzeichnis hoch); ein Waechter fuer eine reine Pfadregel soll das
|
|
515
|
+
* nicht mit auslösen.
|
|
516
|
+
*
|
|
517
|
+
* Fundregel: Vom aktuellen Verzeichnis aufwaerts suchen, bis ein Ordner
|
|
518
|
+
* gefunden ist, der `kontext/` UND `CLAUDE.md` enthaelt.
|
|
519
|
+
*
|
|
520
|
+
* 🔴 DER ERSTE TREFFER GEWINNT, NICHT DER OBERSTE. Die Kandidaten kommen von
|
|
521
|
+
* UNTEN NACH OBEN (aktuelles Verzeichnis zuerst) -- wer in
|
|
522
|
+
* `Kunden/ARNO/KI-Arbeitsplatz` steht, meint ARNO, nicht `Kunden/`. Wuerde
|
|
523
|
+
* stattdessen der oberste Treffer gewinnen, faende der Befehl bei jedem
|
|
524
|
+
* verschachtelten Arbeitsplatz den falschen.
|
|
525
|
+
*
|
|
526
|
+
* 🔑 BEIDES MUSS STIMMEN. Ein Ordner mit `kontext/`, aber ohne `CLAUDE.md`,
|
|
527
|
+
* ist kein Arbeitsplatz -- sonst traefe der Befehl irgendeinen Ordner, in dem
|
|
528
|
+
* jemand zufaellig etwas "kontext" genannt hat.
|
|
529
|
+
*
|
|
530
|
+
* Kein Treffer -> `null`.
|
|
531
|
+
*/
|
|
532
|
+
export function arbeitsplatzAus(kandidaten) {
|
|
533
|
+
for (const k of kandidaten) {
|
|
534
|
+
if (k.hatKontext && k.hatClaude)
|
|
535
|
+
return k.pfad;
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
/*
|
|
540
|
+
🪦 **Hier wohnten `DREI_SAETZE_UEBERSCHRIFT`, der Typ `DreiSaetzeSchnitt` und
|
|
541
|
+
`dreiSaetzeAus` -- ersatzlos gestrichen mit Ruling K-6 (Task 7).**
|
|
542
|
+
|
|
543
|
+
Was sie taten: Sie schnitten den Abschnitt „Die Firma in drei Saetzen" aus
|
|
544
|
+
dem Text der Firmen-`CLAUDE.md`, damit `kimal3 kontext hoch` ihn als
|
|
545
|
+
`drei_saetze` mitschicken konnte. Sie waren die Antwort auf Fund F -- der
|
|
546
|
+
Befehl liess dieses eine Feld aus und meldete trotzdem einen Haken.
|
|
547
|
+
|
|
548
|
+
🔴 **Warum ihr Fall nicht mehr entstehen kann:** Seit Ruling K-18 hat „Die
|
|
549
|
+
Firma in drei Saetzen" eine EIGENE Datei, `kontext/kurzprofil.md`, und seit
|
|
550
|
+
Task 6 stehen in der `CLAUDE.md` nur noch die drei ANTWORTEN -- ohne Anker,
|
|
551
|
+
ohne Ueberschriften. Der Umweg schnitt also ab Task 6 einen Text heraus, den
|
|
552
|
+
die App nicht mehr als beantwortete Fragen lesen kann: EIN `kontext hoch`
|
|
553
|
+
haette drei ankerlose Zeilen in die Spalte geschrieben, `alsStand` haette sie
|
|
554
|
+
als ungepflegt gelesen, und das Formular haette wieder das leere
|
|
555
|
+
Fragengeruest gezeigt -- mit den Saetzen der Firma unsichtbar dahinter.
|
|
556
|
+
|
|
557
|
+
🔑 **Was ihre Aufgabe uebernommen hat:** `kontext/kurzprofil.md` ist seither
|
|
558
|
+
eine Datei wie die vier anderen. Sie steht in {@link FIRMEN_TEXTE} und wird
|
|
559
|
+
gelesen, hochgeschickt und nachgezogen wie `marke.md` auch. *Fuenf
|
|
560
|
+
gleichrangige Dateien, kein Sonderfall, keine Extra-Meldung.*
|
|
561
|
+
|
|
562
|
+
⚠️ Die Ueberschrift steht weiterhin in der Vorlage -- sie ist jetzt aber
|
|
563
|
+
blosser Fliesstext. Kein Code sucht sie mehr; `ausliefern.ts` setzt die drei
|
|
564
|
+
Antworten an der Stelle von `EINSETZ_MARKE_DREI_SAETZE` ein, und dass DIESE
|
|
565
|
+
Marke woertlich in der Vorlage steht, prueft `check:arbeitsplatz`.
|
|
566
|
+
*/
|
|
567
|
+
/**
|
|
568
|
+
* Was ein Mensch lesen soll, wenn `kimal3 kontext runter` Dateien in Ruhe
|
|
569
|
+
* laesst, weil er sie lokal geaendert hat -- je nach Rolle und je nach DATEI.
|
|
570
|
+
*
|
|
571
|
+
* 🔴 **DAS IST FUND F, ZUM ZWEITEN MAL -- und diesmal die echte Fassung
|
|
572
|
+
* (Review-Befund F-3, 2026-08-25).** Der Rat lautete fuer die ganze Liste auf
|
|
573
|
+
* einmal: „Deine Fassung ist die neuere? Dann schick sie hoch: kimal3 kontext
|
|
574
|
+
* hoch". Fuer `CLAUDE.md` ist dieser Satz eine Sackgasse: Sie steht in
|
|
575
|
+
* {@link VERWALTETE_DATEIEN} und landet deshalb in der Liste -- aber
|
|
576
|
+
* `kontext hoch` liest ausschliesslich die fuenf Dateien aus
|
|
577
|
+
* {@link FIRMEN_TEXTE} und meldet danach Erfolg, ohne sie. *Der Mensch tippt
|
|
578
|
+
* den geratenen Befehl, sieht einen Haken, und seine Aenderung steht weiter
|
|
579
|
+
* allein auf seiner Platte.*
|
|
580
|
+
*
|
|
581
|
+
* 🔑 Die ausgelieferte `CLAUDE.md` laedt diese Bearbeitung foermlich ein: Sie
|
|
582
|
+
* sagt selbst, aendern muesse man sie nur, wenn sich die Firma aendert.
|
|
583
|
+
*
|
|
584
|
+
* 🔴 **Der Rat wohnt hier und nicht im Befehl, damit ihn eine Wache sehen
|
|
585
|
+
* kann.** Die spurious Fassung desselben Fehlers (`merkzettelNachHoch`) war
|
|
586
|
+
* behoben, die echte lag vier Runden lang offen -- *ein Satz ueber das
|
|
587
|
+
* Verhalten eines Befehls wohnte im Befehl selbst, wo keine Vorlagen-Wache je
|
|
588
|
+
* hinsieht.* `check:kontext` prueft jetzt genau diese Zuordnung.
|
|
589
|
+
*
|
|
590
|
+
* ⚠️ ECHTE UMLAUTE -- das liest ein Kunde. Die ASCII-Umschrift dieser Datei
|
|
591
|
+
* gilt nur fuer Kommentare.
|
|
592
|
+
*/
|
|
593
|
+
export function ratNachUnberuehrt(unberuehrt, rolle, adminName) {
|
|
594
|
+
const zeilen = [];
|
|
595
|
+
// Fuer alles, was `kontext hoch` wirklich tragen kann.
|
|
596
|
+
if (unberuehrt.some((p) => FIRMEN_TEXTE.some((t) => t.pfad === p))) {
|
|
597
|
+
if (rolle === "admin") {
|
|
598
|
+
zeilen.push(" Deine Fassung ist die neuere? Dann schick sie hoch: kimal3 kontext hoch");
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
const wer = adminName ? ` an ${adminName}` : " an euren Firmen-Admin";
|
|
602
|
+
zeilen.push(" Deine Fassung bleibt stehen — hochschicken darf nur der Firmen-Admin.\n" +
|
|
603
|
+
` Gib deine Ergänzung${wer} weiter; von dort geht sie in der App unter\n` +
|
|
604
|
+
" Firma → Kontext oder mit `kimal3 kontext hoch` an alle.");
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Und fuer die eine Datei, die er nicht tragen kann.
|
|
608
|
+
if (unberuehrt.includes("CLAUDE.md")) {
|
|
609
|
+
zeilen.push(" Die `CLAUDE.md` kann `kimal3 kontext hoch` nicht mitnehmen — der Befehl schickt nur\n" +
|
|
610
|
+
" die fünf Dateien aus `kontext/`. Die drei Sätze in dieser Datei kommen aus\n" +
|
|
611
|
+
" `kontext/kurzprofil.md`, der Firmenname aus der App: Ändere sie dort, dann holt\n" +
|
|
612
|
+
" der nächste `kimal3 kontext runter` sie hierher.\n" +
|
|
613
|
+
" Alles Übrige darin ist unsere Vorlage — dafür gibt es keinen Weg zurück.");
|
|
614
|
+
}
|
|
615
|
+
return zeilen;
|
|
616
|
+
}
|