hmgr 0.0.18 → 0.0.20

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.
@@ -92,4 +92,8 @@ export class ComposerZeiger {
92
92
  daten.scripts = scripts;
93
93
  await writeFile(pfad, `${JSON.stringify(daten, null, 4)}\n`);
94
94
  }
95
+ async verzeichnisExistiert(pfad) {
96
+ const info = await stat(pfad).catch(() => undefined);
97
+ return info !== undefined && info.isDirectory();
98
+ }
95
99
  }
package/dist/cli/hilfe.js CHANGED
@@ -31,9 +31,10 @@ hmgr <kommando> [schalter]
31
31
  mcp migrate <name> MCP-Server aus der geteilten .mcp.json in den privaten Scope übernehmen
32
32
  mcp list Katalog-Einträge zeigen, mit wo sie eingehängt sind
33
33
  sync [pfad] Ziel auf den Stand seines Zeigers bringen
34
- update Alle Checkouts aktualisieren
34
+ update Alle Checkouts aktualisieren, mit <skill> oder --quelle nur eine Quelle
35
35
  status Ist-Zustand der Ziele
36
36
  log Verlauf der letzten Läufe (Changelog)
37
+ doctor Fehlende Arbeitsverzeichnisse finden und Vergessen anbieten
37
38
  harness list Harness-Typen mit Stand aktiv/inaktiv und vorhanden
38
39
  harness enable <name> Harness-Typ aktivieren, hmgr richtet ihn künftig ein
39
40
  harness disable <name> Harness-Typ deaktivieren, mit Angebot zum Säubern
@@ -118,7 +119,7 @@ Häufige Fälle:
118
119
  link --ort dir:<pfad> merkt das Verzeichnis von selbst. dir add braucht es vorher nur, wenn die
119
120
  composer.json mehrdeutig ist.
120
121
 
121
- Siehe auch: hmgr help link, hmgr help sync, hmgr help harness`;
122
+ Siehe auch: hmgr help link, hmgr help sync, hmgr help harness, hmgr help doctor`;
122
123
  const LINK = `hmgr link <skill> [schalter]
123
124
 
124
125
  Legt im Ziel einen Symlink auf den Skill an, der aus einer bekannten Quelle stammt, und trägt ihn
@@ -445,6 +446,8 @@ Häufige Fälle:
445
446
 
446
447
  Siehe auch: hmgr help dir, hmgr help link, hmgr help update, hmgr help harness`;
447
448
  const UPDATE = `hmgr update
449
+ hmgr update <skill>
450
+ hmgr update --quelle <teilstring>
448
451
 
449
452
  Holt für jeden Checkout unter ~/.hmgr den neuesten Stand seines Refs und zieht bei Harnesses mit
450
453
  Plugin-Fähigkeit deren Plugins nach (E14). Die Links zeigen weiter auf dieselben Pfade und müssen
@@ -483,10 +486,21 @@ aufgeräumter Checkout, ein Fehler), schreibt update denselben Eintrag auch in ~
483
486
  nicht im Verlauf: „claude plugin marketplace update" liefert keine Auskunft, ob sich etwas geändert
484
487
  hat, eine Zeile dazu wäre geschönt.
485
488
 
486
- Schalter: keine.
489
+ Eingeschränkter Lauf (E89): hmgr update <skill> zieht nur die Quelle nach, in der der Skill liegt
490
+ (liegt der Skill in mehreren Quellen, bricht update ab, dann --quelle nutzen). hmgr update --quelle <teilstring> nimmt ein Stück
491
+ von URL, Pfad oder Anzeigenamen der Quelle, Groß- und Kleinschreibung egal; passt keine oder
492
+ mehr als eine, bricht update mit der Liste ab. Beides zusammen geht nicht. Der Lauf umfasst die
493
+ Checkouts dieser Quelle und die Bundles, deren Definition in ihr liegt. Es entfallen: Plugins,
494
+ Aufräumen verwaister Checkouts, der Check auf fehlende Arbeitsverzeichnisse (doctor) sowie alle
495
+ übrigen Quellen und Bundles. Der Verlaufseintrag entsteht wie im vollen Lauf.
496
+
497
+ Schalter:
498
+ --quelle <teilstring> Nur diese Quelle nachziehen (statt <skill>)
487
499
 
488
500
  Beispiel:
489
501
  hmgr update
502
+ hmgr update mein-skill
503
+ hmgr update --quelle mein-repo
490
504
 
491
505
  Häufige Fälle:
492
506
  „übersprungen … lokale Änderung (E15)": im Checkout liegt eine Änderung, die durch einen Link
@@ -495,7 +509,7 @@ Häufige Fälle:
495
509
  „liegen lassen … ohne Bestätigung nicht gelöscht": die Frage kam ohne Ja zurück, etwa in einer
496
510
  Pipe oder einem Composer-Script. In einem Terminal erneut hmgr update aufrufen.
497
511
 
498
- Siehe auch: hmgr help sync, hmgr help status, hmgr help log`;
512
+ Siehe auch: hmgr help sync, hmgr help status, hmgr help log, hmgr help doctor`;
499
513
  const LOG = `hmgr log
