hmgr 0.0.38 → 0.0.41

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.
@@ -0,0 +1,31 @@
1
+ // Die Änderungsliste als mitgelieferte JSON-Datei dist/aenderungen.json (E108). Der Adapter liegt in
2
+ // dist/adapter/, die Datei eine Ebene höher; im Quellbaum (node src/cli/main.ts) gibt es sie nicht,
3
+ // dann ist die Liste leer. Fehlende oder kaputte Datei ist nie ein Fehler.
4
+ import { readFile } from 'node:fs/promises';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { ohneBom } from "./datei-text.js";
7
+ export class AenderungenJson {
8
+ pfad;
9
+ constructor(pfad = fileURLToPath(new URL('../aenderungen.json', import.meta.url))) {
10
+ this.pfad = pfad;
11
+ }
12
+ async lesen() {
13
+ const roh = await readFile(this.pfad, 'utf8').catch(() => undefined);
14
+ if (roh === undefined)
15
+ return {};
16
+ try {
17
+ const daten = JSON.parse(ohneBom(roh));
18
+ if (typeof daten.versionen !== 'object' || daten.versionen === null || Array.isArray(daten.versionen))
19
+ return {};
20
+ const ergebnis = {};
21
+ for (const [version, commits] of Object.entries(daten.versionen)) {
22
+ if (Array.isArray(commits))
23
+ ergebnis[version] = commits.filter((c) => typeof c === 'string');
24
+ }
25
+ return ergebnis;
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "fassung": 1,
3
+ "versionen": {
4
+ "0.0.41": [
5
+ "Im Verlauf steht unter „hmgr … installiert\" jetzt aufklappbar, was sich in der neuen Version geändert hat."
6
+ ]
7
+ }
8
+ }
package/dist/cli/hilfe.js CHANGED
@@ -564,6 +564,10 @@ Fehler zu einer Quelle oder einem Bundle, dazu hmgr-Ereignisse: ein Versionsspru
564
564
  (GUI oder CLI, erkannt beim Start bzw. bei jedem CLI-Aufruf) oder ein gefundenes Update, das noch
565
565
  nicht installiert ist. Plugins stehen nicht im Verlauf (siehe hmgr help update).
566
566
 
567
+ Unter einem „installiert"-Eintrag von hmgr stehen die Änderungen: je Version seit der zuvor
568
+ vermerkten die Nummer und darunter die Änderungen als kurze Sätze (E108). Einträge ohne bekannte Vorversion
569
+ zeigen nur die eigene Version, ohne mitgelieferte Änderungsliste entfällt der Abschnitt.
570
+
567
571
  Der Verlauf ist je Rechner: mehrere Maschinen führen ihre eigene Datei, es gibt kein Zusammenführen.
568
572
 
569
573
  Schalter: keine.
package/dist/cli/main.js CHANGED
@@ -22,6 +22,7 @@ import { TerminalRueckfrage } from "../adapter/terminal.js";
22
22
  import { ProzessUmgebung } from "../adapter/umgebung.js";
23
23
  import { ZustandJson } from "../adapter/zustand-json.js";
24
24
  import { VerlaufJson } from "../adapter/verlauf-json.js";
25
+ import { AenderungenJson } from "../adapter/aenderungen-json.js";
25
26
  import { HILFE, hilfeText } from "./hilfe.js";
26
27
  export function umgebungLesen(env = process.env) {
27
28
  const home = env.HOME ?? homedir();
@@ -104,6 +105,7 @@ export function bauen(umgebung, optionen = {}) {
104
105
  variablen: async (distro) => await new WslBruecke(distro, wslUmgebungAusfuehren).variablen(),
105
106
  },
106
107
  verlauf,
108
+ aenderungen: optionen.aenderungen ?? new AenderungenJson(),
107
109
  };
108
110
  return { anwendungsfaelle: new Anwendungsfaelle(ports), ports };
109
111
  }
@@ -858,8 +860,15 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
858
860
  console.log('Noch keine Ereignisse.');
859
861
  for (const eintrag of eintraege) {
860
862
  console.log(verlaufZeitpunkt(eintrag.zeit));
861
- for (const ereignis of eintrag.ereignisse)
863
+ for (const ereignis of eintrag.ereignisse) {
862
864
  console.log(` ${verlaufEreignisZeile(ereignis)}`);
865
+ // Unter einem Versionssprung die Commits der neuen Version(en), eingerückt (E108).
866
+ if (ereignis.art === 'hmgr-installiert') {
867
+ const aenderungen = await anwendungsfaelle.hmgrAenderungen(ereignis.vorher, ereignis.version);
868
+ for (const zeile of aenderungsZeilen(aenderungen))
869
+ console.log(zeile);
870
+ }
871
+ }
863
872
  }
864
873
  return 0;
865
874
  }
