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,254 @@
|
|
|
1
|
+
import { zerlege } from "../argumente.js";
|
|
2
|
+
import { ruf } from "../api.js";
|
|
3
|
+
import { heuteAus } from "../nachricht.js";
|
|
4
|
+
import { istUnsichererPfad } from "../arbeitsplatz-dateien.js";
|
|
5
|
+
import { LEER, VERWALTETE_DATEIEN, alsMerkzettel, sicherungsOrdner, sicherungsZiel, lageFuerDatei, ratNachUnberuehrt, } from "../kontext-stand.js";
|
|
6
|
+
import { leseEineDatei, leseMerkzettelDatei, schreibeMerkzettelDatei, schreibeVerwalteteDatei, sichereDatei, } from "../kontext-dateien.js";
|
|
7
|
+
import { ERLAUBT, absage, arbeitsplatzOderAbsage } from "./kontext-gemeinsam.js";
|
|
8
|
+
/**
|
|
9
|
+
* Holt den Firmenkontext von der App und legt ihn in den verwalteten Dateien
|
|
10
|
+
* des gefundenen Arbeitsplatzes ab -- `CLAUDE.md` und die fuenf Dateien in
|
|
11
|
+
* `kontext/`.
|
|
12
|
+
*
|
|
13
|
+
* π΄ **`kontext/kurzprofil.md` ist seit Ruling K-24 (Task 7) dabei.** Sie wurde
|
|
14
|
+
* beim ERSTEN Ausliefern gefuellt und danach nie wieder aufgefrischt -- waehrend
|
|
15
|
+
* sie selbst dem Kunden zusicherte, sie werde βzentral in der App gepflegt".
|
|
16
|
+
*
|
|
17
|
+
* π΄ **R2 (Spec Β§14): Dieser Befehl fragt NIE `git status`.** Er entscheidet
|
|
18
|
+
* ausschliesslich ueber den Merkzettel (`.kimal3-stand.json`), egal ob der
|
|
19
|
+
* Ordner ein Git-Repo ist oder nicht -- sonst gaebe es zwei Verhalten fuer
|
|
20
|
+
* denselben Befehl, und das schwerer erklaerbare traefe ausgerechnet den, der
|
|
21
|
+
* Git benutzt.
|
|
22
|
+
*
|
|
23
|
+
* β **Unterhalb von `werkbank-<name>/` wird nichts geschrieben** (Spec Β§4).
|
|
24
|
+
* Dieser Befehl kennt genau die Pfade aus `VERWALTETE_DATEIEN`, und in keinem
|
|
25
|
+
* davon steht ein Personenname.
|
|
26
|
+
*
|
|
27
|
+
* π **Ruling T9-2:** Die Gegenrichtung, `kontextHoch`, wohnt seit dem Split
|
|
28
|
+
* von `kontext.ts` in `kontext-hoch.ts`. Beide teilen sich Fundregel,
|
|
29
|
+
* Firmenwahl-Absage und die `--firma`/`--firma-id`-Optionen ueber
|
|
30
|
+
* `kontext-gemeinsam.ts`.
|
|
31
|
+
*/
|
|
32
|
+
const HILFE = `
|
|
33
|
+
kimal3 kontext runter [--firma "Name" | --firma-id "β¦"]
|
|
34
|
+
|
|
35
|
+
Holt den Firmenkontext (CLAUDE.md + die fuenf Dateien in kontext/) und legt
|
|
36
|
+
ihn in deinem KI-Arbeitsplatz ab. Was du selbst geaendert hast, fasst der
|
|
37
|
+
Befehl nicht an -- er sagt dir, wie du es einbringst.
|
|
38
|
+
|
|
39
|
+
--firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst.
|
|
40
|
+
Die Absage nennt dir die Namen als fertige Befehle.
|
|
41
|
+
|
|
42
|
+
--firma-id "β¦" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
|
|
43
|
+
Auch diese Absage nennt dir fertige Befehle.
|
|
44
|
+
`;
|
|
45
|
+
export async function kontextRunter(argv) {
|
|
46
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
47
|
+
console.log(HILFE);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
const zerlegt = zerlege(argv, ERLAUBT);
|
|
51
|
+
if (!zerlegt.ok) {
|
|
52
|
+
console.error(zerlegt.meldung);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
// π Seit Task 7 gemeinsam mit `kontext hoch` und `kontext fragen`
|
|
56
|
+
// (`arbeitsplatzOderAbsage`). Hier standen dieselben vier Zeilen abgetippt;
|
|
57
|
+
// bei drei Aufrufern ist das keine Wiederholung mehr, sondern eine Falle --
|
|
58
|
+
// wer eine Zeile schaerft, schaerft eine von dreien.
|
|
59
|
+
const ordner = arbeitsplatzOderAbsage();
|
|
60
|
+
if (ordner === null)
|
|
61
|
+
return 1;
|
|
62
|
+
const wunsch = zerlegt.werte.optionen.firma;
|
|
63
|
+
const wunschId = zerlegt.werte.optionen["firma-id"];
|
|
64
|
+
const suche = new URLSearchParams();
|
|
65
|
+
if (wunsch !== undefined)
|
|
66
|
+
suche.set("firma", wunsch);
|
|
67
|
+
if (wunschId !== undefined)
|
|
68
|
+
suche.set("firma-id", wunschId);
|
|
69
|
+
const pfad = suche.toString() ? `/api/cli/kontext?${suche.toString()}` : "/api/cli/kontext";
|
|
70
|
+
const antwort = await ruf(pfad, { methode: "GET", mitAusweis: true });
|
|
71
|
+
if (antwort.status !== 200) {
|
|
72
|
+
return absage(antwort, "kontext runter");
|
|
73
|
+
}
|
|
74
|
+
// π΄ Ein `200` mit unlesbarem Rumpf ist KEIN Erfolg -- dasselbe Muster wie
|
|
75
|
+
// in `firma-daten.ts`. `ruf` verschluckt Parse-Fehler zu `null`.
|
|
76
|
+
if (antwort.koerper === null || typeof antwort.koerper !== "object") {
|
|
77
|
+
console.error("Die Antwort war nicht lesbar. Bitte gleich noch einmal.");
|
|
78
|
+
return 3;
|
|
79
|
+
}
|
|
80
|
+
const daten = alsKontextAntwort(antwort.koerper);
|
|
81
|
+
if (daten === null) {
|
|
82
|
+
console.error("Die Antwort war nicht lesbar. Bitte gleich noch einmal.");
|
|
83
|
+
return 3;
|
|
84
|
+
}
|
|
85
|
+
const lokalerMerkzettel = leseMerkzettelDatei(ordner);
|
|
86
|
+
const heute = heuteAus(new Date());
|
|
87
|
+
const geschrieben = [];
|
|
88
|
+
const gesicherte = [];
|
|
89
|
+
const unberuehrt = [];
|
|
90
|
+
for (const verwalteterPfad of VERWALTETE_DATEIEN) {
|
|
91
|
+
const serverDatei = daten.dateien.find((d) => d.pfad === verwalteterPfad);
|
|
92
|
+
if (!serverDatei) {
|
|
93
|
+
console.error(`Die Antwort enthaelt keine Datei β${verwalteterPfad}". Bitte melden.`);
|
|
94
|
+
return 3;
|
|
95
|
+
}
|
|
96
|
+
// Zweites Netz -- s. Kommentar an `schreibeVerwalteteDatei`: `pfad` kommt
|
|
97
|
+
// hier aus der eigenen Iteration, nicht roh vom Server, trotzdem geprueft.
|
|
98
|
+
if (istUnsichererPfad(serverDatei.pfad)) {
|
|
99
|
+
console.error(`Die Antwort enthaelt einen unsicheren Pfad: β${serverDatei.pfad}". Bitte melden.`);
|
|
100
|
+
return 3;
|
|
101
|
+
}
|
|
102
|
+
const lokalGelesen = leseEineDatei(ordner, verwalteterPfad);
|
|
103
|
+
if (!lokalGelesen.ok) {
|
|
104
|
+
console.error(lokalGelesen.meldung);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
const eintrag = lokalerMerkzettel.dateien[verwalteterPfad] ?? null;
|
|
108
|
+
const serverStand = daten.merkzettel.dateien[verwalteterPfad]?.stand ?? LEER;
|
|
109
|
+
const lage = lageFuerDatei({
|
|
110
|
+
lokalerInhalt: lokalGelesen.inhalt,
|
|
111
|
+
serverInhalt: serverDatei.inhalt,
|
|
112
|
+
eintrag,
|
|
113
|
+
serverStand,
|
|
114
|
+
});
|
|
115
|
+
if (lage === "beides") {
|
|
116
|
+
// `lokalGelesen.inhalt` ist hier garantiert eine Zeichenkette:
|
|
117
|
+
// `lageFuerDatei` liefert "beides" nie, wenn `lokalerInhalt === null`.
|
|
118
|
+
const sicherung = sichereDatei(ordner, verwalteterPfad, lokalGelesen.inhalt, heute);
|
|
119
|
+
if (!sicherung.ok) {
|
|
120
|
+
console.error(sicherung.meldung);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
// Ruling T8-2: `sichereDatei` ueberschreibt nie und ueberspringt nie --
|
|
124
|
+
// `nebenVorhandener` sagt nur, ob HEUTE schon eine andere Sicherung
|
|
125
|
+
// dieser Datei existiert. Beide Faelle sind echte Erfolge und werden
|
|
126
|
+
// unten bewusst UNTERSCHIEDLICH gemeldet (Β§8).
|
|
127
|
+
gesicherte.push({
|
|
128
|
+
pfad: verwalteterPfad,
|
|
129
|
+
ziel: sicherung.ziel,
|
|
130
|
+
nebenVorhandener: sicherung.nebenVorhandener,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (lage === "server-neuer" || lage === "beides") {
|
|
134
|
+
const geschriebenErg = schreibeVerwalteteDatei(ordner, verwalteterPfad, serverDatei.inhalt);
|
|
135
|
+
if (!geschriebenErg.ok) {
|
|
136
|
+
console.error(geschriebenErg.meldung);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
geschrieben.push(verwalteterPfad);
|
|
140
|
+
}
|
|
141
|
+
if (lage === "lokal-geaendert") {
|
|
142
|
+
unberuehrt.push(verwalteterPfad);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/*
|
|
146
|
+
π΄ Der Merkzettel des SERVERS wird geschrieben -- unabhaengig davon, ob in
|
|
147
|
+
diesem Lauf ueberhaupt etwas geaendert wurde. Er traegt fuer JEDE der fuenf
|
|
148
|
+
Dateien den Stand, der zum jetzt ausgelieferten Inhalt passt (Β§7b), auch
|
|
149
|
+
fuer die, die "schon aktuell" waren oder lokal geaendert blieben -- ein
|
|
150
|
+
lokal geaenderter Eintrag bleibt so beim naechsten Lauf ERKENNBAR lokal
|
|
151
|
+
geaendert, statt durch einen fehlenden Merkzettel-Eintrag neu "sicher" zu
|
|
152
|
+
werden.
|
|
153
|
+
|
|
154
|
+
π΄ Task 10a: `firma_id` kommt ausdruecklich aus `daten.firma.id` -- der
|
|
155
|
+
Firma, die diese Antwort tatsaechlich beantwortet hat (`waehleFirma` in
|
|
156
|
+
der Route), nicht aus dem Merkzettel-Feld selbst. Wer zu MEHREREN Firmen
|
|
157
|
+
gehoert, soll im Ordner von Firma A niemals lesen "B hat sich geaendert".
|
|
158
|
+
*/
|
|
159
|
+
const merkzettelZumSchreiben = { firma_id: daten.firma.id, dateien: daten.merkzettel.dateien };
|
|
160
|
+
const merkzettelSchreiben = schreibeMerkzettelDatei(ordner, merkzettelZumSchreiben);
|
|
161
|
+
if (!merkzettelSchreiben.ok) {
|
|
162
|
+
console.error(merkzettelSchreiben.meldung);
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
// ---- Ausgabe --------------------------------------------------------
|
|
166
|
+
// π΄ Ruling T8-2 (Fix-Runde 1): ZWEI ehrlich verschiedene Faelle, keine
|
|
167
|
+
// Pauschalmeldung mehr. "Frisch gesichert" und "zusaetzlich neben einer
|
|
168
|
+
// schon vorhandenen Sicherung von heute" sind beide echte Erfolge -- aber
|
|
169
|
+
// verschiedene, und der Mensch muss wissen, WAS mit seinem Text passiert
|
|
170
|
+
// ist. Nie ein Satz, der eine Sicherung behauptet, wo keine (mehr)
|
|
171
|
+
// entstanden ist (der urspruengliche Befund).
|
|
172
|
+
//
|
|
173
|
+
// US-7 bleibt erfuellt: Die Meldung steht VOR der "aktualisiert"-Zeile
|
|
174
|
+
// (vor dem Ergebnis) und nennt den Ordner. Die eigentliche Reihenfolge
|
|
175
|
+
// (erst sichern, dann ueberschreiben) erzwingt schon der Code oben.
|
|
176
|
+
if (gesicherte.length > 0) {
|
|
177
|
+
console.log(`Ich sichere deine lokale Fassung, bevor ich sie ersetze -- Ordner: ${sicherungsOrdner(heute)}`);
|
|
178
|
+
for (const g of gesicherte) {
|
|
179
|
+
if (g.nebenVorhandener) {
|
|
180
|
+
const bisherige = sicherungsZiel(heute, g.pfad).split("/").pop();
|
|
181
|
+
const neue = g.ziel.split("/").pop();
|
|
182
|
+
console.log(` β${g.pfad}": Von heute gibt es schon eine Sicherung (${bisherige}) -- die bleibt ` +
|
|
183
|
+
`unveraendert stehen. Deine neuere Fassung sichere ich zusaetzlich als ${neue}, ` +
|
|
184
|
+
"bevor ich die Datei im Ordner ersetze.");
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.log(` β${g.pfad}" -> ${g.ziel.split("/").pop()}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (geschrieben.length > 0) {
|
|
192
|
+
console.log(`β aktualisiert: ${geschrieben.join(", ")}`);
|
|
193
|
+
}
|
|
194
|
+
// Β§8: Ein Fehlerfall darf nie denselben Namen tragen wie ein Erfolgsfall --
|
|
195
|
+
// "nichts geschrieben" heisst hier DREI verschiedene Dinge, und die Ausgabe
|
|
196
|
+
// muss sie unterscheiden, nicht nur die Rueckgabe (alle drei geben 0).
|
|
197
|
+
if (unberuehrt.length > 0) {
|
|
198
|
+
console.log(`Ich fasse nichts an -- du hast lokal geaendert: ${unberuehrt.join(", ")}`);
|
|
199
|
+
// π΄ Der Rat haengt an der DATEI, nicht nur an der Rolle -- s.
|
|
200
|
+
// `ratNachUnberuehrt` (kontext-stand.ts), Review-Befund F-3.
|
|
201
|
+
for (const zeile of ratNachUnberuehrt(unberuehrt, daten.rolle, daten.adminName)) {
|
|
202
|
+
console.log(zeile);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (geschrieben.length === 0 && unberuehrt.length === 0) {
|
|
206
|
+
const alleLeer = VERWALTETE_DATEIEN.every((p) => (daten.merkzettel.dateien[p]?.stand ?? LEER) === LEER);
|
|
207
|
+
if (alleLeer) {
|
|
208
|
+
console.log("Deine Firma hat noch nichts hinterlegt. Bis dahin bleiben die Vordrucke stehen.");
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log("Dein Firmenkontext ist aktuell.");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
/** Prueft die Antwort-Form, ohne `any` -- eine falsche Form ist unlesbar, keine Ausnahme. */
|
|
217
|
+
function alsKontextAntwort(koerper) {
|
|
218
|
+
if (!koerper || typeof koerper !== "object")
|
|
219
|
+
return null;
|
|
220
|
+
const k = koerper;
|
|
221
|
+
// π΄ Task 10a: `firma.id` ist die Quelle, aus der `kontextRunter` gleich
|
|
222
|
+
// den lokalen Merkzettel stempelt -- s. Kommentar an der Schreibstelle.
|
|
223
|
+
const firmaRoh = k.firma;
|
|
224
|
+
if (!firmaRoh || typeof firmaRoh !== "object")
|
|
225
|
+
return null;
|
|
226
|
+
const firmaId = firmaRoh.id;
|
|
227
|
+
if (typeof firmaId !== "string" || firmaId.trim() === "")
|
|
228
|
+
return null;
|
|
229
|
+
const rolle = k.rolle;
|
|
230
|
+
if (rolle !== "admin" && rolle !== "member")
|
|
231
|
+
return null;
|
|
232
|
+
const adminName = k.admin_name;
|
|
233
|
+
if (adminName !== null && typeof adminName !== "string")
|
|
234
|
+
return null;
|
|
235
|
+
const dateienRoh = k.dateien;
|
|
236
|
+
if (!Array.isArray(dateienRoh))
|
|
237
|
+
return null;
|
|
238
|
+
const dateien = [];
|
|
239
|
+
for (const eintrag of dateienRoh) {
|
|
240
|
+
if (!eintrag || typeof eintrag !== "object")
|
|
241
|
+
return null;
|
|
242
|
+
const p = eintrag.pfad;
|
|
243
|
+
const i = eintrag.inhalt;
|
|
244
|
+
if (typeof p !== "string" || p.trim() === "")
|
|
245
|
+
return null;
|
|
246
|
+
if (typeof i !== "string")
|
|
247
|
+
return null;
|
|
248
|
+
dateien.push({ pfad: p, inhalt: i });
|
|
249
|
+
}
|
|
250
|
+
const merkzettel = alsMerkzettel(k.merkzettel);
|
|
251
|
+
if (merkzettel === null)
|
|
252
|
+
return null;
|
|
253
|
+
return { firma: { id: firmaId }, rolle, adminName, dateien, merkzettel };
|
|
254
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { STEMPEL_MARKE } from "../nachricht.js";
|
|
6
|
+
import { eigeneVersion } from "../version.js";
|
|
5
7
|
/**
|
|
6
8
|
* Namen, die frueher einmal ausgeliefert wurden und heute nicht mehr.
|
|
7
9
|
*
|
|
@@ -25,6 +27,62 @@ const VERALTETE_BEFEHLE = [];
|
|
|
25
27
|
* wird sie NICHT ueberschrieben, sondern uebersprungen und gemeldet.
|
|
26
28
|
*/
|
|
27
29
|
const MARKE = "kimal3:erzeugt";
|
|
30
|
+
/**
|
|
31
|
+
* Wohin das Skill installiert wird -- an EINER Stelle.
|
|
32
|
+
*
|
|
33
|
+
* π WARUM EXPORTIERT (Befund der vierten Review-Runde): `kimal3.ts` baute
|
|
34
|
+
* denselben Pfad ein zweites Mal zusammen, um dort den Stempel zu lesen. Zoege
|
|
35
|
+
* das Ziel je um, faende `existsSync` dort nichts, `skillZustand` antwortete
|
|
36
|
+
* "still" -- und Weg 2 waere lautlos tot. Kein Fehler, keine Meldung, nichts
|
|
37
|
+
* zum Bemerken.
|
|
38
|
+
*
|
|
39
|
+
* Dieselbe Lehre wie bei `version.ts`, nur eine Stunde spaeter: *Zwei
|
|
40
|
+
* Wahrheiten ueber denselben Ort driften, und die zweite verrottet, ohne
|
|
41
|
+
* dass jemand hinsieht.*
|
|
42
|
+
*/
|
|
43
|
+
export function skillOrdner() {
|
|
44
|
+
return join(homedir(), ".claude", "skills", "kimal3");
|
|
45
|
+
}
|
|
46
|
+
export function skillDatei() {
|
|
47
|
+
return join(skillOrdner(), "SKILL.md");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Haengt die eigene Versionsnummer ans ENDE des Skill-Textes.
|
|
51
|
+
*
|
|
52
|
+
* π΄ DER STEMPEL BESCHREIBT DIE SKILL.md -- NICHT DEN GANZEN LAUF.
|
|
53
|
+
* Diese Zuordnung hat drei Review-Runden gekostet und wurde zweimal falsch
|
|
54
|
+
* entschieden; sie steht hier ausfuehrlich, damit es kein viertes Mal gibt:
|
|
55
|
+
*
|
|
56
|
+
* Runde 2 verschob den Stempel ans Ende des Befehls, damit ein halber Lauf
|
|
57
|
+
* nicht "alles aktuell" behauptet. Runde 3 fand eine Luecke darin, Runde 4
|
|
58
|
+
* flickte den Rueckgabewert -- und Runde 5 zeigte, dass die ganze Richtung
|
|
59
|
+
* falsch war: Bei JEDEM stabilen Fehlschlag danach (fremde k3*.md im
|
|
60
|
+
* Ordner, schreibgeschuetzter Befehlsordner) ist die SKILL.md geschrieben,
|
|
61
|
+
* aber ungestempelt. `skillZustand` antwortet dann "veraltet", die CLI
|
|
62
|
+
* meldet TAEGLICH "deine Skilldateien sind aelter, tippe kimal3 skill
|
|
63
|
+
* install" -- und genau dieser Befehl stellt denselben Zustand wieder her.
|
|
64
|
+
*
|
|
65
|
+
* *`skillZustand` liest die SKILL.md. Also muss der Stempel ueber die SKILL.md
|
|
66
|
+
* sprechen und ueber nichts sonst.* Ist sie geschrieben, ist sie aktuell --
|
|
67
|
+
* unabhaengig davon, was mit den Slash-Befehlen passiert ist. Deren Zustand
|
|
68
|
+
* meldet der Befehl mit Text und Rueckgabewert, sofort und einmalig; das ist
|
|
69
|
+
* der richtige Kanal dafuer, nicht ein taeglicher Hinweis, den niemand
|
|
70
|
+
* abstellen kann.
|
|
71
|
+
*
|
|
72
|
+
* π Und er entsteht in DERSELBEN Schreiboperation wie die Datei. Kein zweites
|
|
73
|
+
* Lesen, kein zweites Schreiben, kein Zustand dazwischen, in dem die Datei
|
|
74
|
+
* ohne ihren Stempel dasteht.
|
|
75
|
+
*
|
|
76
|
+
* β οΈ Ohne eigene Nummer GAR KEIN Stempel. Ein erfundener Ersatzwert waere
|
|
77
|
+
* schlimmer als keiner: Die CLI vergliche spaeter gegen ihre eigene Erfindung.
|
|
78
|
+
* Ohne Stempel antwortet `skillZustand` "veraltet" -- die harmlose Seite.
|
|
79
|
+
*/
|
|
80
|
+
function mitStempel(inhalt) {
|
|
81
|
+
const eigene = eigeneVersion();
|
|
82
|
+
if (eigene === null)
|
|
83
|
+
return inhalt;
|
|
84
|
+
return `${inhalt.trimEnd()}\n\n<!-- ${STEMPEL_MARKE}${eigene} -->\n`;
|
|
85
|
+
}
|
|
28
86
|
/**
|
|
29
87
|
* Legt das Skill UND die Slash-Befehle GLOBAL ab, damit Claude dieses Werkzeug
|
|
30
88
|
* ueberall kennt -- und damit der Mensch es auch selbst ausloesen kann.
|
|
@@ -63,14 +121,46 @@ export async function skillInstall() {
|
|
|
63
121
|
// Erfolgsprofil ein" gilt in JEDEM Ordner, nicht in einem. Wer projektlokal
|
|
64
122
|
// installiert, hat das Werkzeug im naechsten Projekt nicht -- und merkt es
|
|
65
123
|
// erst, wenn er dort vergeblich "fertig" sagt.
|
|
66
|
-
const zielOrdner =
|
|
67
|
-
const ziel =
|
|
124
|
+
const zielOrdner = skillOrdner();
|
|
125
|
+
const ziel = skillDatei();
|
|
68
126
|
// Vorher merken: "angelegt" und "aktualisiert" sind zwei verschiedene
|
|
69
127
|
// Wirklichkeiten und bekommen deshalb zwei verschiedene Meldungen.
|
|
70
128
|
const gabEsSchon = existsSync(ziel);
|
|
71
129
|
try {
|
|
72
130
|
mkdirSync(zielOrdner, { recursive: true });
|
|
73
|
-
|
|
131
|
+
/*
|
|
132
|
+
π΄ DER STEMPEL GEHOERT ANS ENDE, NIEMALS AN DEN ANFANG. SKILL.md beginnt
|
|
133
|
+
in Zeile 1 mit YAML-Frontmatter (`---`, dann `name:`). Steht davor
|
|
134
|
+
irgendetwas, ist es kein Frontmatter mehr -- der Skill wird dann gar
|
|
135
|
+
nicht geladen, und zwar OHNE Fehlermeldung. Ein Update-Hinweis, der den
|
|
136
|
+
Skill abschaltet, den er aktuell halten soll, ist der teuerste denkbare
|
|
137
|
+
Fehler in dieser Scheibe.
|
|
138
|
+
|
|
139
|
+
Deshalb auch lesen-anhaengen-schreiben statt copyFileSync: Der Stempel
|
|
140
|
+
entsteht beim KOPIEREN, er steht nicht in der Quelle -- sonst truege das
|
|
141
|
+
Paket seine Versionsnummer an zwei Orten, und die zweite verrottet.
|
|
142
|
+
|
|
143
|
+
π΄ OHNE eigene Versionsnummer wird GAR KEIN Stempel geschrieben, sondern
|
|
144
|
+
unveraendert kopiert. `eigeneVersion()` liefert `null`, wenn
|
|
145
|
+
`cli/package.json` fehlt oder Unsinn enthaelt. Ein Stempel mit einem
|
|
146
|
+
erfundenen Ersatzwert waere hier schlimmer als keiner: Die CLI vergliche
|
|
147
|
+
spaeter gegen ihre eigene Erfindung und meldete entweder ein Update, das
|
|
148
|
+
es nicht gibt, oder schwiege zu einem, das faellig ist. Ohne Stempel
|
|
149
|
+
antwortet `skillZustand` dagegen "veraltet" -- die harmlose Seite, denn
|
|
150
|
+
ein zweites `kimal3 skill install` kostet nichts.
|
|
151
|
+
|
|
152
|
+
β οΈ Die Marke `kimal3:erzeugt` (Konstante MARKE oben) steckt in
|
|
153
|
+
`STEMPEL_MARKE` und bleibt damit erhalten. Der Stempel ist eine
|
|
154
|
+
ERWEITERUNG dieser Marke, kein Ersatz -- `raeumeVeralteteAuf` und die
|
|
155
|
+
Ueberschreib-Pruefung erkennen unsere eigenen Dateien genau daran.
|
|
156
|
+
|
|
157
|
+
π Er entsteht HIER, in derselben Schreiboperation wie die Datei -- und
|
|
158
|
+
zwar wieder, nachdem er drei Runden lang am Ende des Befehls stand.
|
|
159
|
+
Warum die Ruecknahme richtig ist, steht ausfuehrlich bei `mitStempel`:
|
|
160
|
+
Der Stempel spricht ueber die SKILL.md, nicht ueber den ganzen Lauf.
|
|
161
|
+
*/
|
|
162
|
+
const inhalt = readFileSync(quelle, "utf8");
|
|
163
|
+
writeFileSync(ziel, mitStempel(inhalt), "utf8");
|
|
74
164
|
}
|
|
75
165
|
catch (fehler) {
|
|
76
166
|
console.error(`Das Skill liess sich nicht schreiben: ${String(fehler)}`);
|
|
@@ -142,6 +232,22 @@ export async function skillInstall() {
|
|
|
142
232
|
console.error(" Die oben genannten stehen; ein zweiter Lauf holt den Rest.");
|
|
143
233
|
return 3;
|
|
144
234
|
}
|
|
235
|
+
/*
|
|
236
|
+
π΄ Eine nicht lesbare Datei ist ein UNVOLLSTAENDIGER Lauf und bekommt
|
|
237
|
+
deshalb `3`, genau wie `gestoert` (Befund der vierten Review-Runde).
|
|
238
|
+
`3` heisst laut SKILL.md "einmal wiederholen", und das passt zur Lage:
|
|
239
|
+
Eine Datei in Benutzung ist gleich wieder frei.
|
|
240
|
+
|
|
241
|
+
π Der STEMPEL haengt hier nicht mehr dran -- er entsteht beim Schreiben
|
|
242
|
+
der SKILL.md und beschreibt nur sie (ausfuehrliche Begruendung bei
|
|
243
|
+
`mitStempel` oben). Bis zur fuenften Review-Runde stand er an dieser
|
|
244
|
+
Stelle, und jeder stabile Fehlschlag danach erzeugte einen taeglichen
|
|
245
|
+
Hinweis, den kein Befolgen aufloesen konnte.
|
|
246
|
+
*/
|
|
247
|
+
if (unlesbar.length > 0) {
|
|
248
|
+
console.error(" Der Lauf ist damit unvollstaendig -- ein zweiter Versuch holt den Rest.");
|
|
249
|
+
return 3;
|
|
250
|
+
}
|
|
145
251
|
/*
|
|
146
252
|
β οΈ **Nicht auf einen Befehl verweisen, den wir gerade NICHT installiert
|
|
147
253
|
haben** (Review-Runde 3, 2026-08-18, gemessen). Mit einer fremden
|