kimal3 0.1.6 → 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 +3 -0
- package/dist/api.js +77 -0
- 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-katalog.js +49 -0
- package/dist/fragen-lesen.js +178 -0
- package/dist/kimal3.js +9 -0
- package/dist/kontext-dateien.js +248 -0
- package/dist/kontext-stand.js +616 -0
- package/dist/nachricht.js +51 -0
- package/package.json +1 -1
- package/skill/SKILL.md +70 -11
package/README.md
CHANGED
|
@@ -37,6 +37,9 @@ kimal3 login Diesen Rechner mit deinem Konto verbinden
|
|
|
37
37
|
kimal3 skill install Claude beibringen, dieses Werkzeug zu benutzen — und dir die /k3-Befehle geben
|
|
38
38
|
kimal3 arbeitsplatz neu Den ganzen KI-Arbeitsplatz anlegen — Firmenebene und Werkbank
|
|
39
39
|
kimal3 werkbank neu Nur eine eigene Werkbank in einer bestehenden Firma anlegen
|
|
40
|
+
kimal3 kontext runter Den Firmenkontext holen — CLAUDE.md und die fünf Dateien in kontext/
|
|
41
|
+
kimal3 kontext fragen Zeigen, welche Fragen über deine Firma noch unbeantwortet sind
|
|
42
|
+
kimal3 kontext hoch Deine lokalen Änderungen an den fünf Kontext-Dateien hochschicken
|
|
40
43
|
kimal3 projekt neu … Ein Projekt ins Erfolgsprofil eintragen
|
|
41
44
|
kimal3 projekt liste Deine eingetragenen Projekte anzeigen
|
|
42
45
|
kimal3 firma daten Die Daten deiner Firma holen, damit Claude sie auswerten kann
|
package/dist/api.js
CHANGED
|
@@ -1,9 +1,62 @@
|
|
|
1
1
|
import { BASIS_URL, leseAnmeldung } from "./anmeldung.js";
|
|
2
2
|
import { hinweisAusAntwort } from "./nachricht.js";
|
|
3
3
|
import { eigeneVersion } from "./version.js";
|
|
4
|
+
import { findeArbeitsplatz, leseMerkzettelDatei } from "./kontext-dateien.js";
|
|
5
|
+
import { bekannterStandAus, istKopfwertSicher } from "./kontext-stand.js";
|
|
4
6
|
export class NichtAngemeldet extends Error {
|
|
5
7
|
constructor() { super("nicht angemeldet"); }
|
|
6
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Der Wert fuer den Kopf `X-Kimal3-Kontext-Stand` -- `<firmen-id>:<stand>`
|
|
11
|
+
* aus dem lokalen Merkzettel, oder `null`, wenn er sich nicht bilden laesst
|
|
12
|
+
* (Task 10, Step 1).
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ WIRFT NIE -- dieselbe Regel wie beim Update-Kanal insgesamt: Ein
|
|
15
|
+
* Nebenschauplatz darf keinen Befehl umwerfen. `findeArbeitsplatz` und
|
|
16
|
+
* `leseMerkzettelDatei` werfen zwar selbst schon nicht (s. ihre eigenen
|
|
17
|
+
* Kommentare), das `try` ist trotzdem das Netz fuer den Fall, dass sich das
|
|
18
|
+
* einmal aendert.
|
|
19
|
+
*
|
|
20
|
+
* 🔴 **Fix-Runde 1, Fund 1:** `firma_id` und jeder `stand` kommen aus dem
|
|
21
|
+
* Merkzettel auf der Platte des Kunden -- einer Datei, die ausdruecklich zum
|
|
22
|
+
* Von-Hand-Aufmachen gedacht ist (s. Kopf-Kommentar an `pruefsumme` in
|
|
23
|
+
* `kontext-stand.ts`). `leseMerkzettel` prueft nur `typeof === "string"`, nie
|
|
24
|
+
* den INHALT. Ohne `istKopfwertSicher` wuerde ein einziges Nicht-ASCII-
|
|
25
|
+
* Zeichen darin `headers.set(...)` in `ruf()` unten zum Werfen bringen --
|
|
26
|
+
* und zwar bei JEDEM folgenden Befehl, nicht nur bei `kontext`-Befehlen. Der
|
|
27
|
+
* Kunde saehe "kimal3 ist gerade nicht erreichbar" und suchte im Netz, waehrend
|
|
28
|
+
* der Fehler auf seiner eigenen Platte sitzt.
|
|
29
|
+
*
|
|
30
|
+
* 🔑 **Warum stillschweigend `null`, keine Warnung:** Dieselbe Antwort wie
|
|
31
|
+
* bei den drei anderen Wirklichkeiten, die diese Funktion schon zu `null`
|
|
32
|
+
* zusammenfaltet (kein Arbeitsplatz, keine bekannte `firma_id` in allen drei
|
|
33
|
+
* Lesarten von Task 10a). Eine Warnung liefe bei JEDEM Befehl erneut, bis der
|
|
34
|
+
* Merkzettel neu geschrieben wird (naechstes `kontext runter`/`kontext hoch`
|
|
35
|
+
* heilt das automatisch) -- und ein Nebenschauplatz, der ploetzlich auf jedem
|
|
36
|
+
* Befehl eine unerwartete Zeile ausgibt, ist selbst ein Verstoss gegen die
|
|
37
|
+
* Regel, die ihn ueberhaupt rechtfertigt ("darf keinen Befehl umwerfen" ist
|
|
38
|
+
* eng verwandt mit "darf keinen Befehl verunstalten"). *Wie viele
|
|
39
|
+
* verschiedene Wirklichkeiten enden auf `null` -- und ist jede ein sicherer
|
|
40
|
+
* Ausgang?* Vier, und JEDE fuehrt zum selben, harmlosen Ergebnis: kein Kopf,
|
|
41
|
+
* kein Hinweis, der Befehl laeuft unveraendert weiter.
|
|
42
|
+
*/
|
|
43
|
+
function kontextStandKopf() {
|
|
44
|
+
try {
|
|
45
|
+
const ordner = findeArbeitsplatz(process.cwd());
|
|
46
|
+
if (ordner === null)
|
|
47
|
+
return null;
|
|
48
|
+
const merkzettel = leseMerkzettelDatei(ordner);
|
|
49
|
+
if (merkzettel.firma_id === null)
|
|
50
|
+
return null;
|
|
51
|
+
const wert = `${merkzettel.firma_id}:${bekannterStandAus(merkzettel)}`;
|
|
52
|
+
if (!istKopfwertSicher(wert))
|
|
53
|
+
return null;
|
|
54
|
+
return wert;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
7
60
|
/**
|
|
8
61
|
* Ein Aufruf gegen die App. `mitAusweis` haengt den Schluessel an.
|
|
9
62
|
*
|
|
@@ -40,6 +93,30 @@ export async function ruf(pfad, optionen = {}) {
|
|
|
40
93
|
const version = eigeneVersion();
|
|
41
94
|
if (version !== null)
|
|
42
95
|
kopf["X-Kimal3-Version"] = version;
|
|
96
|
+
/*
|
|
97
|
+
🔴 DER ZWEITE KOPF (Task 10) -- der lokal bekannte Firmenstand, damit der
|
|
98
|
+
Server einen firmenbezogenen Hinweis anhaengen kann ("deine Firma hat den
|
|
99
|
+
Kontext geaendert"). Der Server weiss nicht, was auf DIESER Platte liegt
|
|
100
|
+
-- deshalb schickt die CLI ihn mit, statt dass der Server raet.
|
|
101
|
+
|
|
102
|
+
⚠️ ASCII PFLICHT, aus demselben Grund wie der Versions-Kopf oben: die
|
|
103
|
+
Firmen-ID ist eine UUID, der Stand ein ISO-Zeitstempel oder `"leer"" --
|
|
104
|
+
beides ASCII. Der Firmenname geht NIE hier mit, nur die ID; den Namen
|
|
105
|
+
loest der Server fuer den Hinweistext selbst auf (Step 4, US-8).
|
|
106
|
+
|
|
107
|
+
🔴 GAR KEIN KOPF, wenn sich kein Arbeitsplatz findet ODER der lokale
|
|
108
|
+
Merkzettel keine Firmen-ID kennt -- letzteres kollabiert bewusst DREI
|
|
109
|
+
Wirklichkeiten in einen Wert (altes Merkzettel-Format ohne das Feld,
|
|
110
|
+
kaputtes JSON, ausdruecklich `null`, s. `leseMerkzettel` in
|
|
111
|
+
`kontext-stand.ts`). Diese Funktion unterscheidet sie nicht: Alle drei
|
|
112
|
+
heissen "wir wissen die Firma nicht sicher", und "kein Kopf" ist in
|
|
113
|
+
JEDEM der drei Faelle die richtige, sichere Antwort -- kein Hinweis
|
|
114
|
+
bleibt der Vorgabewert, auch wenn er einen der drei Faelle zu Unrecht
|
|
115
|
+
(aber folgenlos) mit erwischt.
|
|
116
|
+
*/
|
|
117
|
+
const kontextKopf = kontextStandKopf();
|
|
118
|
+
if (kontextKopf !== null)
|
|
119
|
+
kopf["X-Kimal3-Kontext-Stand"] = kontextKopf;
|
|
43
120
|
const methode = optionen.methode ?? "POST";
|
|
44
121
|
// Content-Type nur, wenn wirklich etwas im Rumpf steht. Ein GET mit
|
|
45
122
|
// "Content-Type: application/json" und ohne Rumpf ist eine Luege ueber die
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { FIRMEN_TEXTE } from "../kontext-stand.js";
|
|
2
|
+
import { fragenAus, traegtFragenVon } from "../fragen-lesen.js";
|
|
3
|
+
import { leseEineDatei } from "../kontext-dateien.js";
|
|
4
|
+
import { arbeitsplatzOderAbsage, fremdeDateiAbsage } from "./kontext-gemeinsam.js";
|
|
5
|
+
/**
|
|
6
|
+
* `kimal3 kontext fragen` -- zeigt, welche Fragen ueber die Firma noch offen
|
|
7
|
+
* sind.
|
|
8
|
+
*
|
|
9
|
+
* 🔴 **DIESER BEFEHL FUEHRT KEIN GESPRAECH.** Er gibt Daten aus: Datei, Anker,
|
|
10
|
+
* Frage, Hinweis. Das Gespraech fuehrt Claude, angeleitet vom Skill. *Ein
|
|
11
|
+
* Werkzeug, das selbst ein Gespraech fuehren will, baut eine zweite,
|
|
12
|
+
* schlechtere Fassung von dem, wofuer der Kunde Claude ohnehin schon hat.*
|
|
13
|
+
*
|
|
14
|
+
* 🔑 **Er braucht kein Netz und keine Anmeldung.** Alles, was er wissen muss,
|
|
15
|
+
* steht in den fuenf Dateien auf der Platte -- die Fragen samt Ankern und
|
|
16
|
+
* Hinweisen sind Teil des ausgelieferten Textes. Deshalb laeuft er auch dann,
|
|
17
|
+
* wenn der Ausweis abgelaufen ist, und deshalb kann er nichts kaputt machen:
|
|
18
|
+
* **er liest, er schreibt nirgends.**
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ **Die Reihenfolge ist die von `FIRMEN_TEXTE`** -- dieselbe wie die der
|
|
21
|
+
* Felder im Formular auf `/firma/kontext`. Wer beides nebeneinander hat, findet
|
|
22
|
+
* dieselbe Frage an derselben Stelle.
|
|
23
|
+
*/
|
|
24
|
+
/*
|
|
25
|
+
🔴 **ECHTE UMLAUTE, auch hier** (Review-Befund J2). Diese Zeilen liest ein
|
|
26
|
+
Kunde im Terminal -- die ASCII-Umschrift dieses Ordners gilt ausschliesslich
|
|
27
|
+
fuer Kommentare. Hier stand eine Fassung in Umschrift, waehrend die
|
|
28
|
+
Erfolgsausgabe derselben Datei Umlaute setzte: *eine Datei, zwei Schreibweisen,
|
|
29
|
+
und der Unterschied war nicht zu begruenden.*
|
|
30
|
+
*/
|
|
31
|
+
const HILFE = `
|
|
32
|
+
kimal3 kontext fragen
|
|
33
|
+
|
|
34
|
+
Zeigt, welche Fragen über deine Firma in kontext/ noch unbeantwortet sind —
|
|
35
|
+
mit Datei, Anker, Frage und Hinweis.
|
|
36
|
+
|
|
37
|
+
Der Befehl liest nur. Er braucht weder Netz noch Anmeldung und ändert nichts.
|
|
38
|
+
|
|
39
|
+
Antworten trägst du in die genannte Datei unter ihre Frage ein; danach
|
|
40
|
+
schickt "kimal3 kontext hoch" sie an die Firma (nur als Admin).
|
|
41
|
+
`;
|
|
42
|
+
/** `[optional]` hinter einer Frage, die keine Pflicht ist -- sonst nichts. */
|
|
43
|
+
function zusatz(frage) {
|
|
44
|
+
return frage.pflicht ? "" : " [optional]";
|
|
45
|
+
}
|
|
46
|
+
/*
|
|
47
|
+
⚠️ **Bewusst NICHT `async`, anders als die Nachbarn `kontextRunter` und
|
|
48
|
+
`kontextHoch`.** Die beiden warten auf den Server; dieser Befehl nicht. Ein
|
|
49
|
+
`async` ohne `await` waere ein Versprechen auf Arbeit, die nicht stattfindet
|
|
50
|
+
-- und beim Lesen die falsche Erwartung, dass hier etwas ueber das Netz geht.
|
|
51
|
+
Der Verteiler in `kimal3.ts` ist selbst `async` und nimmt beides.
|
|
52
|
+
*/
|
|
53
|
+
export function kontextFragen(argv) {
|
|
54
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
55
|
+
console.log(HILFE);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
/*
|
|
59
|
+
⚠️ **Der Befehl nimmt GAR KEIN Argument -- auch kein `--firma`.** Er fragt
|
|
60
|
+
den Server nicht, also gibt es keine Firma zu waehlen; und er liest fest die
|
|
61
|
+
fuenf Dateien neben sich, also auch keinen Dateinamen.
|
|
62
|
+
|
|
63
|
+
🔴 Geprueft wird auf JEDES Argument, nicht nur auf solche mit `-` davor. Ein
|
|
64
|
+
stillschweigend geschlucktes `--firma "Nordwind"` waere schlimmer als eine
|
|
65
|
+
Absage -- der Mensch glaubte dann, er habe eine Firma ausgewaehlt -- und ein
|
|
66
|
+
verschlucktes `marke` genauso: Er saehe alle 17 Fragen und hielte sie fuer
|
|
67
|
+
die einer Datei.
|
|
68
|
+
*/
|
|
69
|
+
if (argv.length > 0) {
|
|
70
|
+
console.error(`„${argv[0]}" kenne ich hier nicht — dieser Befehl nimmt keine Angaben.`);
|
|
71
|
+
console.error(" Er liest die fünf Dateien in kontext/ neben dir und gibt aus, was offen ist.");
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
const ordner = arbeitsplatzOderAbsage();
|
|
75
|
+
if (ordner === null)
|
|
76
|
+
return 1;
|
|
77
|
+
/*
|
|
78
|
+
🔴 **EINE FEHLENDE DATEI IST EIN ABBRUCH, KEINE LUECKE IN DER LISTE.**
|
|
79
|
+
Wer aus vier von fuenf Dateien eine Liste offener Fragen baut und sie als
|
|
80
|
+
die Antwort hinlegt, sagt einem Menschen, er sei fertig, waehrend drei
|
|
81
|
+
Fragen nirgends stehen. Dieselbe Regel wie bei `kontext hoch`: lieber ein
|
|
82
|
+
Abbruch, der sagt warum, als ein halber Stand, den niemand benennen kann.
|
|
83
|
+
*/
|
|
84
|
+
const gelesen = [];
|
|
85
|
+
for (const datei of FIRMEN_TEXTE) {
|
|
86
|
+
const antwort = leseEineDatei(ordner, datei.pfad);
|
|
87
|
+
if (!antwort.ok) {
|
|
88
|
+
console.error(antwort.meldung);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
if (antwort.inhalt === null) {
|
|
92
|
+
console.error(`„${datei.pfad}" gibt es hier nicht — dann fehlen mir ihre Fragen.`);
|
|
93
|
+
console.error(" `kimal3 kontext runter` holt die Datei; danach noch einmal.");
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
/*
|
|
97
|
+
🔴 **EINE DATEI OHNE IHRE FRAGEN IST DERSELBE FALL WIE EINE FEHLENDE**
|
|
98
|
+
(Review-Befund J1). Ohne diese Zeilen lieferte `fragenAus` eine leere
|
|
99
|
+
Liste, und die Datei fiel aus BEIDEN Summen -- gemessen mit der
|
|
100
|
+
ausgelieferten `README.md` als `kontext/marke.md`: „12 von 12 Fragen",
|
|
101
|
+
„Die Marke" nirgends genannt. Nach zwoelf Antworten haette der Befehl
|
|
102
|
+
`✓ Alle 12 Fragen … sind beantwortet` gemeldet, und `kontext hoch` waere
|
|
103
|
+
einen Schritt spaeter -- im Ablauf, den unser eigener Skill vorgibt --
|
|
104
|
+
auf genau dieser Datei mit Rueckgabewert 1 abgebrochen.
|
|
105
|
+
|
|
106
|
+
🔑 Woertlich dieselbe Beschreibung des Ordners wie dort
|
|
107
|
+
(`fremdeDateiAbsage`): Zwei Befehle, die dieselbe Lage verschieden
|
|
108
|
+
benennen, sind zwei Wahrheiten.
|
|
109
|
+
*/
|
|
110
|
+
if (!traegtFragenVon(antwort.inhalt, datei.ankerPraefix)) {
|
|
111
|
+
fremdeDateiAbsage(datei.pfad, datei.titel, datei.ankerPraefix, "Ich kann ihre Fragen deshalb nicht zählen — und zähle dann gar nicht.");
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
gelesen.push({ pfad: datei.pfad, titel: datei.titel, fragen: fragenAus(antwort.inhalt) });
|
|
115
|
+
}
|
|
116
|
+
const gesamt = gelesen.reduce((summe, d) => summe + d.fragen.length, 0);
|
|
117
|
+
const offen = gelesen.reduce((summe, d) => summe + d.fragen.filter((f) => f.antwort === "").length, 0);
|
|
118
|
+
/*
|
|
119
|
+
🔑 **Alles beantwortet -> EIN Satz, keine Liste** (Auftrag, Step 1). Eine
|
|
120
|
+
leere Liste mit Ueberschriften sieht aus wie ein Fehler; ein Satz ist eine
|
|
121
|
+
Antwort.
|
|
122
|
+
|
|
123
|
+
⚠️ Er sagt „in deinen Kontext-Dateien" und nicht „alle Fragen deiner
|
|
124
|
+
Firma". Gemessen wurde, was in diesen fuenf Dateien steht -- wer einen
|
|
125
|
+
Anker geloescht hat, hat eine Frage, die hier nicht auftaucht (s.
|
|
126
|
+
`fragen-lesen.ts`). *Ein Satz darf nur behaupten, was seine Messung
|
|
127
|
+
deckt.*
|
|
128
|
+
*/
|
|
129
|
+
if (offen === 0) {
|
|
130
|
+
console.log(`✓ Alle ${gesamt} Fragen, die in deinen Dateien stehen, sind beantwortet.`);
|
|
131
|
+
/*
|
|
132
|
+
🔴 **DIE ZWEITE ZEILE BENENNT DIE LUECKE, STATT SIE ZU VERSTECKEN**
|
|
133
|
+
(Review-Befund J5). Gezaehlt wird, was in den fuenf Dateien steht. Wer den
|
|
134
|
+
Ankerkommentar EINER Frage loescht und sie unbeantwortet laesst, hat sie
|
|
135
|
+
hier nicht mehr -- `fragenAus` erkennt eine Frage am Anker (Ruling K-14),
|
|
136
|
+
die App zusaetzlich am Wortlaut. Dann stuende hier `Alle 16 …`, waehrend
|
|
137
|
+
die App weiter „16 von 17" fuehrt.
|
|
138
|
+
|
|
139
|
+
🔑 Der Ausweg gehoert in dieselbe Ausgabe wie die Zahl. *Eine Zahl, deren
|
|
140
|
+
Grenze man kennt, gehoert nicht ohne ihre Grenze hingeschrieben* -- sonst
|
|
141
|
+
liest ein Mensch „fertig" und die App widerspricht ihm morgen.
|
|
142
|
+
*/
|
|
143
|
+
console.log(" Gezählt ist, was in kontext/ steht. Nennt die App unter Firma → Kontext");
|
|
144
|
+
console.log(" eine höhere Zahl, fehlt in einer Datei eine Frage — `kimal3 kontext runter`");
|
|
145
|
+
console.log(" holt sie zurück.");
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
console.log(`${offen} von ${gesamt} Fragen über deine Firma sind noch offen.\n`);
|
|
149
|
+
for (const datei of gelesen) {
|
|
150
|
+
const offeneDieserDatei = datei.fragen.filter((f) => f.antwort === "");
|
|
151
|
+
if (offeneDieserDatei.length === 0)
|
|
152
|
+
continue;
|
|
153
|
+
console.log(`${datei.pfad} — ${datei.titel}`);
|
|
154
|
+
for (const frage of offeneDieserDatei) {
|
|
155
|
+
console.log(` <!-- k3:${frage.anker} -->${zusatz(frage)}`);
|
|
156
|
+
console.log(` ## ${frage.ueberschrift}`);
|
|
157
|
+
if (frage.hinweis !== "")
|
|
158
|
+
console.log(` ${frage.hinweis}`);
|
|
159
|
+
console.log("");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
console.log(" Die Antwort kommt in der genannten Datei unter ihre Überschrift.");
|
|
163
|
+
console.log(" Danach als Admin: kimal3 kontext hoch");
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { feld } from "../api.js";
|
|
2
|
+
import { schlussFehler } from "../fehler.js";
|
|
3
|
+
import { BASIS_URL } from "../anmeldung.js";
|
|
4
|
+
import { findeArbeitsplatz } from "../kontext-dateien.js";
|
|
5
|
+
/**
|
|
6
|
+
* Was `kontext runter` (`kontext-runter.ts`) und `kontext hoch`
|
|
7
|
+
* (`kontext-hoch.ts`) TEILEN -- die `--firma`/`--firma-id`-Optionen und die
|
|
8
|
+
* Absage-Logik fuer `/api/cli/kontext`.
|
|
9
|
+
*
|
|
10
|
+
* 🔴 **Ruling T9-2 (Split von `kontext.ts`, ehemals 400 Code-Zeilen mit zwei
|
|
11
|
+
* Befehlen).** Diese Datei existiert, damit die 409-Firmenwahl-Logik nicht
|
|
12
|
+
* zweimal existiert: zwei Kopien einer Entscheidung ueber Sichern/
|
|
13
|
+
* Ueberschreiben sind genau das Fehlerbild, das dieses Projekt schon mehrfach
|
|
14
|
+
* teuer bezahlt hat.
|
|
15
|
+
*
|
|
16
|
+
* 🔑 Was hier NICHT steht: alles, das nur EINE der beiden Richtungen betrifft.
|
|
17
|
+
* Die Fundregel-Implementierung (`findeArbeitsplatz`) bleibt in
|
|
18
|
+
* `kontext-dateien.ts` -- sie war schon vorher gemeinsam genutzt, aber importiert
|
|
19
|
+
* (Ruling T8-1), nicht hier definiert. Die Antwort-Formen `KontextAntwort` und
|
|
20
|
+
* `HochAntwort` bleiben bei ihrem jeweiligen Befehl, weil sie unterschiedliche
|
|
21
|
+
* Felder pruefen.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Den Arbeitsplatz suchen -- und, wenn keiner da ist, die Absage GLEICH
|
|
25
|
+
* ausgeben.
|
|
26
|
+
*
|
|
27
|
+
* 🔴 **Sie steht hier, weil es jetzt DREI Aufrufer gibt** (`kontext runter`,
|
|
28
|
+
* `kontext hoch`, `kontext fragen`). Bis Task 7 waren es zwei, und beide
|
|
29
|
+
* trugen dieselben vier Zeilen abgetippt. Bei der dritten Kopie ist es keine
|
|
30
|
+
* Wiederholung mehr, sondern eine Falle: Wer eine Zeile schaerft, schaerft
|
|
31
|
+
* eine von dreien, und zwei Befehle erklaeren dieselbe Lage danach anders.
|
|
32
|
+
*
|
|
33
|
+
* `null` heisst "kein Arbeitsplatz" -- die Meldung steht dann schon auf
|
|
34
|
+
* stderr, der Aufrufer gibt nur noch `1` zurueck.
|
|
35
|
+
*/
|
|
36
|
+
export function arbeitsplatzOderAbsage() {
|
|
37
|
+
const ordner = findeArbeitsplatz(process.cwd());
|
|
38
|
+
if (ordner !== null)
|
|
39
|
+
return ordner;
|
|
40
|
+
console.error("Hier steht kein KI-Arbeitsplatz -- ich suche von hier aufwaerts nach einem");
|
|
41
|
+
console.error("Ordner, der `kontext/` UND `CLAUDE.md` enthaelt.");
|
|
42
|
+
console.error("");
|
|
43
|
+
console.error(" Wechsle in deinen Arbeitsplatz-Ordner und versuch es dort noch einmal.");
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Was ein Mensch liest, wenn eine der fuenf Dateien keine einzige ihrer Fragen
|
|
48
|
+
* mehr traegt -- **woertlich dieselben Zeilen fuer `kontext hoch` und
|
|
49
|
+
* `kontext fragen`** (Review-Befund J1).
|
|
50
|
+
*
|
|
51
|
+
* 🔴 **WARUM SIE GETEILT WIRD.** `kontext fragen` zaehlte eine solche Datei
|
|
52
|
+
* vorher stillschweigend mit NULL Fragen und liess sie aus beiden Summen
|
|
53
|
+
* fallen. Gemessen mit der ausgelieferten `README.md` als `kontext/marke.md`:
|
|
54
|
+
* „12 von 12 Fragen" statt 17, „Die Marke" nirgends genannt -- und nach zwoelf
|
|
55
|
+
* Antworten meldete der Befehl `✓ Alle 12 Fragen … sind beantwortet`, waehrend
|
|
56
|
+
* `kontext hoch` einen Schritt spaeter auf genau dieser Datei abbrach.
|
|
57
|
+
* *Der lesende Befehl gab die beruhigende Auskunft, der schreibende
|
|
58
|
+
* verweigerte sich.* Das ist die Form von Fund F, und es ist die Regel, gegen
|
|
59
|
+
* die diese Aufgabe gebaut ist: **Ein Fehlerfall darf nie denselben Namen
|
|
60
|
+
* tragen wie ein Erfolgsfall.**
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ `folge` ist die EINE Zeile, die sich unterscheiden darf -- was der
|
|
63
|
+
* jeweilige Befehl gerade nicht getan hat. Die Beschreibung des Ordners
|
|
64
|
+
* (Zeile 1 und 2) ist bei beiden Zeichen fuer Zeichen dieselbe; zwei Fassungen
|
|
65
|
+
* davon wuerden auseinanderlaufen, und dann beschriebe jeder Befehl eine
|
|
66
|
+
* andere Lage.
|
|
67
|
+
*
|
|
68
|
+
* 🔑 ECHTE UMLAUTE -- das hier liest ein Kunde im Terminal. Die ASCII-Umschrift
|
|
69
|
+
* dieses Ordners gilt nur fuer Kommentare.
|
|
70
|
+
*/
|
|
71
|
+
export function fremdeDateiAbsage(pfad, titel, ankerPraefix, folge) {
|
|
72
|
+
console.error(`„${pfad}" sieht nicht aus wie „${titel}". ${folge}`);
|
|
73
|
+
console.error(` Ich finde darin keine einzige Frage dieser Datei (Zeilen wie \`<!-- k3:${ankerPraefix}… -->\`).`);
|
|
74
|
+
console.error(" Hol sie mit `kimal3 kontext runter` zurück und trag deine Antworten dort ein.");
|
|
75
|
+
}
|
|
76
|
+
/** `--firma`/`--firma-id` -- die einzigen Optionen, die beide Richtungen kennen. */
|
|
77
|
+
export const ERLAUBT = ["firma", "firma-id"];
|
|
78
|
+
/**
|
|
79
|
+
* Alles, was `/api/cli/kontext` NICHT mit `200` beantwortet -- fuer GET
|
|
80
|
+
* (`kontextRunter`) und POST (`kontextHoch`) gleichermassen. `befehl` sagt,
|
|
81
|
+
* welcher der beiden fuer die kopierfertigen Zeilen richtig ist.
|
|
82
|
+
*
|
|
83
|
+
* 🔑 Dieselbe Struktur wie `firmaDaten` in `firma-daten.ts` -- 402 vor 401
|
|
84
|
+
* (verlangen Gegenteiliges), 409 nennt die eigenen Firmen bzw. die IDs bei
|
|
85
|
+
* gleichnamigen, der Rest geht an `schlussFehler`. Die kopierfertigen Zeilen
|
|
86
|
+
* unten sind eine BEWUSSTE Kopie und keine Wiederverwendung von
|
|
87
|
+
* `firmen-liste.ts`: Dessen `firmenZeilen`/`firmenIdZeilen` schreiben den
|
|
88
|
+
* Befehl `kimal3 firma daten …` fest in die Zeile -- fuer `kontext *`
|
|
89
|
+
* waere das ein falscher, nicht ausfuehrbarer Rat.
|
|
90
|
+
*
|
|
91
|
+
* 🔴 **`409` heisst hier ZWEI verschiedene Dinge** (Task 9): Bei GET und bei
|
|
92
|
+
* POST kommt ein `409` mit `grund` von `waehleFirma` (mehrdeutige Firmenwahl,
|
|
93
|
+
* unten weiterbehandelt) -- POST kann ZUSAETZLICH `grund: "veraltet"` senden
|
|
94
|
+
* (§7d, `postRoh` in `route.ts`): Die Firma wurde inzwischen von jemand
|
|
95
|
+
* anderem geaendert. Die `meldung` dafuer nennt bereits WER und WANN sowie
|
|
96
|
+
* den Ausweg (`--ueberschreiben`) -- sie kommt woertlich vom Server und wird
|
|
97
|
+
* hier nur ausgegeben, nicht neu formuliert.
|
|
98
|
+
*/
|
|
99
|
+
export function absage(antwort, befehl) {
|
|
100
|
+
const meldung = feld(antwort.koerper, "meldung") ?? "Das hat nicht geklappt.";
|
|
101
|
+
if (antwort.status === 402) {
|
|
102
|
+
console.error(meldung);
|
|
103
|
+
return 4;
|
|
104
|
+
}
|
|
105
|
+
if (antwort.status === 401) {
|
|
106
|
+
console.error(`${meldung} Bitte \`kimal3 login\` ausführen.`);
|
|
107
|
+
return 2;
|
|
108
|
+
}
|
|
109
|
+
if (antwort.status === 409) {
|
|
110
|
+
const grund = feld(antwort.koerper, "grund");
|
|
111
|
+
if (grund === "veraltet") {
|
|
112
|
+
console.error(meldung);
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
const koerper = antwort.koerper;
|
|
116
|
+
const namen = Array.isArray(koerper?.firmen)
|
|
117
|
+
? koerper.firmen.filter((x) => typeof x === "string")
|
|
118
|
+
: [];
|
|
119
|
+
console.error(meldung);
|
|
120
|
+
const auswahl = Array.isArray(koerper.auswahl)
|
|
121
|
+
? koerper.auswahl.filter((x) => !!x &&
|
|
122
|
+
typeof x === "object" &&
|
|
123
|
+
typeof x.id === "string" &&
|
|
124
|
+
typeof x.name === "string")
|
|
125
|
+
: [];
|
|
126
|
+
if (auswahl.length > 0) {
|
|
127
|
+
console.error("\n Diese beiden heissen gleich — nimm die ID:");
|
|
128
|
+
firmenIdZeilenKontext(auswahl, befehl).forEach((z) => console.error(z));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const zeilen = firmenZeilenKontext(namen, befehl);
|
|
132
|
+
if (zeilen.length > 0) {
|
|
133
|
+
console.error("\n Deine Firmen:");
|
|
134
|
+
zeilen.forEach((z) => console.error(z));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (grund === "keine-firma") {
|
|
138
|
+
console.error(`\n Eine Firma anlegen: ${BASIS_URL}/firma/neu`);
|
|
139
|
+
}
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
const schluss = schlussFehler(antwort.status, meldung);
|
|
143
|
+
console.error(schluss.text);
|
|
144
|
+
return schluss.wert;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* `firmenZeilen` aus `firmen-liste.ts`, nur mit dem richtigen Befehl in der
|
|
148
|
+
* Zeile -- s. Kommentar an `absage`.
|
|
149
|
+
*/
|
|
150
|
+
function firmenZeilenKontext(namen, befehl) {
|
|
151
|
+
const anzahl = (name) => namen.filter((n) => n === name).length;
|
|
152
|
+
const gesehen = new Set();
|
|
153
|
+
const zeilen = [];
|
|
154
|
+
for (const name of namen) {
|
|
155
|
+
if (gesehen.has(name))
|
|
156
|
+
continue;
|
|
157
|
+
gesehen.add(name);
|
|
158
|
+
const wieOft = anzahl(name);
|
|
159
|
+
if (wieOft === 1) {
|
|
160
|
+
zeilen.push(` kimal3 ${befehl} --firma "${name}"`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
zeilen.push(` „${name}" — kommt ${wieOft}× vor und ist über den Namen nicht wählbar.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return zeilen;
|
|
167
|
+
}
|
|
168
|
+
/** `firmenIdZeilen` aus `firmen-liste.ts`, nur mit dem richtigen Befehl. */
|
|
169
|
+
function firmenIdZeilenKontext(auswahl, befehl) {
|
|
170
|
+
return auswahl.map((f, i) => ` kimal3 ${befehl} --firma-id "${f.id}" # ${f.name} (${i + 1}.)`);
|
|
171
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { zerlege } from "../argumente.js";
|
|
2
|
+
import { ruf } from "../api.js";
|
|
3
|
+
import { FIRMEN_TEXTE, alsMerkzettel, bekannterStandAus, merkzettelNachHoch, } from "../kontext-stand.js";
|
|
4
|
+
import { traegtFragenVon } from "../fragen-lesen.js";
|
|
5
|
+
import { leseEineDatei, leseMerkzettelDatei, schreibeMerkzettelDatei } from "../kontext-dateien.js";
|
|
6
|
+
import { ERLAUBT, absage, arbeitsplatzOderAbsage, fremdeDateiAbsage } from "./kontext-gemeinsam.js";
|
|
7
|
+
/*
|
|
8
|
+
🪦 **Hier stand `feldSchluessel`, gestrichen mit Ruling K-6 (Task 7).**
|
|
9
|
+
|
|
10
|
+
Sie leitete den Feldnamen der Route aus dem Dateinamen ab (`marke.md` ->
|
|
11
|
+
`marke_md`). Fuer vier der fuenf Dateien war das richtig; die fuenfte,
|
|
12
|
+
`kontext/kurzprofil.md`, fuellt `drei_saetze` und haette daraus `kurzprofil_md`
|
|
13
|
+
bekommen -- ein Feld, das die Route nicht kennt. Die Zuordnung steht jetzt als
|
|
14
|
+
Tabelle in `FIRMEN_TEXTE` (`../kontext-stand.js`), und dass sie mit `FELDER`
|
|
15
|
+
in der App uebereinstimmt, prueft `check:kontext`.
|
|
16
|
+
*/
|
|
17
|
+
// 📌 `bekannterStandAus` wohnte hier (Fix-Runde 1, Review-Fund 1) und ist seit
|
|
18
|
+
// Task 10 nach `../kontext-stand.js` umgezogen -- `cli/src/api.ts` braucht
|
|
19
|
+
// dieselbe Rechnung fuer den Kopf `X-Kimal3-Kontext-Stand`, und ein Import
|
|
20
|
+
// von HIER haette `api.ts` in einen Kreisimport mit sich selbst gezwungen
|
|
21
|
+
// (diese Datei importiert `ruf` aus `../api.js`). Die volle Begruendung der
|
|
22
|
+
// Rechnung selbst steht jetzt dort.
|
|
23
|
+
const HILFE_HOCH = `
|
|
24
|
+
kimal3 kontext hoch [--firma "Name" | --firma-id "…"] [--ueberschreiben]
|
|
25
|
+
|
|
26
|
+
Schickt deine lokalen Aenderungen an allen fuenf Firmen-Texten nach oben --
|
|
27
|
+
fuer den Firmen-Admin. Nur wer Admin ist, darf das; die App entscheidet.
|
|
28
|
+
|
|
29
|
+
Die fuenf sind die Dateien in kontext/: kurzprofil.md, firma.md, marke.md,
|
|
30
|
+
systeme.md, sicherheit.md.
|
|
31
|
+
|
|
32
|
+
Welche Fragen darin noch offen sind, zeigt "kimal3 kontext fragen".
|
|
33
|
+
|
|
34
|
+
--firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst.
|
|
35
|
+
Die Absage nennt dir die Namen als fertige Befehle.
|
|
36
|
+
|
|
37
|
+
--firma-id "…" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
|
|
38
|
+
|
|
39
|
+
--ueberschreiben Speichert auch dann, wenn die Firma inzwischen von
|
|
40
|
+
jemand anderem geaendert wurde (§7d).
|
|
41
|
+
`;
|
|
42
|
+
/**
|
|
43
|
+
* Schickt die fuenf Firmen-Texte des gefundenen Arbeitsplatzes nach oben --
|
|
44
|
+
* die Gegenrichtung zu `kontextRunter` (`kontext-runter.ts`). Fundregel,
|
|
45
|
+
* Firmenwahl-Absage und die `--firma`/`--firma-id`-Optionen teilt sie sich mit
|
|
46
|
+
* `kontextRunter` ueber `kontext-gemeinsam.ts` (Ruling T9-2).
|
|
47
|
+
*
|
|
48
|
+
* 🔴 **FUENF GLEICHRANGIGE DATEIEN IN `kontext/` -- kein Sonderfall mehr
|
|
49
|
+
* (Ruling K-6, Task 7).** Bis hierher las dieser Befehl die drei Saetze aus der
|
|
50
|
+
* Firmen-`CLAUDE.md` heraus. Seit Task 6 stehen dort nur noch die ANTWORTEN,
|
|
51
|
+
* ohne Anker und ohne Ueberschriften -- zurueckgeschickt haetten sie die Spalte
|
|
52
|
+
* mit einem Text gefuellt, in dem die App keine einzige beantwortete Frage
|
|
53
|
+
* findet. Das Formular haette danach wieder das leere Fragengeruest gezeigt,
|
|
54
|
+
* mit den Saetzen der Firma unsichtbar dahinter. Gepflegt wird stattdessen
|
|
55
|
+
* `kontext/kurzprofil.md`, wie die vier anderen auch.
|
|
56
|
+
*
|
|
57
|
+
* 🔴 **Importiert bewusst KEIN `FELDER` aus `src/app/firma/kontext-felder.ts`.**
|
|
58
|
+
* `cli/src/` ist ein eigenes Paket mit eigenem `tsconfig` und darf nichts aus
|
|
59
|
+
* `src/` importieren (s. Kopf von `kontext-stand.ts`). Welche Datei welches
|
|
60
|
+
* Feld fuellt, steht deshalb in `FIRMEN_TEXTE` -- und dass diese Tabelle mit
|
|
61
|
+
* `FELDER` uebereinstimmt, ist eine Pruefung in `check:kontext`.
|
|
62
|
+
*/
|
|
63
|
+
export async function kontextHoch(argv) {
|
|
64
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
65
|
+
console.log(HILFE_HOCH);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
const ueberschreiben = argv.includes("--ueberschreiben");
|
|
69
|
+
// ⚠️ `zerlege` kennt keine Optionen ohne Wert -- `--ueberschreiben` wird
|
|
70
|
+
// deshalb VOR der Zerlegung herausgefiltert, genau wie `--help`/`-h` oben
|
|
71
|
+
// nie an `zerlege` gereicht werden.
|
|
72
|
+
const ohneFlag = argv.filter((a) => a !== "--ueberschreiben");
|
|
73
|
+
const zerlegt = zerlege(ohneFlag, ERLAUBT);
|
|
74
|
+
if (!zerlegt.ok) {
|
|
75
|
+
console.error(zerlegt.meldung);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
const ordner = arbeitsplatzOderAbsage();
|
|
79
|
+
if (ordner === null)
|
|
80
|
+
return 1;
|
|
81
|
+
// 🔴 Fix-Runde 1: der bekannte Stand kommt aus ALLEN Merkzettel-Eintraegen
|
|
82
|
+
// (`bekannterStandAus`), nicht nur aus den Dateien, die gleich gelesen
|
|
83
|
+
// werden. Die Firmenzeile traegt genau EIN `geaendert_am` fuer alle fuenf
|
|
84
|
+
// Felder -- jeder nicht-leere Eintrag kennt es also, auch der der `CLAUDE.md`.
|
|
85
|
+
const lokalerMerkzettel = leseMerkzettelDatei(ordner);
|
|
86
|
+
const bekannterStand = bekannterStandAus(lokalerMerkzettel);
|
|
87
|
+
/*
|
|
88
|
+
---- die fuenf Texte lesen ---------------------------------------------
|
|
89
|
+
|
|
90
|
+
🔴 JEDER ABBRUCH HIER PASSIERT VOR DEM ERSTEN `ruf` -- es wird nichts
|
|
91
|
+
gesendet, auch nichts Halbes. Ein Lauf, der vier von fuenf Texten schickt
|
|
92
|
+
und dabei einen Fehler meldet, laesst den Admin ratlos zurueck: Er weiss
|
|
93
|
+
dann nicht, was jetzt in der App steht. *Ein halber Stand, den niemand
|
|
94
|
+
benennen kann, ist teurer als ein Abbruch, der klar sagt, warum.*
|
|
95
|
+
*/
|
|
96
|
+
const werte = {};
|
|
97
|
+
for (const datei of FIRMEN_TEXTE) {
|
|
98
|
+
const gelesen = leseEineDatei(ordner, datei.pfad);
|
|
99
|
+
if (!gelesen.ok) {
|
|
100
|
+
console.error(gelesen.meldung);
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
if (gelesen.inhalt === null) {
|
|
104
|
+
console.error(`„${datei.pfad}" gibt es hier nicht -- ich schicke nichts Halbes hoch.`);
|
|
105
|
+
console.error(" Erst `kimal3 kontext runter` ausfuehren, dann bearbeiten, dann noch einmal.");
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
/*
|
|
109
|
+
🔴 **DIE FALSCHE DATEI ERWISCHT -- DER ABBRUCH (Spec, Abschnitt 11).**
|
|
110
|
+
|
|
111
|
+
Ohne diese Pruefung schickt der Befehl jeden Inhalt hoch, der an dieser
|
|
112
|
+
Stelle liegt. Die App zerlegt ihn gegen den Fragenkatalog GENAU DIESER
|
|
113
|
+
Datei, findet keine einzige Antwort -- und legt den kompletten Text als
|
|
114
|
+
„Zusatz" ab. Die Firma haette danach einen Zusatz statt eines
|
|
115
|
+
Firmentextes, und die CLI haette dazu „✓ hochgeschickt" gemeldet.
|
|
116
|
+
*Ein Fehlerfall darf nie denselben Namen tragen wie ein Erfolgsfall.*
|
|
117
|
+
|
|
118
|
+
Der Fall ist nicht ausgedacht: Er entsteht durch ein verrutschtes
|
|
119
|
+
`cp`/`Copy-Item` ebenso wie durch eine Datei, die jemand von Hand
|
|
120
|
+
angelegt hat, weil `kontext runter` noch nie lief.
|
|
121
|
+
*/
|
|
122
|
+
if (!traegtFragenVon(gelesen.inhalt, datei.ankerPraefix)) {
|
|
123
|
+
fremdeDateiAbsage(datei.pfad, datei.titel, datei.ankerPraefix, "Nichts wurde gespeichert.");
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
werte[datei.feld] = gelesen.inhalt;
|
|
127
|
+
}
|
|
128
|
+
// ---- senden -------------------------------------------------------------
|
|
129
|
+
const wunsch = zerlegt.werte.optionen.firma;
|
|
130
|
+
const wunschId = zerlegt.werte.optionen["firma-id"];
|
|
131
|
+
const suche = new URLSearchParams();
|
|
132
|
+
if (wunsch !== undefined)
|
|
133
|
+
suche.set("firma", wunsch);
|
|
134
|
+
if (wunschId !== undefined)
|
|
135
|
+
suche.set("firma-id", wunschId);
|
|
136
|
+
const pfad = suche.toString() ? `/api/cli/kontext?${suche.toString()}` : "/api/cli/kontext";
|
|
137
|
+
const koerper = { stand: bekannterStand, ...werte };
|
|
138
|
+
if (ueberschreiben)
|
|
139
|
+
koerper.ueberschreiben = true;
|
|
140
|
+
const antwort = await ruf(pfad, { methode: "POST", mitAusweis: true, koerper });
|
|
141
|
+
if (antwort.status !== 200) {
|
|
142
|
+
return absage(antwort, "kontext hoch");
|
|
143
|
+
}
|
|
144
|
+
// 🔴 Ein `200` mit unlesbarem Rumpf ist KEIN Erfolg -- dasselbe Muster wie
|
|
145
|
+
// in `kontextRunter` und in `firma-daten.ts`.
|
|
146
|
+
if (antwort.koerper === null || typeof antwort.koerper !== "object") {
|
|
147
|
+
console.error("Die Antwort war nicht lesbar. Bitte gleich noch einmal.");
|
|
148
|
+
return 3;
|
|
149
|
+
}
|
|
150
|
+
const ergebnis = alsHochAntwort(antwort.koerper);
|
|
151
|
+
if (ergebnis === null) {
|
|
152
|
+
console.error("Die Antwort war nicht lesbar. Bitte gleich noch einmal.");
|
|
153
|
+
return 3;
|
|
154
|
+
}
|
|
155
|
+
/*
|
|
156
|
+
🔴 §6b: Der Merkzettel aus der ANTWORT wird JETZT geschrieben -- im selben
|
|
157
|
+
Zug wie der Erfolg, nicht danach. Ohne ihn saehe das naechste
|
|
158
|
+
`kontext runter` „Server neuer" UND „lokal geaendert", wo keiner ist, und
|
|
159
|
+
sicherte die eigene, gerade erst hochgeladene Fassung unnoetig weg.
|
|
160
|
+
|
|
161
|
+
Schlaegt das Schreiben fehl, ist die Firma trotzdem gespeichert -- das
|
|
162
|
+
Scheitern betrifft nur die lokale Buchfuehrung. Ein Fehlerfall darf nie
|
|
163
|
+
denselben Namen tragen wie ein Erfolgsfall (§8), deshalb sagt die Meldung
|
|
164
|
+
das ausdruecklich, statt einen missglueckten Datei-Schreibvorgang wie ein
|
|
165
|
+
gescheitertes Hochladen aussehen zu lassen.
|
|
166
|
+
*/
|
|
167
|
+
/*
|
|
168
|
+
🔴 **NICHT die Antwort des Servers eins zu eins** -- s. `merkzettelNachHoch`.
|
|
169
|
+
Ueber die Firmen-`CLAUDE.md` weiss dieser Befehl nichts: Er hat sie nicht
|
|
170
|
+
gesendet und schreibt sie nicht. Traege ihr Eintrag die Pruefsumme aus der
|
|
171
|
+
Antwort, meldete der naechste `kontext runter` „du hast lokal geaendert:
|
|
172
|
+
CLAUDE.md" und riete zu `kontext hoch` -- dem Befehl, der sie gar nicht
|
|
173
|
+
mitnimmt. Gemessen auf der Kundenstrecke am 2026-08-25.
|
|
174
|
+
*/
|
|
175
|
+
const zuSchreiben = merkzettelNachHoch(ergebnis.merkzettel, lokalerMerkzettel, FIRMEN_TEXTE.map((d) => d.pfad));
|
|
176
|
+
const merkzettelSchreiben = schreibeMerkzettelDatei(ordner, zuSchreiben);
|
|
177
|
+
if (!merkzettelSchreiben.ok) {
|
|
178
|
+
console.error("Die Firma hat deine Aenderungen gespeichert -- nur der lokale Merkzettel liess sich nicht schreiben:");
|
|
179
|
+
console.error(merkzettelSchreiben.meldung);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
// US-3: WAS hochgeladen wurde, nicht nur "fertig".
|
|
183
|
+
//
|
|
184
|
+
// 🔑 Der Erklaersatz darunter ist mit Fund F ersatzlos WEGGEFALLEN. Er
|
|
185
|
+
// benannte eine Ausnahme, die es nicht mehr gibt -- fuenf Texte, zwei
|
|
186
|
+
// Richtungen, kein Sonderfall zu lernen. Die Namen kommen aus der Antwort
|
|
187
|
+
// der Route (`uebernommen`), nicht aus einer zweiten Liste hier.
|
|
188
|
+
console.log(`✓ hochgeschickt: ${ergebnis.uebernommen.join(", ")}`);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
/** Prueft die Antwort-Form von POST, ohne `any`. */
|
|
192
|
+
function alsHochAntwort(koerper) {
|
|
193
|
+
if (!koerper || typeof koerper !== "object")
|
|
194
|
+
return null;
|
|
195
|
+
const k = koerper;
|
|
196
|
+
const merkzettel = alsMerkzettel(k.merkzettel);
|
|
197
|
+
if (merkzettel === null)
|
|
198
|
+
return null;
|
|
199
|
+
const uebernommenRoh = k.uebernommen;
|
|
200
|
+
if (!Array.isArray(uebernommenRoh) || !uebernommenRoh.every((x) => typeof x === "string"))
|
|
201
|
+
return null;
|
|
202
|
+
return { merkzettel, uebernommen: uebernommenRoh };
|
|
203
|
+
}
|