@@ -1091,6 +1100,10 @@ function verlaufZeitpunkt(iso) {
1091
1100
  return (`${zwei(datum.getDate())}.${zwei(datum.getMonth() + 1)}.${datum.getFullYear()} ` +
1092
1101
  `${zwei(datum.getHours())}:${zwei(datum.getMinutes())}`);
1093
1102
  }
1103
+ /** Je Version die Nummer und darunter ihre Commit-Betreffs, tiefer eingerückt als die Ereigniszeile (E108). */
1104
+ function aenderungsZeilen(aenderungen) {
1105
+ return aenderungen.flatMap((a) => [` ${a.version}`, ...a.commits.map((c) => ` ${c}`)]);
1106
+ }
1094
1107
  /** Eine Ereigniszeile im Verlauf (E80), eingerückt unter der Kopfzeile ihres Laufs. */
1095
1108
  function verlaufEreignisZeile(ereignis) {
1096
1109
  if (ereignis.art === 'checkout') {
package/dist/gui/main.js CHANGED
@@ -11,6 +11,7 @@ import { randomUUID } from 'node:crypto';
11
11
  import { stat } from 'node:fs/promises';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import { bauen, umgebungLesen } from "../cli/main.js";
14
+ import { AenderungenJson } from "../adapter/aenderungen-json.js";
14
15
  import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
15
16
  import { paketVersion } from "../adapter/paket-version.js";
16
17
  import { infoPlistPfad, MeldungsPuffer, neuereVersionAufPlatte } from "./selbst-update.js";
@@ -129,6 +130,11 @@ const rueckfrage = new GuiRueckfrage();
129
130
  const { anwendungsfaelle, ports } = bauen(umgebung, {
130
131
  rueckfrage,
131
132
  ...(smoketest ? { claudeAusfuehrer: smoketestClaudeAusfuehrer } : {}),
133
+ // Nur der Smoke-Test biegt die Änderungsliste auf eine Fixture-Datei um (E108); sonst die
134
+ // mitgelieferte dist/aenderungen.json.
135
+ ...(smoketest && process.env.HMGR_GUI_SMOKE_AENDERUNGEN !== undefined
136
+ ? { aenderungen: new AenderungenJson(process.env.HMGR_GUI_SMOKE_AENDERUNGEN) }
137
+ : {}),
132
138
  });
133
139
  /**
134
140
  * Wer den laufenden Aktualisieren-Lauf ausgelöst hat (E80): der Renderer reicht es bei jedem Aufruf
@@ -329,6 +335,8 @@ function registriereIpc() {
329
335
  }));
330
336
  // Changelog der letzten Läufe (E80), neueste zuerst.
331
337
  ipcMain.handle('hmgr:verlauf', async () => auffangen(async () => anwendungsfaelle.verlauf()));
338
+ // Änderungen (Commit-Betreffs) der Versionen eines hmgr-Versionssprungs (E108).
339
+ ipcMain.handle('hmgr:hmgrAenderungen', async (_event, vorher, version) => auffangen(async () => anwendungsfaelle.hmgrAenderungen(vorher, version)));
332
340
  // E77: der Prüfen-Knopf stößt zusätzlich zum Quellen-Lauf die Prüfung auf hmgr selbst an. Kein
333
341
  // `handle`, sondern `send`/`on`: der Renderer wartet auf nichts, das Ergebnis kommt später von
334
342
  // sich aus über 'hmgr:selbstUpdate'.
@@ -416,8 +424,11 @@ function registriereIpc() {
416
424
  }));
417
425
  // MCP-Ansicht (E61-Folgerunde): Whitelisting und der F39-Vermerk je Karte stehen in mcp-ansicht.ts
418
426
  // (rein, ohne Electron, wie oeffnen.ts und icons.ts), damit sie testbar sind. Der Main-Prozess
419
- // ruft nur noch auf und liefert die eigene Prozess-Umgebung für die F39-Prüfung.
420
- ipcMain.handle('hmgr:mcpUebersicht', async () => auffangen(async () => mcpUebersichtSerialisieren(await anwendungsfaelle.mcpUebersicht(), process.env)));
427
+ // ruft nur noch auf; die F39-Lage je Welt (lokal, WSL-Distro) kommt aus dem Kern (E107).
428
+ ipcMain.handle('hmgr:mcpUebersicht', async () => auffangen(async () => {
429
+ const uebersicht = await anwendungsfaelle.mcpUebersicht();
430
+ return mcpUebersichtSerialisieren(uebersicht, await anwendungsfaelle.mcpVariablenLage(uebersicht));
431
+ }));
421
432
  // Plätze für den Einhängen-Dialog (F42): die Fähigkeit hängt am Adapter, nicht am Namen (E16),
422
433
  // deshalb kommt die Liste unverändert aus dem Kern statt aus einer Harness-Fallunterscheidung hier.
423
434
  ipcMain.handle('hmgr:mcpPlaetze', async () => auffangen(async () => anwendungsfaelle.mcpPlaetze()));
@@ -709,6 +720,23 @@ function fensterOeffnen() {
709
720
  verlaufBloecke = document.querySelectorAll('#verlauf .block').length;
710
721
  if (verlaufBloecke > 0) break;
711
722
  }
723
+ // E108: die Änderungslisten kommen nach dem Verlauf per IPC; warten, bis jeder Platz
724
+ // entweder sein details hat oder (ohne Daten) entfernt ist.
725
+ for (var wartezeit2 = 0; wartezeit2 < 2000; wartezeit2 += 100) {
726
+ if (document.querySelector('#verlauf .aenderungen-platz:empty') === null) break;
727
+ await new Promise((r) => setTimeout(r, 100));
728
+ }
729
+ var aenderungenDetails = document.querySelectorAll('#verlauf details.aenderungen').length;
730
+ var aenderungenElement = document.querySelector('#verlauf details.aenderungen');
731
+ var aenderungenSummary = aenderungenElement?.querySelector('summary')?.textContent?.trim() ?? null;
732
+ var aenderungenOffenVorher = aenderungenElement?.open ?? null;
733
+ aenderungenElement?.querySelector('summary')?.click();
734
+ var aenderungenOffenNachher = aenderungenElement?.open ?? null;
735
+ var aenderungenGruppen = aenderungenElement?.querySelectorAll('.aenderungen-version').length ?? 0;
736
+ var aenderungenText = Array.prototype.map.call(
737
+ aenderungenElement?.querySelectorAll('.aenderungen-version') ?? [],
738
+ (g) => g.textContent.replace(/\\s+/g, ' ').trim(),
739
+ ).join(' ');
712
740
  var verlaufText = document.getElementById('verlauf')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
713
741
  // Nachziehen je Quelle (E89): das Fixture hat im skills-repo einen neuen Upstream-Commit
714
742
  // (fixtureAufbauen), den erst ein Lauf für diese Quelle holt. Erst der Knopf der
@@ -1292,6 +1320,12 @@ function fensterOeffnen() {
1292
1320
  verlaufVorhanden,
1293
1321
  verlaufText,
1294
1322
  verlaufBloecke,
1323
+ aenderungenDetails,
1324
+ aenderungenSummary,
1325
+ aenderungenOffenVorher,
1326
+ aenderungenOffenNachher,
1327
+ aenderungenGruppen,
1328
+ aenderungenText,
1295
1329
  nachziehenFehler,
1296
1330
  nachziehenKnopfZahl,
1297
1331
  nachziehenBloeckeStart,
@@ -1,7 +1,7 @@
1
1
  // MCP-Ansicht der GUI (E61-Folgerunde): reine Umformung von Kern-Daten in IPC-taugliche, gewhiteliste
2
2
  // Objekte, ohne Electron — testbar wie oeffnen.ts und icons.ts. Der eigentliche IPC-Handler in
3
3
  // main.ts ruft diese Funktionen nur noch auf.
4
- import { variablenVerweise, } from "../kern/anwendungsfaelle.js";
4
+ import {} from "../kern/anwendungsfaelle.js";
5
5
  /** Katalog-Eintrag über IPC: die bekannten Felder, nichts ungefiltert durchgereicht (E40 R3). */
6
6
  export function mcpEintragSerialisieren(eintrag) {
7
7
  return {
@@ -12,23 +12,14 @@ export function mcpEintragSerialisieren(eintrag) {
12
12
  konfig: eintrag.konfig,
13
13
  };
14
14
  }
15
- /**
16
- * F39 an der Katalog-Karte: dieselbe Prüf-Funktion wie beim tatsächlichen Einhängen
17
- * (`variablenVerweise`), hier aber nur gegen die übergebene Umgebung geprüft — ohne den
18
- * ortsbezogenen `.env`-Abgleich, der ist ortsspezifisch und bleibt dem eigentlichen Einhängen
19
- * vorbehalten (kommt von dort als `warnung` zurück).
20
- */
21
- export function mcpFehlendeVariablen(eintrag, umgebung) {
22
- return variablenVerweise(eintrag.konfig).filter((name) => !(name in umgebung) || umgebung[name] === undefined);
23
- }
24
- /** Übersicht über IPC (E61-Folgerunde): Katalog-Einträge whitelisted, dazu der F39-Vermerk je Karte. */
25
- export function mcpUebersichtSerialisieren(uebersicht, umgebung) {
15
+ /** Übersicht über IPC (E61-Folgerunde): Katalog-Einträge whitelisted, dazu je Karte die F39-Befunde je Welt (E107, `variablenWelten`). */
16
+ export function mcpUebersichtSerialisieren(uebersicht, lage) {
26
17
  return {
27
18
  katalog: uebersicht.katalog.map((k) => ({
28
19
  eintrag: mcpEintragSerialisieren(k.eintrag),
29
20
  eingehaengt: k.eingehaengt,
30
21
  geteilteFundorte: k.geteilteFundorte,
31
- fehlendeVariablen: mcpFehlendeVariablen(k.eintrag, umgebung),
22
+ variablenWelten: lage.get(k.eintrag.name) ?? [],
32
23
  })),
33
24
  unbekannt: uebersicht.unbekannt,
34
25
  unbekannteGeteilteFundorte: uebersicht.unbekannteGeteilteFundorte,
@@ -35,6 +35,8 @@ contextBridge.exposeInMainWorld('hmgr', {
35
35
  aktualisieren: (args) => ipcRenderer.invoke('hmgr:aktualisieren', args),
36
36
  /** Changelog der letzten Läufe (E80), neueste zuerst. */
37
37
  verlauf: () => ipcRenderer.invoke('hmgr:verlauf'),
38
+ /** Commit-Betreffs der Versionen zwischen `vorher` (exklusiv) und `version` (E108), neueste zuerst. */
39
+ hmgrAenderungen: (vorher, version) => ipcRenderer.invoke('hmgr:hmgrAenderungen', vorher, version),
38
40
  /** Installiert das geladene GUI-Update und startet neu (E80), Gegenstück zum Neustart-Knopf. */
39
41
  neuStarten: () => {
40
42
  ipcRenderer.send('hmgr:neuStarten');
@@ -323,7 +323,7 @@
323
323
  // Homedirs je Harness (E78): je Eintrag { harness, heime: [{ adresse?, pfad, eingestellt }],
324
324
  // kannZusaetzliche }. Grundlage der Homedir-Zeilen in den Einstellungen.
325
325
  var HARNESS_HEIME = [];
326
- // MCP-Katalog (E61-Folgerunde): je Eintrag { eintrag, eingehaengt, fehlendeVariablen }, dazu
326
+ // MCP-Katalog (E61-Folgerunde): je Eintrag { eintrag, eingehaengt, variablenWelten }, dazu
327
327
  // Einträge in Harness-Konfigurationen, die nicht im Katalog stehen (nur genannt). Seit F46 nur
328
328
  // noch Grundlage für den MCP-Abschnitt der Ziel-Karten (mcpZeilenFuerZiel), die MCP-Ansicht selbst
329
329
  // zeigt keine Fundorte mehr.
@@ -2927,6 +2927,12 @@
2927
2927
  } else if (ereignis.art === 'hmgr-installiert') {
2928
2928
  li.appendChild(e('span', 'marke-ok', 'installiert'));
2929
2929
  li.appendChild(e('span', 'pfad', (ereignis.paket === 'gui' ? 'hmgr-GUI ' : 'hmgr ') + ereignis.version));
2930
+ // E108: die Commits der Versionen seit der zuvor installierten, zum Ausklappen unter der Zeile.
2931
+ // Der Platzhalter hält die Stelle, das details kommt nach dem Aufbau des Verlaufs hinein.
2932
+ var aenderungenPlatz = e('div', 'aenderungen-platz');
2933
+ aenderungenPlatz.dataset.vorher = ereignis.vorher || '';
2934
+ aenderungenPlatz.dataset.version = ereignis.version;
2935
+ li.appendChild(aenderungenPlatz);
2930
2936
  } else if (ereignis.art === 'gui-update-geladen') {
2931
2937
  li.appendChild(e('span', 'marke-ok', 'geladen'));
2932
2938
  li.appendChild(e('span', 'pfad', 'GUI-Update ' + ereignis.version));
@@ -2965,6 +2971,29 @@
2965
2971
  block.appendChild(ul);
2966
2972
  box.appendChild(block);
2967
2973
  });
2974
+ // E108: die Änderungslisten nachladen, nachdem der Verlauf steht. Ein Fehler lässt nur den
2975
+ // jeweiligen Abschnitt weg, nie den Verlauf.
2976
+ var plaetze = Array.prototype.slice.call(box.querySelectorAll('.aenderungen-platz'));
2977
+ await Promise.all(plaetze.map(async function (platz) {
2978
+ var versionen = await window.hmgr.hmgrAenderungen(platz.dataset.vorher || undefined, platz.dataset.version)
2979
+ .then(function (r) { return r && r.ok ? r.daten : []; }, function () { return []; });
2980
+ if (versionen.length === 0) { platz.remove(); return; }
2981
+ var zahl = versionen.reduce(function (summe, v) { return summe + v.commits.length; }, 0);
2982
+ var d = e('details', 'notizen aenderungen');
2983
+ var s = e('summary');
2984
+ s.appendChild(keilIcon());
2985
+ s.appendChild(document.createTextNode('Änderungen (' + zahl + ')'));
2986
+ d.appendChild(s);
2987
+ versionen.forEach(function (v) {
2988
+ var gruppe = e('div', 'aenderungen-version');
2989
+ gruppe.appendChild(e('b', null, v.version));
2990
+ var liste = e('ul');
2991
+ v.commits.forEach(function (commit) { liste.appendChild(e('li', null, commit)); });
2992
+ gruppe.appendChild(liste);
2993
+ d.appendChild(gruppe);
2994
+ });
2995
+ platz.appendChild(d);
2996
+ }));
2968
2997
  }
2969
2998
 
2970
2999
  /* ================= PLUGINS ================= */
@@ -3071,13 +3100,11 @@
3071
3100
  return a;
3072
3101
  }
3073
3102
 
3074
- /** F39: die Prüf-Funktion ist dieselbe wie beim tatsächlichen Einhängen, hier aber nur gegen die
3075
- Prozess-Umgebung von hmgr (der Main-Prozess liefert `fehlendeVariablen` schon fertig mit der
3076
- Übersicht, ohne den ortsbezogenen .env-Abgleich, der bleibt dem Einhängen selbst vorbehalten
3077
- und kommt dann als `warnung` zurück). Wiederverwendet die bestehende .ruhezeile-Optik. */
3078
- function mcpWarnZeile(fehlend) {
3079
- var mehrzahl = fehlend.length > 1;
3080
- var namen = fehlend.map(function (v) { return '${' + v + '}'; }).join(', ');
3103
+ /** F39 (E107): der Main-Prozess liefert je Karte `variablenWelten`, die Befunde je Welt (lokal oder
3104
+ WSL-Distro), geprüft gegen dieselbe Umgebungsquelle wie das Einhängen — ohne den ortsbezogenen
3105
+ .env-Abgleich, der bleibt dem Einhängen selbst vorbehalten und kommt dann als `warnung` zurück.
3106
+ Je Welt mit Befund eine Zeile, Welten ohne Befund keine. Wiederverwendet die .ruhezeile-Optik. */
3107
+ function mcpWarnZeile(text) {
3081
3108
  var rz = e('div', 'ruhezeile');
3082
3109
  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3083
3110
  svg.setAttribute('viewBox', '0 0 16 16');
@@ -3085,19 +3112,37 @@
3085
3112
  svg.setAttribute('aria-hidden', 'true');
3086
3113
  svg.innerHTML = '<circle cx="8" cy="8" r="6.5"/><path d="M8 4.5v4M8 10.5v.5"/>';
3087
3114
  rz.appendChild(svg);
3088
- rz.appendChild(e('p', null, namen + (mehrzahl ? ' sind' : ' ist') + ' in der Umgebung von hmgr nicht gesetzt. Ob '
3089
- + (mehrzahl ? 'die Variablen' : 'die Variable') + ' beim Start des Harness gesetzt ' + (mehrzahl ? 'sind' : 'ist')
3090
- + ', kann hmgr nicht wissen.'));
3115
+ rz.appendChild(e('p', null, text));
3091
3116
  return rz;
3092
3117
  }
3093
3118
 
3094
- /** Notizen sind das Langsame am Eintrag (entwurf/gui.html): zugeklappt, öffnen sich nur, wenn
3095
- jemand sie sucht. Der Wortlaut bleibt, wie er in der mcp.yaml steht, Zeilenumbrüche
3096
- eingeschlossen (weiss-raum: pre-line in styles.css). */
3097
- function mcpNotizenBlock(text) {
3098
- var d = document.createElement('details');
3099
- d.className = 'notizen';
3100
- var s = document.createElement('summary');
3119
+ function mcpWarnZeilen(welten) {
3120
+ var mehrere = welten.some(function (w) { return w.distro !== undefined; });
3121
+ var zeilen = [];
3122
+ welten.forEach(function (w) {
3123
+ if (w.ungeprueft) {
3124
+ zeilen.push(mcpWarnZeile('hmgr konnte die Umgebung von WSL ' + w.distro + ' nicht lesen, die Verweise sind ungeprüft.'));
3125
+ return;
3126
+ }
3127
+ if (w.fehlend.length === 0) return;
3128
+ var mz = w.fehlend.length > 1;
3129
+ var namen = w.fehlend.map(function (v) { return '${' + v + '}'; }).join(', ');
3130
+ var verb = mz ? ' sind' : ' ist';
3131
+ if (w.distro !== undefined) {
3132
+ zeilen.push(mcpWarnZeile(namen + verb + ' in WSL ' + w.distro + ' nicht gesetzt (Login-Shell, interaktiv).'));
3133
+ } else if (mehrere) {
3134
+ zeilen.push(mcpWarnZeile(namen + verb + ' in Windows nicht gesetzt.'));
3135
+ } else {
3136
+ zeilen.push(mcpWarnZeile(namen + verb + ' in der Umgebung von hmgr nicht gesetzt. Ob '
3137
+ + (mz ? 'die Variablen' : 'die Variable') + ' beim Start des Harness gesetzt ' + (mz ? 'sind' : 'ist')
3138
+ + ', kann hmgr nicht wissen.'));
3139
+ }
3140
+ });
3141
+ return zeilen;
3142
+ }
3143
+
3144
+ /** Der Aufklapp-Keil in einer summary (dreht sich per CSS beim Öffnen). */
3145
+ function keilIcon() {
3101
3146
  var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3102
3147
  pfeil.setAttribute('viewBox', '0 0 16 16');
3103
3148
  pfeil.setAttribute('class', 'keil');
@@ -3107,7 +3152,17 @@
3107
3152
  pfeil.setAttribute('stroke-linejoin', 'round');
3108
3153
  pfeil.setAttribute('aria-hidden', 'true');
3109
3154
  pfeil.innerHTML = '<path d="M4.3 2.5 7.8 6 4.3 9.5"/>';
3110
- s.appendChild(pfeil);
3155
+ return pfeil;
3156
+ }
3157
+
3158
+ /** Notizen sind das Langsame am Eintrag (entwurf/gui.html): zugeklappt, öffnen sich nur, wenn
3159
+ jemand sie sucht. Der Wortlaut bleibt, wie er in der mcp.yaml steht, Zeilenumbrüche
3160
+ eingeschlossen (weiss-raum: pre-line in styles.css). */
3161
+ function mcpNotizenBlock(text) {
3162
+ var d = document.createElement('details');
3163
+ d.className = 'notizen';
3164
+ var s = document.createElement('summary');
3165
+ s.appendChild(keilIcon());
3111
3166
  s.appendChild(document.createTextNode('Notizen'));
3112
3167
  d.appendChild(s);
3113
3168
  d.appendChild(e('p', null, text));
@@ -3135,7 +3190,7 @@
3135
3190
  konf.appendChild(e('span', 'mono', mcpKonfigWortlaut(eintrag.konfig)));
3136
3191
  kEl.appendChild(konf);
3137
3192
 
3138
- if (k.fehlendeVariablen.length > 0) kEl.appendChild(mcpWarnZeile(k.fehlendeVariablen));
3193
+ mcpWarnZeilen(k.variablenWelten).forEach(function (z) { kEl.appendChild(z); });
3139
3194
  if (eintrag.notizen) kEl.appendChild(mcpNotizenBlock(eintrag.notizen));
3140
3195
 
3141
3196
  return kEl;
@@ -490,6 +490,13 @@ img.hsymbol{border-radius:3px}
490
490
  .block .marke-ok{color:var(--emaille-hoch);font-weight:700}
491
491
  .block .marke-aus{color:var(--messing-tief);font-weight:700}
492
492
  .block .pfad{color:var(--tinte-weich)}
493
+ /* E108: Änderungen unter „hmgr X installiert", aufklappbar wie die Notizen der MCP-Karte. */
494
+ .block .aenderungen-platz{grid-column:1 / -1}
495
+ .block .aenderungen-platz:empty{display:none}
496
+ .aenderungen{margin-top:0}
497
+ .aenderungen .aenderungen-version{margin-top:9px;padding-left:13px;border-left:2px solid var(--strich);font-size:16px;color:var(--tinte-weich)}
498
+ .block .aenderungen ul{margin-top:4px;gap:4px;font-size:16px}
499
+ .block .aenderungen li{display:block}
493
500
 
494
501
  .markt{margin-bottom:26px}
495
502
  .marktkopf{display:flex;flex-wrap:wrap;gap:8px 16px;align-items:baseline;margin-bottom:12px;padding-bottom:10px;border-bottom:2px solid var(--tinte)}
@@ -6,6 +6,17 @@ import { passenderRef } from "./version.js";
6
6
  import { kannIstRegistriert, kannMcp, kannPlugins, kannProfile, kannSkillOrdner, kannZurueckbauen, kannZusaetzlicheHeime, } from "../ports/harness.js";
7
7
  /** So viele `plugin list`-Prozesse laufen im Ist-Zustand höchstens gleichzeitig (E93). */
8
8
  const PLUGIN_ABFRAGEN_GLEICHZEITIG = 6;
9
+ /** „0.0.39" → [0, 0, 39]; alles andere (Vorabversion, Text) → undefined. */
10
+ function versionZerlegen(version) {
11
+ const t = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
12
+ return t === null ? undefined : [Number(t[1]), Number(t[2]), Number(t[3])];
13
+ }
14
+ function versionVergleichen(a, b) {
15
+ for (let i = 0; i < 3; i++)
16
+ if (a[i] !== b[i])
17
+ return a[i] - b[i];
18
+ return 0;
19
+ }
9
20
  /**
10
21
  * Die Welt eines Orts als Text für Bericht und Verlauf (E101), in der Schreibweise von `--ort`:
11
22
  * „home" für die Installation dieses Rechners, „wsl:<distro>" für die einer Distro.
@@ -394,6 +405,67 @@ export class Anwendungsfaelle {
394
405
  throw new McpUnbekannt(mcpName);
395
406
  return eintrag;
396
407
  }
408
+ /**
409
+ * Die Umgebung einer Welt (E107): ohne `distro` die Prozess-Umgebung, sonst die einer im Terminal
410
+ * der Distro gestarteten claude (E104). `undefined`, wenn die Distro-Umgebung nicht lesbar ist.
411
+ * Einzige Stelle dafür, geteilt von Einhängen-Warnung und Kartenhinweis.
412
+ */
413
+ async mcpUmgebungDerWelt(distro) {
414
+ if (distro === undefined)
415
+ return this.p.umgebung.variablen();
416
+ return await this.p.wsl.variablen(distro).catch(() => undefined);
417
+ }
418
+ /**
419
+ * F39 an der Katalog-Karte (E107): je Eintrag mit `${VAR}`-Verweisen die Welten, gegen die zu
420
+ * prüfen ist, jeweils mit Befund. Welten: die der Ziele, an denen der Eintrag eingehängt ist; sonst
421
+ * die der Ziele, an denen er einhängbar wäre (`ortBedienbar`, ohne Git-Wurzel-Ermittlung). Je Distro
422
+ * höchstens ein Aufruf je Lauf; ohne Verweise im Katalog wird nichts befragt. Die `.env`-Zusatzdatei
423
+ * (Hermes) bleibt dem Einhängen vorbehalten. Einträge ohne Verweise fehlen in der Map.
424
+ */
425
+ async mcpVariablenLage(uebersicht) {
426
+ const ergebnis = new Map();
427
+ const ue = uebersicht ?? (await this.mcpUebersicht());
428
+ const mitVerweisen = ue.katalog.filter((k) => variablenVerweise(k.eintrag.konfig).length > 0);
429
+ if (mitVerweisen.length === 0)
430
+ return ergebnis;
431
+ let moegliche;
432
+ const umgebungen = new Map();
433
+ for (const k of mitVerweisen) {
434
+ let welten = k.eingehaengt.map((z) => wslDistroVonOrt(z.ort));
435
+ if (welten.length === 0) {
436
+ if (moegliche === undefined) {
437
+ moegliche = [];
438
+ for (const ziel of await this.mcpZiele()) {
439
+ const mcp = this.harnessMitMcp(ziel.harness).mcp;
440
+ if (mcp.ortBedienbar !== undefined && !(await mcp.ortBedienbar(ziel.ort)))
441
+ continue;
442
+ moegliche.push(wslDistroVonOrt(ziel.ort));
443
+ }
444
+ }
445
+ welten = moegliche;
446
+ }
447
+ const verweise = variablenVerweise(k.eintrag.konfig);
448
+ const befunde = [];
449
+ // Lokale Welt zuerst, dann die Distros in der Reihenfolge ihres Auftretens.
450
+ const eindeutig = [...new Set(welten)].sort((a, b) => (a === undefined ? -1 : b === undefined ? 1 : 0));
451
+ for (const distro of eindeutig) {
452
+ let env = umgebungen.get(distro);
453
+ if (env === undefined) {
454
+ env = this.mcpUmgebungDerWelt(distro);
455
+ umgebungen.set(distro, env);
456
+ }
457
+ const umgebung = await env;
458
+ if (umgebung === undefined)
459
+ befunde.push({ distro: distro, ungeprueft: true });
460
+ else {
461
+ const fehlend = verweise.filter((v) => umgebung[v] === undefined);
462
+ befunde.push(distro === undefined ? { fehlend } : { distro, fehlend });
463
+ }
464
+ }
465
+ ergebnis.set(k.eintrag.name, befunde);
466
+ }
467
+ return ergebnis;
468
+ }
397
469
  /**
398
470
  * F39: fehlende `${VAR}`-Verweise als Warnung, nie als Abbruch. Geprüft wird gegen die eigene
399
471
  * Prozess-Umgebung und, wenn die Fähigkeit eine `.env`-Datei nennt (Hermes), zusätzlich gegen sie.
@@ -404,17 +476,10 @@ export class Anwendungsfaelle {
404
476
  return undefined;
405
477
  // Ein WSL-Ziel startet claude in der Distro mit deren Umgebung, nicht mit der von Windows (E104).
406
478
  const distro = wslDistroVonOrt(ort);
407
- let prozessEnv;
408
- if (distro === undefined) {
409
- prozessEnv = this.p.umgebung.variablen();
410
- }
411
- else {
412
- const distroEnv = await this.p.wsl.variablen(distro).catch(() => undefined);
413
- if (distroEnv === undefined) {
414
- return (`hmgr konnte die Umgebung der WSL-Distro ${distro} nicht lesen; die Verweise ` +
415
- `${verweise.map((v) => '${' + v + '}').join(', ')} sind deshalb ungeprüft.`);
416
- }
417
- prozessEnv = distroEnv;
479
+ const prozessEnv = await this.mcpUmgebungDerWelt(distro);
480
+ if (prozessEnv === undefined) {
481
+ return (`hmgr konnte die Umgebung der WSL-Distro ${distro} nicht lesen; die Verweise ` +
482
+ `${verweise.map((v) => '${' + v + '}').join(', ')} sind deshalb ungeprüft.`);
418
483
  }
419
484
  const envPfad = faehig.mcp.envDatei?.(ort);
420
485
  const zusatzEnv = envPfad === undefined ? {} : await this.p.umgebung.envDatei(envPfad);
@@ -1988,6 +2053,31 @@ export class Anwendungsfaelle {
1988
2053
  async verlauf() {
1989
2054
  return (await this.p.verlauf.lesen()).eintraege;
1990
2055
  }
2056
+ /**
2057
+ * Die Änderungen (Commit-Betreffs) der Versionen, die ein Sprung von `vorher` auf `version`
2058
+ * mitbrachte (E108): alle Versionen V mit vorher < V ≤ version, neueste zuerst. Ohne `vorher`
2059
+ * nur `version` selbst. Versionen ohne Eintrag in der Liste (oder ohne Commits) fehlen im
2060
+ * Ergebnis; eine nicht als x.y.z lesbare Version ergibt eine leere Liste.
2061
+ */
2062
+ async hmgrAenderungen(vorher, version) {
2063
+ const bis = versionZerlegen(version);
2064
+ if (bis === undefined)
2065
+ return [];
2066
+ const ab = vorher === undefined ? undefined : versionZerlegen(vorher);
2067
+ const liste = await this.p.aenderungen.lesen();
2068
+ const treffer = [];
2069
+ for (const [v, commits] of Object.entries(liste)) {
2070
+ const zahlen = versionZerlegen(v);
2071
+ if (zahlen === undefined || commits.length === 0)
2072
+ continue;
2073
+ const imBereich = vorher === undefined
2074
+ ? versionVergleichen(zahlen, bis) === 0
2075
+ : ab !== undefined && versionVergleichen(zahlen, ab) > 0 && versionVergleichen(zahlen, bis) <= 0;
2076
+ if (imBereich)
2077
+ treffer.push({ zahlen, eintrag: { version: v, commits } });
2078
+ }
2079
+ return treffer.sort((a, b) => versionVergleichen(b.zahlen, a.zahlen)).map((t) => t.eintrag);
2080
+ }
1991
2081
  /**
1992
2082
  * Merkt die installierte Version eines hmgr-Pakets (GUI oder CLI) und schreibt bei einem
1993
2083
  * Versionssprung einen Verlaufseintrag (E80). Der erste Aufruf ohne gemerkte Version merkt nur,
@@ -2006,7 +2096,7 @@ export class Anwendungsfaelle {
2006
2096
  const eintrag = {
2007
2097
  zeit: new Date().toISOString(),
2008
2098
  ausloeser,
2009
- ereignisse: [{ art: 'hmgr-installiert', paket, version }],
2099
+ ereignisse: [{ art: 'hmgr-installiert', paket, version, vorher: bisher }],
2010
2100
  };
2011
2101
  await this.p.verlauf.schreiben({ gemerkt, eintraege: [eintrag, ...daten.eintraege] });
2012
2102
  }
@@ -0,0 +1,3 @@
1
+ // Änderungen: die gepflegten Sätze je hmgr-Version (E108) aus AENDERUNGEN.md, beim Bauen nach
2
+ // dist/aenderungen.json übertragen (scripts/aenderungen-erzeugen.mjs). Der Verlauf zeigt sie unter „hmgr X installiert".
3
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.38",
3
+ "version": "0.0.41",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "scripts": {
21
21
  "dev": "node src/cli/main.ts",
22
- "build": "tsc -p tsconfig.build.json && node scripts/copy-gui-assets.mjs",
22
+ "build": "tsc -p tsconfig.build.json && node scripts/copy-gui-assets.mjs && node scripts/aenderungen-erzeugen.mjs",
23
23
  "gui": "npm run build && electron dist/gui/main.js",
24
24
  "gui:smoke": "npm run build && node scripts/gui-smoke-test.ts",
25
25
  "gui:app": "npm run build && electron-builder --mac --arm64",