500
514
 
501
515
  Zeigt den Verlauf der letzten Läufe aus ~/.hmgr/verlauf.json (E80), neueste zuerst: dieselbe Datei,
@@ -521,6 +535,25 @@ Häufige Fälle:
521
535
  „Noch keine Ereignisse": entweder frisch installiert, oder jeder bisherige Lauf war ohne Fund.
522
536
 
523
537
  Siehe auch: hmgr help update, hmgr help status`;
538
+ const DOCTOR = `hmgr doctor
539
+
540
+ Prüft, ob jedes gemerkte Arbeitsverzeichnis (hmgr dir add) noch auf der Platte existiert (E88): ein
541
+ umbenanntes oder woanders neu geklontes Projekt hinterlässt sonst für immer einen toten Pfad im
542
+ Zustand, den nur hmgr dir forget von Hand entfernt. Bei einem Fund fragt doctor wie hmgr update
543
+ einmal für alle Funde, ob sie vergessen werden sollen; ohne Bestätigung bleiben sie gemeldet, aber
544
+ gemerkt. Anders als update holt doctor nichts aus dem Netz und schreibt keinen Eintrag in den
545
+ Verlauf — ein reiner Konsolen-Check, kein Lauf im E80-Sinn.
546
+
547
+ Schalter: keine.
548
+
549
+ Beispiel:
550
+ hmgr doctor
551
+
552
+ Häufige Fälle:
553
+ „liegen lassen … ohne Bestätigung nicht vergessen": die Frage kam ohne Ja zurück. In einem
554
+ Terminal erneut hmgr doctor aufrufen, oder hmgr dir forget <pfad> gezielt.
555
+
556
+ Siehe auch: hmgr help update, hmgr help dir`;
524
557
  const STATUS = `hmgr status
525
558
 
526
559
  Liest den Ist-Zustand vom Dateisystem: je Ziel eines vorhandenen, aktiven Harness die Einträge mit Quelle
@@ -648,6 +681,7 @@ const THEMEN = new Map([
648
681
  ['update', UPDATE],
649
682
  ['status', STATUS],
650
683
  ['log', LOG],
684
+ ['doctor', DOCTOR],
651
685
  ['harness', HARNESS],
652
686
  ['help', HILFE],
653
687
  ]);
package/dist/cli/main.js CHANGED
@@ -93,9 +93,10 @@ const SCHALTER = {
93
93
  bundle: { werte: ['harness', 'ort', 'composer', 'quelle'], flaggen: [] },
94
94
  mcp: { werte: ['harness', 'ort'], flaggen: [] },
95
95
  sync: { werte: [], flaggen: [] },
96
- update: { werte: [], flaggen: [] },
96
+ update: { werte: ['quelle'], flaggen: [] },
97
97
  status: { werte: [], flaggen: [] },
98
98
  log: { werte: [], flaggen: [] },
99
+ doctor: { werte: [], flaggen: [] },
99
100
  harness: { werte: ['profil'], flaggen: [] },
100
101
  };
