hmgr 0.0.15 → 0.0.17

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.
@@ -153,25 +153,30 @@ export class DateisystemZiele {
153
153
  }
154
154
  return undefined;
155
155
  }
156
- /**
157
- * Ein Skill ist ein Verzeichnis. Dateien wie .DS_Store zählen nicht als fremder Eintrag,
158
- * ebenso wenig ein Symlink, der auf kein Verzeichnis zeigt.
159
- */
160
- istVerzeichnis(info) {
161
- return info !== undefined && info.istVerzeichnis;
162
- }
163
156
  async eintraege(ziel, verzeichnis) {
164
157
  const zugriff = this.zugriffFuer(ziel);
165
158
  const namen = await zugriff.namen(verzeichnis);
166
159
  const ergebnis = [];
167
160
  for (const name of [...namen].sort()) {
168
161
  const pfad = zugriff.verbinden(verzeichnis, name);
169
- if (!this.istVerzeichnis(await zugriff.info(pfad)))
162
+ const info = await zugriff.info(pfad);
163
+ // Kein Eintrag überhaupt: nichts zu melden.
164
+ if (info === undefined)
165
+ continue;
166
+ // Eine normale Datei (kein Verzeichnis, kein Link), etwa .DS_Store: zählt nicht (E12).
167
+ if (!info.istLink && !info.istVerzeichnis)
170
168
  continue;
171
169
  const zielPfad = await zugriff.linkZiel(pfad);
172
170
  const gedeutet = zielPfad ? await this.deuten(zielPfad) : undefined;
171
+ // Ein Symlink, der ins Leere zeigt (E85): weder überspringen noch verschlucken — die Deutung
172
+ // funktioniert rein pfadstrukturell auch ohne dass das Ziel existiert, und ein eigener Link
173
+ // wird als kaputt gemeldet statt unsichtbar zu bleiben.
174
+ const kaputt = info.istLink && !info.istVerzeichnis ? true : undefined;
173
175
  if (gedeutet?.art === 'link')
174
- ergebnis.push({ art: 'link', link: { ziel, name, checkout: gedeutet.checkout, skill: gedeutet.skill } });
176
+ ergebnis.push({
177
+ art: 'link',
178
+ link: { ziel, name, checkout: gedeutet.checkout, skill: gedeutet.skill, ...(kaputt ? { kaputt } : {}) },
179
+ });
175
180
  else if (gedeutet)
176
181
  ergebnis.push({ art: 'link-ohne-quelle', name, checkoutPfad: gedeutet.checkoutPfad });
177
182
  // Ist der fremde Eintrag ein Symlink, wandert sein Ziel mit: darauf setzt die Übernahme auf (B12).
package/dist/cli/hilfe.js CHANGED
@@ -449,7 +449,9 @@ const UPDATE = `hmgr update
449
449
  Holt für jeden Checkout unter ~/.hmgr den neuesten Stand seines Refs und zieht bei Harnesses mit
450
450
  Plugin-Fähigkeit deren Plugins nach (E14). Die Links zeigen weiter auf dieselben Pfade und müssen
451
451
  nicht erneuert werden. Ein Checkout mit lokaler Änderung wird übersprungen und gemeldet (E15),
452
- damit nichts verloren geht. Lokale Quellen haben keinen Checkout und bleiben unberührt.
452
+ damit nichts verloren geht. Lokale Quellen haben keinen Checkout und bleiben unberührt. Auch eine
453
+ Quelle ohne eigenen Checkout wird dabei aufgefrischt (E85): ihr Katalog zeigt danach, was neu im
454
+ Remote liegt, auch ohne dass irgendwo ein Checkout von ihr existiert.
453
455
 
454
456
  Unter jeder „aktualisiert"-Zeile steht, welche Skills der neue Stand geändert hat (E70): die Namen
455
457
  kommen aus dem Vergleich von altem und neuem Commit, gezählt werden nur Skills, die in diesem
package/dist/cli/main.js CHANGED
@@ -783,7 +783,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
783
783
  // Steht im Zeiger eine Versionseinschränkung, kommt sie vor den aufgelösten Ref (E33).
784
784
  const einschraenkung = zielZustand.einschraenkungen.get(eintrag.link.name);
785
785
  const spanne = einschraenkung === undefined ? '' : `${einschraenkung} → `;
786
- console.log(` ${eintrag.link.name} ${spanne}${quelleAlsText(eintrag.link.checkout.quelle)} @ ${eintrag.link.checkout.ref} (${eintrag.link.skill.pfad})${beschreibeGrund(zielZustand.gruende.get(eintrag.link.name))}`);
786
+ console.log(` ${eintrag.link.name} ${spanne}${quelleAlsText(eintrag.link.checkout.quelle)} @ ${eintrag.link.checkout.ref} (${eintrag.link.skill.pfad})${beschreibeGrund(zielZustand.gruende.get(eintrag.link.name))}${eintrag.link.kaputt ? ` — Ziel fehlt, Quelle wurde vermutlich geändert. \`hmgr link ${eintrag.link.name}\` erneut ausführen` : ''}`);
787
787
  }
788
788
  }
789
789
  // Bundles des Ziels (E69), unter den Einträgen: welche Links als Einheit zusammengehören.
package/dist/gui/main.js CHANGED
@@ -934,6 +934,14 @@ function fensterOeffnen() {
934
934
  var kundeXWarnzahl = warnzahlVon(kundeXKarte);
935
935
  var kundeXWarnlabel = warnungVon(kundeXKarte)?.getAttribute('aria-label') ?? null;
936
936
  var agentsWarnungVorhanden = warnungVon(agentsKarte) !== null;
937
+ // Kaputter Link (E85): eigene Quelle „kaputt-repo", Skill „bruechig" im Claude-Code-
938
+ // Home-Ziel, dessen Checkout-Ordner die Fixture vor dem Start gelöscht hat. Die Karte
939
+ // muss dafür ein Dreieck zeigen (Auslöser 1) und die Zeile eine Warnmarke „Ziel fehlt".
940
+ var claudeKarte = karteMitKopftext('Claude Code · Home');
941
+ var claudeWarnzahl = warnzahlVon(claudeKarte);
942
+ var kaputtZeile = Array.from(claudeKarte ? claudeKarte.querySelectorAll('.linkliste li') : [])
943
+ .find((li) => (li.querySelector('.name')?.textContent || '') === 'bruechig');
944
+ var kaputtMarkeVorhanden = (kaputtZeile?.querySelector('.merker.warnend')?.textContent || '') === 'Ziel fehlt';
937
945
  // Dialog „MCP hinzufügen" (E47-Muster): öffnen an der Hermes-Karte und über Abbrechen
938
946
  // wieder schließen, ohne IPC-Schreibaktion (mcpEinhaengen liefe erst nach „Einhängen").
939
947
  var dmhFehler = null;
@@ -1118,6 +1126,8 @@ function fensterOeffnen() {
1118
1126
  kundeXWarnzahl,
1119
1127
  kundeXWarnlabel,
1120
1128
  agentsWarnungVorhanden,
1129
+ claudeWarnzahl,
1130
+ kaputtMarkeVorhanden,
1121
1131
  quellenLeerDisplay,
1122
1132
  agentsKarteVorhanden,
1123
1133
  agentsMcpAbschnittVorhanden,
@@ -358,6 +358,7 @@
358
358
  var ignoriert = z.ignoriert || [];
359
359
  return z.eintraege.filter(function (ei) {
360
360
  if (ei.art === 'fremd') return ignoriert.indexOf(ei.name) === -1;
361
+ if (ei.art === 'link') return !!ei.link.kaputt;
361
362
  return ei.art === 'link-ohne-quelle';
362
363
  }).length;
363
364
  }
@@ -754,6 +755,15 @@
754
755
 
755
756
  li.appendChild(e('span', 'name', name));
756
757
 
758
+ // Kaputter Link (E85): das Zielverzeichnis existiert nicht mehr, meist weil ein `update` den
759
+ // Skill aus seiner Quelle nicht mehr fand. Derselbe Marken-Stil wie „registriert Hooks".
760
+ if (ei.art === 'link' && ei.link.kaputt) {
761
+ var kaputtMarke = e('span', 'merker leise warnend', 'Ziel fehlt');
762
+ kaputtMarke.setAttribute('aria-label', 'Ziel fehlt: Quelle wurde vermutlich geändert, hmgr link ' + name + ' erneut ausführen');
763
+ kaputtMarke.title = 'Quelle wurde vermutlich geändert. hmgr link ' + name + ' erneut ausführen.';
764
+ li.appendChild(kaputtMarke);
765
+ }
766
+
757
767
  // Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
758
768
  // deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
759
769
  li.appendChild(wasZelle(ei.art === 'link' ? ei.link.skill : null));
@@ -2261,6 +2271,16 @@
2261
2271
  }
2262
2272
  $('dsk-suche').addEventListener('input', filtereDskListe);
2263
2273
 
2274
+ /** Quelle · [Pfad ·] Ref für eine Skill-Karte im „Skills hinzufügen"-Dialog (F106): der Pfad
2275
+ unterscheidet Skills, die unter demselben Namen mehrfach im selben Repo liegen (z. B. je
2276
+ Harness ein eigener Ordner). Ein Wurzel-Skill hat pfad === '' und zeigt kein Pfad-Segment. */
2277
+ function skillQuelleBeschriftung(skill) {
2278
+ var teile = [skill.quelleName];
2279
+ if (skill.pfad) teile.push(skill.pfad);
2280
+ teile.push('Ref ' + skill.ref);
2281
+ return teile.join(' · ');
2282
+ }
2283
+
2264
2284
  function oeffneSkillsHinzufuegen(z) {
2265
2285
  skillsKontext = { modus: 'einzeln', z: z };
2266
2286
  $('dsk-ziel').textContent = zielTitel(z);
@@ -2285,7 +2305,7 @@
2285
2305
  // Dieselben Marken wie im Katalog (F13): hier fällt die Entscheidung, was installiert wird.
2286
2306
  var marken = skillMarkenBox(skill);
2287
2307
  if (marken) txt.appendChild(marken);
2288
- var quelleSpanEl = e('span', 'mono', skill.quelleName + ' · Ref ' + skill.ref);
2308
+ var quelleSpanEl = e('span', 'mono', skillQuelleBeschriftung(skill));
2289
2309
  quelleSpanEl.title = skill.quelleText;
2290
2310
  txt.appendChild(quelleSpanEl);
2291
2311
  if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
@@ -2327,7 +2347,7 @@
2327
2347
  // Dieselben Marken wie im Katalog (F13): hier fällt die Entscheidung, was installiert wird.
2328
2348
  var marken = skillMarkenBox(skill);
2329
2349
  if (marken) txt.appendChild(marken);
2330
- var quelleSpanEl = e('span', 'mono', skill.quelleName + ' · Ref ' + skill.ref);
2350
+ var quelleSpanEl = e('span', 'mono', skillQuelleBeschriftung(skill));
2331
2351
  quelleSpanEl.title = skill.quelleText;
2332
2352
  txt.appendChild(quelleSpanEl);
2333
2353
  if (ueberall) txt.appendChild(e('span', 'notiz', 'Hängt schon überall.'));
@@ -3479,7 +3499,12 @@
3479
3499
  liste.appendChild(e('li', 'leerhinweis', 'Noch keine Quelle. „Quelle hinzufügen …" legt die erste an.'));
3480
3500
  return;
3481
3501
  }
3482
- QUELLEN.forEach(function (q) {
3502
+ // Kopie statt QUELLEN direkt: baueQuellenfilter() und andere Stellen iterieren über QUELLEN in
3503
+ // Lieferreihenfolge, die soll unangetastet bleiben (F105).
3504
+ var quellenSortiert = QUELLEN.slice().sort(function (a, b) {
3505
+ return a.name.localeCompare(b.name, 'de', { sensitivity: 'base' });
3506
+ });
3507
+ quellenSortiert.forEach(function (q) {
3483
3508
  var li = document.createElement('li');
3484
3509
  li.appendChild(repoIcon(q));
3485
3510
  var beschriftung = e('b', null, q.name);
@@ -1754,6 +1754,24 @@ export class Anwendungsfaelle {
1754
1754
  if (aktualisierung.ergebnis === 'aktualisiert' || aktualisierung.ergebnis === 'unveraendert')
1755
1755
  this.nachgezogen.add(quelleSchluessel(checkout.quelle));
1756
1756
  }
1757
+ // Quellen ohne eigenen Checkout fetchen den Checkout-Loop oben nie an (E85): ihr Basis-Clone
1758
+ // würde sonst für immer auf dem Stand von `source add` stehen bleiben, und ein neuer Skill in
1759
+ // so einer Quelle wäre für `link`/`status`/`source list` unsichtbar. Jede git-Quelle, die im
1760
+ // Loop noch nicht nachgezogen wurde, holt hier ihre Refs nach — ohne Checkout-Update, reine
1761
+ // Katalogpflege. Ein Fehlschlag (kein Netz, Quelle nicht erreichbar) bricht den Lauf nicht ab.
1762
+ for (const quelle of bekannt.values()) {
1763
+ if (quelle.art !== 'git')
1764
+ continue;
1765
+ if (this.nachgezogen.has(quelleSchluessel(quelle)))
1766
+ continue;
1767
+ try {
1768
+ await this.p.quellen.refsNachziehen(quelle);
1769
+ }
1770
+ catch {
1771
+ // wie ein unerreichbarer Checkout (E51): übrige Quellen laufen weiter.
1772
+ }
1773
+ this.nachgezogen.add(quelleSchluessel(quelle));
1774
+ }
1757
1775
  // Nach den Checkouts die Plugins nachziehen (E14).
1758
1776
  const pluginHarnesses = [];
1759
1777
  for (const harness of await this.vorhandeneHarnesses()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",