101
102
  /**
@@ -714,8 +715,20 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
714
715
  return 0;
715
716
  }
716
717
  if (kommando === 'update') {
717
- restPruefen(schalterLesen(argv.slice(1), 'update').rest, 0, 'update');
718
- const { checkouts: liste, bundles, aufraeumen } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli' });
718
+ const { rest: updateRest, werte: updateWerte } = schalterLesen(argv.slice(1), 'update');
719
+ restPruefen(updateRest, 1, 'update');
720
+ const updateSkill = updateRest[0];
721
+ const updateHinweis = updateWerte.get('quelle');
722
+ if (updateSkill !== undefined && updateHinweis !== undefined)
723
+ throw new Error('update nimmt entweder einen Skill oder --quelle, nicht beides. hmgr help update zeigt die Verwendung.');
724
+ // Eingeschränkter Lauf (E89): nur die Quelle des Skills bzw. die genannte, ohne Plugins,
725
+ // Aufräumen und doctor.
726
+ const nurQuelle = updateSkill !== undefined
727
+ ? (await anwendungsfaelle.skillFinden(updateSkill)).quelle
728
+ : updateHinweis !== undefined
729
+ ? await anwendungsfaelle.quelleZuHinweis(updateHinweis)
730
+ : undefined;
731
+ const { checkouts: liste, bundles, aufraeumen, doctor, } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli', ...(nurQuelle === undefined ? {} : { quelle: nurQuelle }) });
719
732
  if (liste.length === 0)
720
733
  console.log('Keine Checkouts.');
721
734
  for (const { checkout, aktualisierung, geaenderteSkills } of liste) {
@@ -751,6 +764,13 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
751
764
  const geloescht = aufraeumen.verwaiste.filter((v) => v.geloescht).length;
752
765
  console.log(`${aufraeumen.verwaiste.length} verwaist, ${geloescht} gelöscht, ${aufraeumen.verwaiste.length - geloescht} liegen geblieben.`);
753
766
  }
767
+ // Fehlende Arbeitsverzeichnisse (E88): was es auf der Platte nicht mehr gibt.
768
+ for (const pfad of doctor.fehlendeArbeitsverzeichnisse) {
769
+ if (doctor.vergessen.includes(pfad))
770
+ console.log(` vergessen Arbeitsverzeichnis ${pfad} (nicht mehr auf der Platte)`);
771
+ else
772
+ console.log(` liegen lassen Arbeitsverzeichnis ${pfad} (nicht mehr auf der Platte, ohne Bestätigung nicht vergessen)`);
773
+ }
754
774
  // Zuletzt hmgr selbst (E77): wer schon aktualisiert, will auch wissen, dass sein hmgr alt ist.
755
775
  // Verglichen wird die eigene Version (E72) gegen npm, gemeldet wird nur — installiert wird
756
776
  // nichts. Offline oder Registry unerreichbar heißt keine Zeile, still: der Lauf selbst ist
@@ -809,6 +829,19 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
809
829
  }
810
830
  return 0;
811
831
  }
832
+ if (kommando === 'doctor') {
833
+ restPruefen(schalterLesen(argv.slice(1), 'doctor').rest, 0, 'doctor');
834
+ const { fehlendeArbeitsverzeichnisse, vergessen } = await anwendungsfaelle.doctor();
835
+ if (fehlendeArbeitsverzeichnisse.length === 0)
836
+ console.log('Keine fehlenden Arbeitsverzeichnisse.');
837
+ for (const pfad of fehlendeArbeitsverzeichnisse) {
838
+ if (vergessen.includes(pfad))
839
+ console.log(` vergessen ${pfad}`);
840
+ else
841
+ console.log(` liegen lassen ${pfad} (ohne Bestätigung nicht vergessen)`);
842
+ }
843
+ return 0;
844
+ }
812
845
  if (kommando === 'harness') {
813
846
  const schalter = schalterLesen(argv.slice(1), 'harness');
814
847
  unterkommandoPruefen('harness', schalter);
@@ -1042,6 +1075,8 @@ function verlaufEreignisZeile(ereignis) {
1042
1075
  }
1043
1076
  if (ereignis.art === 'aufgeraeumt')
1044
1077
  return `Checkout ${ereignis.checkout} aufgeräumt`;
1078
+ if (ereignis.art === 'arbeitsverzeichnis-vergessen')
1079
+ return `Arbeitsverzeichnis ${ereignis.pfad} vergessen (nicht mehr auf der Platte)`;
1045
1080
  if (ereignis.art === 'fehler')
1046
1081
  return `Fehler ${ereignis.betrifft}: ${ereignis.fehler}`;
1047
1082
  if (ereignis.art === 'hmgr-installiert')
package/dist/gui/main.js CHANGED
@@ -676,6 +676,34 @@ function fensterOeffnen() {
676
676
  if (verlaufBloecke > 0) break;
677
677
  }
678
678
  var verlaufText = document.getElementById('verlauf')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
679
+ // Nachziehen je Quelle (E89): das Fixture hat im skills-repo einen neuen Upstream-Commit
680
+ // (fixtureAufbauen), den erst ein Lauf für diese Quelle holt. Erst der Knopf der
681
+ // anderen Quelle (kaputt-repo, ohne Checkout): der Verlauf bleibt, wie er ist, obwohl
682
+ // das skills-repo hinterherhinkt. Dann der Knopf des skills-repo: ein neuer Block, der
683
+ // nur diese Quelle nennt.
684
+ var nachziehenFehler = null;
685
+ var nachziehenKnopfZahl = document.querySelectorAll('#quellenliste .nachziehen').length;
686
+ var nachziehenBloeckeStart = document.querySelectorAll('#verlauf .block').length;
687
+ var nachziehenBloeckeNachAnderer = -1;
688
+ var nachziehenBloeckeNachEigener = -1;
689
+ var nachziehenErsterBlock = null;
690
+ try {
691
+ var nachziehenLauf = async (label) => {
692
+ var knopf = document.querySelector('#quellenliste button[aria-label="' + label + '"]');
693
+ if (!knopf) throw new Error('Knopf fehlt: ' + label);
694
+ knopf.click();
695
+ for (var w = 0; w < 100; w++) {
696
+ await new Promise((r) => setTimeout(r, 100));
697
+ if (!document.getElementById('updatestart').disabled) break;
698
+ }
699
+ return document.querySelectorAll('#verlauf .block').length;
700
+ };
701
+ nachziehenBloeckeNachAnderer = await nachziehenLauf('kaputt-repo nachziehen');
702
+ nachziehenBloeckeNachEigener = await nachziehenLauf('skills-repo nachziehen');
703
+ nachziehenErsterBlock = document.querySelector('#verlauf .block')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
704
+ } catch (fehler) {
705
+ nachziehenFehler = String(fehler);
706
+ }
679
707
  // E60: Beschreibung in der Ziel-Tabelle (nicht nur im Katalog), „manuell" entfällt,
680
708
  // Zahnrad statt Sonne am Einstellungen-Knopf.
681
709
  var zielBeschreibung = document.querySelector('#zielliste .linkliste .was')?.textContent ?? null;
@@ -1149,6 +1177,12 @@ function fensterOeffnen() {
1149
1177
  verlaufVorhanden,
1150
1178
  verlaufText,
1151
1179
  verlaufBloecke,
1180
+ nachziehenFehler,
1181
+ nachziehenKnopfZahl,
1182
+ nachziehenBloeckeStart,
1183
+ nachziehenBloeckeNachAnderer,
1184
+ nachziehenBloeckeNachEigener,
1185
+ nachziehenErsterBlock,
1152
1186
  zielsucheVorhanden,
1153
1187
  zielzahlNachSuche,
1154
1188
  zielKartenNachSucheZahl,
@@ -2629,7 +2629,9 @@
2629
2629
  standText();
2630
2630
  }
2631
2631
 
2632
- async function aktualisierenLauf(ausloeser) {
2632
+ // Mit `quelle` (E89, Knopf „Nachziehen" der Quellenliste) läuft nur diese Quelle, sonst alles.
2633
+ // Bericht, Stand-Zeile und Verlauf sind dieselben wie beim Kringel.
2634
+ async function aktualisierenLauf(ausloeser, quelle) {
2633
2635
  var knopf = $('updatestart');
2634
2636
  if (knopf.disabled) return;
2635
2637
  knopf.disabled = true;
@@ -2637,7 +2639,9 @@
2637
2639
  standLaeuft = true;
2638
2640
  standText();
2639
2641
  var lauf = rufe(function () {
2640
- return window.hmgr.aktualisieren({ rueckfragen: ausloeser !== 'klick', ausloeser: ausloeser });
2642
+ var args = { rueckfragen: ausloeser !== 'klick', ausloeser: ausloeser };
2643
+ if (quelle) args.quelle = quelle;
2644
+ return window.hmgr.aktualisieren(args);
2641
2645
  });
2642
2646
  // Jeder Lauf prüft auch hmgr selbst (E80): angestoßen, nicht abgewartet — ein Fund meldet sich
2643
2647
  // später von sich aus in der Seitenleiste (onSelbstUpdate).
@@ -2754,6 +2758,9 @@
2754
2758
  } else if (ereignis.art === 'aufgeraeumt') {
2755
2759
  li.appendChild(e('span', 'marke-ok', 'aufgeräumt'));
2756
2760
  li.appendChild(e('span', 'pfad', ereignis.checkout));
2761
+ } else if (ereignis.art === 'arbeitsverzeichnis-vergessen') {
2762
+ li.appendChild(e('span', 'marke-ok', 'vergessen'));
2763
+ li.appendChild(e('span', 'pfad', ereignis.pfad));
2757
2764
  } else if (ereignis.art === 'fehler') {
2758
2765
  li.appendChild(e('span', 'marke-aus', 'fehlgeschlagen'));
2759
2766
  li.appendChild(e('span', 'pfad', ereignis.betrifft + ': ' + ereignis.fehler));
@@ -3536,6 +3543,17 @@
3536
3543
  stift.appendChild(stiftSvg);
3537
3544
  stift.addEventListener('click', function () { oeffneQuelleUmbenennen(q); });
3538
3545
  knoepfe.appendChild(stift);
3546
+ // Nur diese Quelle nachziehen (E89): wie der Kringel in der Update-Ansicht, aber ohne
3547
+ // Plugins, Aufräumen und übrige Quellen. Der Renderer kennt nur Text und Art (E54), die
3548
+ // Quelle für den Kern baut er daraus.
3549
+ var nachziehen = e('button', 'knopf leise klein nachziehen', 'Nachziehen');
3550
+ nachziehen.type = 'button';
3551
+ nachziehen.setAttribute('aria-label', q.name + ' nachziehen');
3552
+ nachziehen.addEventListener('click', function () {
3553
+ var quelle = q.art === 'lokal' ? { art: 'lokal', pfad: q.text } : { art: 'git', url: q.text };
3554
+ aktualisierenLauf('klick', quelle);
3555
+ });
3556
+ knoepfe.appendChild(nachziehen);
3539
3557
  var weg = e('button', 'knopf leise klein', 'Entfernen …');
3540
3558
  weg.type = 'button';
3541
3559
  weg.setAttribute('aria-label', q.name + ' entfernen');
@@ -14,6 +14,17 @@ export function aufraeumFrage(checkouts) {
14
14
  const pfade = checkouts.map((c) => ` ${c.pfad}`).join('\n');
15
15
  return `${kopf}\n${pfade}\nLöschen?`;
16
16
  }
17
+ /**
18
+ * Frage vor dem Vergessen fehlender Arbeitsverzeichnisse (E88), eine für alle Funde — Analogie zu
19
+ * `aufraeumFrage`.
20
+ */
21
+ export function doctorFrage(pfade) {
22
+ const kopf = pfade.length === 1
23
+ ? 'Ein Arbeitsverzeichnis gibt es auf der Platte nicht mehr:'
24
+ : `${pfade.length} Arbeitsverzeichnisse gibt es auf der Platte nicht mehr:`;
25
+ const zeilen = pfade.map((p) => ` ${p}`).join('\n');
26
+ return `${kopf}\n${zeilen}\nVergessen?`;
27
+ }
17
28
  function arbeitsverzeichnisZiel(harness, pfad) {
18
29
  return { harness, ort: { art: 'arbeitsverzeichnis', pfad } };
19
30
  }
@@ -1098,7 +1109,9 @@ export class Anwendungsfaelle {
1098
1109
  async bundleAbgleich(args) {
1099
1110
  const zustand = await this.p.zustand.lesen();
1100
1111
  const zielSchl = args?.ziel === undefined ? undefined : zielSchluessel(args.ziel);
1112
+ const quelleSchl = args?.quelle === undefined ? undefined : quelleSchluessel(args.quelle);
1101
1113
  const installationen = zustand.bundleInstallationen.filter((i) => (args?.bundle === undefined || i.bundle === args.bundle) &&
1114
+ (quelleSchl === undefined || quelleSchluessel(i.quelle) === quelleSchl) &&
1102
1115
  (zielSchl === undefined || zielSchluessel(Anwendungsfaelle.installationZiel(i)) === zielSchl));
1103
1116
  const ergebnis = [];
1104
1117
  for (const installation of installationen) {
@@ -1205,6 +1218,26 @@ export class Anwendungsfaelle {
1205
1218
  zustand.bindungen = zustand.bindungen.filter((b) => !(b.ziel.ort.art === 'arbeitsverzeichnis' && b.ziel.ort.pfad === verzeichnis));
1206
1219
  await this.p.zustand.schreiben(zustand);
1207
1220
  }
1221
+ /**
1222
+ * Arbeitsverzeichnisse erkennen, die es auf der Platte nicht mehr gibt, und ihr Vergessen anbieten
1223
+ * (E88). `rueckfragen: false` (GUI-Automatik, Smoke) meldet nur, fragt nicht und vergisst nichts —
1224
+ * dieselbe Weiche wie bei `verwaisteAufraeumen`.
1225
+ */
1226
+ async doctor(args) {
1227
+ const rueckfragen = args?.rueckfragen ?? true;
1228
+ const zustand = await this.p.zustand.lesen();
1229
+ const fehlend = [];
1230
+ for (const pfad of zustand.arbeitsverzeichnisse)
1231
+ if (!(await this.p.zeiger.verzeichnisExistiert(pfad)))
1232
+ fehlend.push(pfad);
1233
+ if (fehlend.length === 0 || !rueckfragen)
1234
+ return { fehlendeArbeitsverzeichnisse: fehlend, vergessen: [] };
1235
+ if (!(await this.p.rueckfrage.bestaetigen(doctorFrage(fehlend))))
1236
+ return { fehlendeArbeitsverzeichnisse: fehlend, vergessen: [] };
1237
+ for (const pfad of fehlend)
1238
+ await this.arbeitsverzeichnisVergessen(pfad);
1239
+ return { fehlendeArbeitsverzeichnisse: fehlend, vergessen: [...fehlend] };
1240
+ }
1208
1241
  /**
1209
1242
  * Mehrere composer.json unter einem Verzeichnis (E26): hmgr rät nicht, es fragt. Bleibt die Frage
1210
1243
  * ohne Antwort, weil niemand antworten kann oder die Eingabe keine der Kandidaten trifft, gilt
@@ -1243,6 +1276,22 @@ export class Anwendungsfaelle {
1243
1276
  if ((await this.bindungFinden(ziel)) !== undefined)
1244
1277
  throw new ZeigerSchreibenGesperrt(ziel);
1245
1278
  }
1279
+ /**
1280
+ * Die eine geführte Quelle zu einem Teilstring (E89, `hmgr update --quelle`): verglichen wird mit
1281
+ * Schreibweise (URL/Pfad), Schlüssel und Anzeigenamen, Groß- und Kleinschreibung egal. Kein Treffer
1282
+ * und mehrere Treffer sind Fehler, mit der Liste, aus der sich der Nutzer eingrenzen kann.
1283
+ */
1284
+ async quelleZuHinweis(hinweis) {
1285
+ const quellen = (await this.p.zustand.lesen()).quellen;
1286
+ const klein = hinweis.toLowerCase();
1287
+ const treffer = quellen.filter((q) => [quelleAlsText(q), quelleSchluessel(q), q.name ?? ''].some((t) => t.toLowerCase().includes(klein)));
1288
+ const zeilen = (liste) => liste.map((q) => ` ${quelleAlsText(q)}${q.name === undefined ? '' : ` (${q.name})`}`).join('\n');
1289
+ if (treffer.length === 1)
1290
+ return treffer[0];
1291
+ if (treffer.length === 0)
1292
+ throw new Error(`Keine Quelle passt auf „${hinweis}".${quellen.length === 0 ? '' : `\nBekannte Quellen:\n${zeilen(quellen)}`}`);
1293
+ throw new Error(`Mehrere Quellen passen auf „${hinweis}", bitte eingrenzen:\n${zeilen(treffer)}`);
1294
+ }
1246
1295
  /** Skill über alle Quellen suchen. Mehrere Treffer sind nur mit Quellenhinweis erlaubt. */
1247
1296
  async skillFinden(name, quelleHinweis) {
1248
1297
  const zustand = await this.p.zustand.lesen();
@@ -1728,11 +1777,18 @@ export class Anwendungsfaelle {
1728
1777
  * ergeben hat: unveränderte oder übersprungene Checkouts und Bundles ohne Änderung erzeugen
1729
1778
  * nichts, ein leerer Lauf bleibt ganz aus dem Changelog. `ausloeser` steht mit im Eintrag, damit
1730
1779
  * der Verlauf zeigt, ob ein Fund vom automatischen Lauf, einem Klick oder der CLI kam.
1780
+ *
1781
+ * Mit `quelle` (E89) läuft nur diese geführte Quelle: ihre Checkouts, ihr Katalog-Fetch und die
1782
+ * Bundle-Installationen, deren Definition in ihr liegt. Plugins, Aufräumen verwaister Checkouts
1783
+ * und doctor entfallen (leere Ergebnisse), weil sie den Gesamtbestand betreffen.
1731
1784
  */
1732
1785
  async aktualisieren(args) {
1733
1786
  const rueckfragen = args?.rueckfragen ?? true;
1734
1787
  const ausloeser = args?.ausloeser ?? 'cli';
1735
- const bekannt = new Map((await this.p.zustand.lesen()).quellen.map((q) => [quelleSchluessel(q), q]));
1788
+ const nurSchluessel = args?.quelle === undefined ? undefined : quelleSchluessel(args.quelle);
1789
+ const bekannt = new Map((await this.p.zustand.lesen()).quellen
1790
+ .filter((q) => nurSchluessel === undefined || quelleSchluessel(q) === nurSchluessel)
1791
+ .map((q) => [quelleSchluessel(q), q]));
1736
1792
  const checkouts = [];
1737
1793
  for (const checkout of await this.p.quellen.checkouts()) {
1738
1794
  const gefuehrt = bekannt.get(quelleSchluessel(checkout.quelle));
@@ -1774,7 +1830,7 @@ export class Anwendungsfaelle {
1774
1830
  }
1775
1831
  // Nach den Checkouts die Plugins nachziehen (E14).
1776
1832
  const pluginHarnesses = [];
1777
- for (const harness of await this.vorhandeneHarnesses()) {
1833
+ for (const harness of nurSchluessel !== undefined ? [] : await this.vorhandeneHarnesses()) {
1778
1834
  if (!kannPlugins(harness))
1779
1835
  continue;
1780
1836
  await harness.plugins.aktualisieren();
@@ -1783,17 +1839,22 @@ export class Anwendungsfaelle {
1783
1839
  // Bundles vor dem Aufräumen (E69, F64=O1): der Abgleich legt und entfernt Links, und erst
1784
1840
  // danach steht fest, in welchen Checkout noch ein Link zeigt. Ein Fehlschlag je Bundle steckt
1785
1841
  // in seinem Eintrag, er bricht den Lauf nicht ab — wie eine unerreichbare Quelle (E51).
1786
- const bundles = await this.bundleAbgleich();
1787
- const aufraeumen = await this.verwaisteAufraeumen(checkouts, rueckfragen);
1788
- await this.verlaufVermerken(ausloeser, this.aktualisierungsEreignisse(checkouts, bundles, aufraeumen));
1789
- return { checkouts, pluginHarnesses, bundles, aufraeumen };
1842
+ const bundles = await this.bundleAbgleich(args?.quelle === undefined ? undefined : { quelle: args.quelle });
1843
+ const aufraeumen = nurSchluessel !== undefined
1844
+ ? { verwaiste: [], gefragt: false }
1845
+ : await this.verwaisteAufraeumen(checkouts, rueckfragen);
1846
+ const doctorErgebnis = nurSchluessel !== undefined
1847
+ ? { fehlendeArbeitsverzeichnisse: [], vergessen: [] }
1848
+ : await this.doctor({ rueckfragen });
1849
+ await this.verlaufVermerken(ausloeser, this.aktualisierungsEreignisse(checkouts, bundles, aufraeumen, doctorErgebnis));
1850
+ return { checkouts, pluginHarnesses, bundles, aufraeumen, doctor: doctorErgebnis };
1790
1851
  }
1791
1852
  /**
1792
1853
  * Ereignisse eines Aktualisieren-Laufs für den Verlauf (E80): Plugins bleiben draußen, weil sich
1793
1854
  * eine Änderung dort nicht messen lässt (`claude plugin marketplace update` liefert keine
1794
1855
  * Auskunft) — die bisherige Zeile „Plugins aktualisiert" war deshalb geschönt.
1795
1856
  */
1796
- aktualisierungsEreignisse(checkouts, bundles, aufraeumen) {
1857
+ aktualisierungsEreignisse(checkouts, bundles, aufraeumen, doctorErgebnis) {
1797
1858
  const ereignisse = [];
1798
1859
  for (const { checkout, aktualisierung, geaenderteSkills } of checkouts) {
1799
1860
  if (aktualisierung.ergebnis === 'aktualisiert')
@@ -1827,6 +1888,8 @@ export class Anwendungsfaelle {
1827
1888
  for (const verwaist of aufraeumen.verwaiste)
1828
1889
  if (verwaist.geloescht)
1829
1890
  ereignisse.push({ art: 'aufgeraeumt', checkout: `${quelleAlsText(verwaist.checkout.quelle)} @ ${verwaist.checkout.ref}` });
1891
+ for (const pfad of doctorErgebnis.vergessen)
1892
+ ereignisse.push({ art: 'arbeitsverzeichnis-vergessen', pfad });
1830
1893
  return ereignisse;
1831
1894
  }
1832
1895
  /** Neuen Eintrag vorn anfügen, nur wenn Ereignisse da sind (E80). Kappen übernimmt der Adapter. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",