hmgr 0.0.51 → 0.0.53
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/dist/adapter/agents.js +3 -0
- package/dist/adapter/claude-code.js +3 -0
- package/dist/adapter/hermes.js +3 -0
- package/dist/aenderungen.json +6 -0
- package/dist/cli/hilfe.js +9 -0
- package/dist/cli/main.js +28 -2
- package/dist/gui/main.js +24 -4
- package/dist/gui/preload.cjs +1 -0
- package/dist/gui/renderer/renderer.js +103 -36
- package/dist/gui/renderer/styles.css +2 -12
- package/dist/kern/anwendungsfaelle.js +77 -14
- package/dist/kern/modell.js +16 -2
- package/package.json +1 -1
package/dist/adapter/agents.js
CHANGED
|
@@ -51,6 +51,9 @@ export class Agents {
|
|
|
51
51
|
if (ort.art === 'arbeitsverzeichnis' && (await istGitRepo(ort.pfad)))
|
|
52
52
|
await gitignoreEintragen(ort.pfad, GITIGNORE_ZEILE);
|
|
53
53
|
}
|
|
54
|
+
quellOrdner() {
|
|
55
|
+
return `${AGENTS_VERZEICHNIS}/${SKILLS_ORDNER}`;
|
|
56
|
+
}
|
|
54
57
|
folgtLinks() {
|
|
55
58
|
return true;
|
|
56
59
|
}
|
|
@@ -681,6 +681,9 @@ export class ClaudeCode {
|
|
|
681
681
|
if (ort.art === 'arbeitsverzeichnis' && (await istGitRepo(ort.pfad)))
|
|
682
682
|
await gitignoreEintragen(ort.pfad, GITIGNORE_ZEILE);
|
|
683
683
|
}
|
|
684
|
+
quellOrdner() {
|
|
685
|
+
return `${CLAUDE_VERZEICHNIS}/${SKILLS_ORDNER}`;
|
|
686
|
+
}
|
|
684
687
|
folgtLinks() {
|
|
685
688
|
return true;
|
|
686
689
|
}
|
package/dist/adapter/hermes.js
CHANGED
package/dist/aenderungen.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"fassung": 1,
|
|
3
3
|
"versionen": {
|
|
4
|
+
"0.0.53": [
|
|
5
|
+
"MCP-Server stehen an der Ordnerkarte jetzt in derselben Tabelle wie die Skills, mit dem Zeichen des Harness davor. Ein Tooltip erklärt, warum ein Server für Hermes fehlt: Hermes liest MCP-Server nur im Home oder in einem Profil, nicht pro Ordner. `hmgr mcp list` sagt das am Ende ebenfalls."
|
|
6
|
+
],
|
|
7
|
+
"0.0.52": [
|
|
8
|
+
"Liegt ein Skill in einem Repo in mehreren Fassungen, je Werkzeug eine (etwa `.claude/skills` und `.hermes/skills`), verlinkt hmgr jetzt für jeden Harness die passende Fassung. Gibt es keine passende, nennt die Meldung die Pfade, und `hmgr link … --pfad` wählt einen aus."
|
|
9
|
+
],
|
|
4
10
|
"0.0.51": [
|
|
5
11
|
"Ein gelöschter Projektordner, an dem noch ein Bundle hing, wird beim Aktualisieren nicht mehr neu angelegt. `hmgr doctor` findet solche Ordner jetzt und bietet an, sie samt ihren Bundles zu vergessen."
|
|
6
12
|
],
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -147,6 +147,7 @@ Schalter:
|
|
|
147
147
|
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
148
148
|
kennt nur hermes, wsl:<distro> nur claude-code unter Windows (E78)
|
|
149
149
|
--quelle <teil> Teilstring der Quelle, wenn der Skill-Name in mehreren Quellen vorkommt
|
|
150
|
+
--pfad <skill-pfad> Skill-Ordner in der Quelle, wenn der Name dort mehrfach vorkommt (E121)
|
|
150
151
|
--version <spanne> Versionseinschränkung nach Composer-Regeln (etwa ^1.2) oder ein Tag- oder
|
|
151
152
|
Branchname. Nur mit Zeiger, also dir:<pfad> ohne --local (E33)
|
|
152
153
|
--composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
|
|
@@ -155,11 +156,15 @@ Schalter:
|
|
|
155
156
|
|
|
156
157
|
Beispiele:
|
|
157
158
|
hmgr link netzwerk
|
|
159
|
+
hmgr link impeccable --pfad plugin/skills/impeccable
|
|
158
160
|
hmgr link netzwerk --harness hermes --ort profil:researcher
|
|
159
161
|
hmgr link tq-schema --ort dir:. --version ^2.0
|
|
160
162
|
|
|
161
163
|
Häufige Fälle:
|
|
162
164
|
„liegt in mehreren Quellen": mit --quelle <teilstring der URL oder des Pfads> entscheiden.
|
|
165
|
+
„liegt in der Quelle … mehrfach": mit --pfad einen der genannten Pfade wählen. Liegt genau
|
|
166
|
+
eine Fassung im Skill-Ordner des Harness (claude-code .claude/skills, hermes .hermes/skills,
|
|
167
|
+
agents .agents/skills), nimmt hmgr sie von selbst und fragt nicht nach.
|
|
163
168
|
„liegt bereits ein fremder Eintrag": --ersetzen setzen oder vorher hmgr adopt <name>.
|
|
164
169
|
„gibt es keinen Zeiger": --version braucht ein Arbeitsverzeichnis mit composer.json und kein
|
|
165
170
|
--local. Ohne Zeiger den Link ohne --version legen.
|
|
@@ -419,6 +424,10 @@ global (Scope user). Hermes kennt nur global, mcp_servers in seiner config.yaml
|
|
|
419
424
|
mit --ort profil:<name>); ein Projekt-Ziel ist bei Hermes ein Fehler, es kennt keine Projektebene für
|
|
420
425
|
MCPs. agents kennt MCP-Server gar nicht.
|
|
421
426
|
|
|
427
|
+
Hängt ein Eintrag an einem Arbeitsverzeichnis und ist ein aktiver Harness ohne Projektebene (Hermes) da,
|
|
428
|
+
endet mcp list mit einer Zeile, die sagt, dass dieser Harness den Eintrag dort nicht liest, und auf
|
|
429
|
+
--ort home oder --ort profil:<name> verweist.
|
|
430
|
+
|
|
422
431
|
Bei claude-code führt der Scope local die Server je Git-Repo, nicht je Unterordner (E91). Ein
|
|
423
432
|
Arbeitsverzeichnis, das in einem Repo liegt, ohne dessen Wurzel zu sein, bekommt deshalb kein mcp add,
|
|
424
433
|
mcp remove und mcp migrate: die Meldung nennt den Wurzelordner, dort gehören die Server hin (ist er
|
package/dist/cli/main.js
CHANGED
|
@@ -121,7 +121,7 @@ const SCHALTER = {
|
|
|
121
121
|
source: { werte: ['name'], flaggen: [] },
|
|
122
122
|
dir: { werte: ['composer'], flaggen: [] },
|
|
123
123
|
link: {
|
|
124
|
-
werte: ['harness', 'ort', 'composer', 'quelle', 'version'],
|
|
124
|
+
werte: ['harness', 'ort', 'composer', 'quelle', 'version', 'pfad'],
|
|
125
125
|
flaggen: ['local', 'lokal', 'ersetzen'],
|
|
126
126
|
},
|
|
127
127
|
unlink: { werte: ['harness', 'ort', 'composer'], flaggen: ['local', 'lokal'] },
|
|
@@ -507,10 +507,12 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
507
507
|
}
|
|
508
508
|
const quelle = werte.get('quelle');
|
|
509
509
|
const version = werte.get('version');
|
|
510
|
+
const pfad = werte.get('pfad');
|
|
510
511
|
const link = await anwendungsfaelle.linkLegen({
|
|
511
512
|
skill,
|
|
512
513
|
ziel,
|
|
513
514
|
...(quelle === undefined ? {} : { quelle }),
|
|
515
|
+
...(pfad === undefined ? {} : { pfad }),
|
|
514
516
|
...(version === undefined ? {} : { version }),
|
|
515
517
|
ersetzen: flaggen.has('ersetzen'),
|
|
516
518
|
lokal,
|
|
@@ -739,6 +741,28 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
739
741
|
for (const f of [...uebersicht.unbekannteGeteilteFundorte].sort((a, b) => nachName(a.name, b.name)))
|
|
740
742
|
console.log(` ${f.name} ${f.ziel.harness} ${beschreibeOrt(f.ziel.ort)} ${f.datei} · Scope ${f.scope}`);
|
|
741
743
|
}
|
|
744
|
+
// E120: hängt etwas an einem Arbeitsverzeichnis, erklärt eine Zeile, warum ein aktiver Harness
|
|
745
|
+
// ohne Ordner-MCP (Hermes) es dort nicht hat. Abgeleitet aus den Ebenen der Fähigkeit (E16).
|
|
746
|
+
const anOrdner = [
|
|
747
|
+
...uebersicht.katalog.flatMap((k) => [...k.eingehaengt, ...k.geteilteFundorte.map((g) => g.ziel)]),
|
|
748
|
+
...uebersicht.unbekannt.map((u) => u.ziel),
|
|
749
|
+
...uebersicht.unbekannteGeteilteFundorte.map((u) => u.ziel),
|
|
750
|
+
].some((z) => z.ort.art === 'arbeitsverzeichnis');
|
|
751
|
+
if (anOrdner) {
|
|
752
|
+
const aktive = await anwendungsfaelle.aktiveHarnesses();
|
|
753
|
+
const anzeigeNamen = { 'claude-code': 'Claude Code', hermes: 'Hermes', agents: 'Agents' };
|
|
754
|
+
for (const { harness, ebenen } of anwendungsfaelle.mcpEbenen()) {
|
|
755
|
+
if (!aktive.includes(harness) || ebenen.includes('arbeitsverzeichnis'))
|
|
756
|
+
continue;
|
|
757
|
+
const home = ebenen.includes('home');
|
|
758
|
+
const profil = ebenen.includes('profil');
|
|
759
|
+
if (!home && !profil)
|
|
760
|
+
continue;
|
|
761
|
+
const wo = home && profil ? 'im Home oder in einem Profil' : home ? 'im Home' : 'in einem Profil';
|
|
762
|
+
const orte = [...(home ? ['--ort home'] : []), ...(profil ? ['--ort profil:<name>'] : [])].join(' oder ');
|
|
763
|
+
console.log(`${anzeigeNamen[harness] ?? harness} liest MCP-Server nicht pro Ordner, nur ${wo} (${orte}).`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
742
766
|
return 0;
|
|
743
767
|
}
|
|
744
768
|
if (unterkommando !== 'add' && unterkommando !== 'remove' && unterkommando !== 'migrate')
|
|
@@ -1320,7 +1344,9 @@ function istModellFehler(fehler) {
|
|
|
1320
1344
|
*/
|
|
1321
1345
|
function naechsterSchritt(fehler) {
|
|
1322
1346
|
if (fehler instanceof SkillMehrdeutig)
|
|
1323
|
-
return
|
|
1347
|
+
return fehler.nurEineQuelle
|
|
1348
|
+
? `Fassung wählen: hmgr link ${fehler.name} --pfad <einer der genannten Pfade>`
|
|
1349
|
+
: `Quelle eingrenzen: hmgr link ${fehler.name} --quelle <teilstring einer der genannten Quellen>`;
|
|
1324
1350
|
if (fehler instanceof SkillUnbekannt)
|
|
1325
1351
|
return 'Erst die Quelle hinzufügen: hmgr source add <url|pfad>. hmgr source list zeigt die bekannten Quellen.';
|
|
1326
1352
|
if (fehler instanceof NameBelegt)
|
package/dist/gui/main.js
CHANGED
|
@@ -228,6 +228,7 @@ function registriereIpc() {
|
|
|
228
228
|
...(skill.eigenerKontext === undefined ? {} : { eigenerKontext: skill.eigenerKontext }),
|
|
229
229
|
...(skill.argumentHinweis === undefined ? {} : { argumentHinweis: skill.argumentHinweis }),
|
|
230
230
|
...(skill.geaendert === undefined ? {} : { geaendert: skill.geaendert }),
|
|
231
|
+
...(skill.jeHarness === undefined ? {} : { jeHarness: skill.jeHarness }),
|
|
231
232
|
quelleText: quelleAlsText(eintrag.quelle),
|
|
232
233
|
quelleName: quelleName(eintrag.quelle),
|
|
233
234
|
quelleSchluessel: quelleSchluessel(eintrag.quelle),
|
|
@@ -463,6 +464,7 @@ function registriereIpc() {
|
|
|
463
464
|
}));
|
|
464
465
|
// Plätze für den Einhängen-Dialog (F42): die Fähigkeit hängt am Adapter, nicht am Namen (E16),
|
|
465
466
|
// deshalb kommt die Liste unverändert aus dem Kern statt aus einer Harness-Fallunterscheidung hier.
|
|
467
|
+
ipcMain.handle('hmgr:mcpEbenen', async () => auffangen(async () => anwendungsfaelle.mcpEbenen()));
|
|
466
468
|
ipcMain.handle('hmgr:mcpPlaetze', async () => auffangen(async () => anwendungsfaelle.mcpPlaetze()));
|
|
467
469
|
ipcMain.handle('hmgr:mcpEinhaengen', async (_event, args) => auffangen(async () => mcpWirkungSerialisieren(await anwendungsfaelle.mcpEinhaengen({ name: args.name, ziel: zielAusIpc(args.harness, args.ort) }))));
|
|
468
470
|
ipcMain.handle('hmgr:mcpAushaengen', async (_event, args) => auffangen(async () => mcpWirkungSerialisieren(await anwendungsfaelle.mcpAushaengen({ name: args.name, ziel: zielAusIpc(args.harness, args.ort) }))));
|
|
@@ -886,6 +888,8 @@ function fensterOeffnen() {
|
|
|
886
888
|
var dskBundleBoxSichtbar = false;
|
|
887
889
|
var dskBundleText = null;
|
|
888
890
|
var dskBundleZuVorher = null, dskBundleKoerperText = null;
|
|
891
|
+
// E121: ein Name mit mehreren Fassungen in der Quelle steht einmal, mit Vermerk statt Pfad.
|
|
892
|
+
var dskMehrfachAnzahl = null, dskMehrfachText = null;
|
|
889
893
|
// Aufklappbereich je Zeile (E92): zu vor dem Klick, danach sichtbar mit der ungekürzten
|
|
890
894
|
// Beschreibung; die Checkbox bleibt unberührt, der Dialog offen.
|
|
891
895
|
var dskKoerperVorher = null;
|
|
@@ -920,6 +924,10 @@ function fensterOeffnen() {
|
|
|
920
924
|
dskAufklapp?.click();
|
|
921
925
|
dskKoerper?.querySelector('p')?.click();
|
|
922
926
|
dskAufklappFreiAngehakt = dskEingabe?.checked ?? null;
|
|
927
|
+
var dskMehrfach = Array.from(document.querySelectorAll('#dsk-liste .wahlzeile'))
|
|
928
|
+
.filter((z) => z.querySelector('b')?.textContent === 'zmehrfach');
|
|
929
|
+
dskMehrfachAnzahl = dskMehrfach.length;
|
|
930
|
+
dskMehrfachText = dskMehrfach[0]?.querySelector('.mono')?.textContent ?? null;
|
|
923
931
|
dskBundleBoxSichtbar = document.getElementById('dsk-bundlebox')?.hidden === false;
|
|
924
932
|
dskBundleText = document.querySelector('#dsk-bundles .wahlzeile')?.textContent
|
|
925
933
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
@@ -990,7 +998,7 @@ function fensterOeffnen() {
|
|
|
990
998
|
streifenHarnesse = streifen[0]?.querySelector('.zeilenzeichen')?.getAttribute('aria-label') ?? null;
|
|
991
999
|
streifenText = streifen[0]?.querySelector('.bundlekopf')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
992
1000
|
alleGruppeZeilen = Array.from(streifen[0]?.querySelectorAll('.linkliste li .name') ?? []).map(nurName).join(',');
|
|
993
|
-
alleTabelleZeilen = Array.from(streifenKarte?.querySelectorAll('.linkliste li') ?? [])
|
|
1001
|
+
alleTabelleZeilen = Array.from(streifenKarte?.querySelectorAll('.linkliste:not(.mcpzeilen) li') ?? [])
|
|
994
1002
|
.filter((li) => li.closest('.bundlegruppe') === null && li.querySelector('.name'))
|
|
995
1003
|
.map((li) => nurName(li.querySelector('.name'))).join(',');
|
|
996
1004
|
Array.from(streifen[0]?.querySelectorAll('.bundlekopf button') ?? [])
|
|
@@ -1067,7 +1075,7 @@ function fensterOeffnen() {
|
|
|
1067
1075
|
// Seit E64 stehen im MCP-Abschnitt zwei Knopfarten nebeneinander (Entfernen und
|
|
1068
1076
|
// Übernehmen), deshalb wird hier nicht mehr der erste Knopf genommen, sondern der
|
|
1069
1077
|
// Entfernen-Knopf an seinem aria-label erkannt.
|
|
1070
|
-
var mcpZeilenKnoepfe = Array.from(document.querySelectorAll('#zielliste .
|
|
1078
|
+
var mcpZeilenKnoepfe = Array.from(document.querySelectorAll('#zielliste .mcpzeilen button'));
|
|
1071
1079
|
try {
|
|
1072
1080
|
var mcpAusKnopf = mcpZeilenKnoepfe.find(function (b) {
|
|
1073
1081
|
return (b.getAttribute('aria-label') || '').indexOf('aushängen') > -1;
|
|
@@ -1119,13 +1127,20 @@ function fensterOeffnen() {
|
|
|
1119
1127
|
var hermesKarte = karteMitKopftext('Hermes · Home');
|
|
1120
1128
|
var hermesMcpText = hermesKarte?.querySelector('.mcpkarteabschnitt')?.textContent
|
|
1121
1129
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
1122
|
-
var hermesMcpZeilenKnopfZahl = hermesKarte?.querySelectorAll('.
|
|
1130
|
+
var hermesMcpZeilenKnopfZahl = hermesKarte?.querySelectorAll('.mcpzeilen button').length ?? 0;
|
|
1123
1131
|
var hermesMcpKnopfVorhanden = mcpHinzuKnopfAn(hermesKarte) !== undefined;
|
|
1124
1132
|
var kundeXKarte = karteMitKopftext('kunde-x');
|
|
1125
1133
|
var kundeXMcpText = kundeXKarte?.querySelector('.mcpkarteabschnitt')?.textContent
|
|
1126
1134
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
1127
|
-
var kundeXMcpZeilenKnopfZahl = kundeXKarte?.querySelectorAll('.
|
|
1135
|
+
var kundeXMcpZeilenKnopfZahl = kundeXKarte?.querySelectorAll('.mcpzeilen button').length ?? 0;
|
|
1128
1136
|
var kundeXMcpKnopfVorhanden = mcpHinzuKnopfAn(kundeXKarte) !== undefined;
|
|
1137
|
+
// E120: Tabellenkopf des MCP-Abschnitts und die Zeile mail (Katalog, unter „Alle").
|
|
1138
|
+
var kundeXMcpKopf = Array.from(kundeXKarte?.querySelectorAll('.mcpkarteabschnitt .linkkopf .etikett') ?? [])
|
|
1139
|
+
.map((x) => x.textContent).join('|');
|
|
1140
|
+
var kundeXMailZeile = Array.from(kundeXKarte?.querySelectorAll('.mcpzeilen li') ?? [])
|
|
1141
|
+
.find((li) => (li.querySelector('.name')?.textContent || '') === 'mail');
|
|
1142
|
+
var kundeXMailQuelle = kundeXMailZeile?.querySelector('.quelle')?.textContent ?? null;
|
|
1143
|
+
var kundeXMailZeichenTitel = kundeXMailZeile?.querySelector('.zeilenzeichen')?.getAttribute('title') ?? null;
|
|
1129
1144
|
// Kartenkopf-Zahl (E71): der Kopf zählt Skills, gebündelt wie der Körper zeilt. An der
|
|
1130
1145
|
// Verzeichnis-Karte kunde-x hängen dieselben zwei Skills an zwei Harnesses, eine Summe
|
|
1131
1146
|
// über die Abschnitte ergäbe hier 4.
|
|
@@ -1283,6 +1298,8 @@ function fensterOeffnen() {
|
|
|
1283
1298
|
katalogDetails,
|
|
1284
1299
|
katalogMarkenWarnend,
|
|
1285
1300
|
dskMarken,
|
|
1301
|
+
dskMehrfachAnzahl,
|
|
1302
|
+
dskMehrfachText,
|
|
1286
1303
|
dskKoerperVorher,
|
|
1287
1304
|
dskKoerperNachher,
|
|
1288
1305
|
dskKoerperText,
|
|
@@ -1364,6 +1381,9 @@ function fensterOeffnen() {
|
|
|
1364
1381
|
kundeXMcpText,
|
|
1365
1382
|
kundeXMcpZeilenKnopfZahl,
|
|
1366
1383
|
kundeXMcpKnopfVorhanden,
|
|
1384
|
+
kundeXMcpKopf,
|
|
1385
|
+
kundeXMailQuelle,
|
|
1386
|
+
kundeXMailZeichenTitel,
|
|
1367
1387
|
kundeXKopfZahl,
|
|
1368
1388
|
hermesKopfZahl,
|
|
1369
1389
|
hermesWarnzahl,
|
package/dist/gui/preload.cjs
CHANGED
|
@@ -100,6 +100,7 @@ contextBridge.exposeInMainWorld('hmgr', {
|
|
|
100
100
|
mcpUebersicht: () => ipcRenderer.invoke('hmgr:mcpUebersicht'),
|
|
101
101
|
/** Plätze für den Einhängen-Dialog (F42): kommt aus dem Kern, hängt an der Fähigkeit (E16). */
|
|
102
102
|
mcpPlaetze: () => ipcRenderer.invoke('hmgr:mcpPlaetze'),
|
|
103
|
+
mcpEbenen: () => ipcRenderer.invoke('hmgr:mcpEbenen'),
|
|
103
104
|
mcpEinhaengen: (args) => ipcRenderer.invoke('hmgr:mcpEinhaengen', args),
|
|
104
105
|
mcpAushaengen: (args) => ipcRenderer.invoke('hmgr:mcpAushaengen', args),
|
|
105
106
|
/** Übernehmen aus der geteilten .mcp.json in den privaten Scope (E64), nach Bestätigung im Renderer. */
|
|
@@ -339,6 +339,8 @@
|
|
|
339
339
|
// Kern (F42, E16): Grundlage für den MCP-Abschnitt und den „MCP hinzufügen …"-Knopf an den
|
|
340
340
|
// Ziel-Karten — nie am Harness-Namen entschieden, sondern an dieser Liste.
|
|
341
341
|
var MCP_PLAETZE = [];
|
|
342
|
+
// MCP-Ebenen je Harness (E120), aus der Fähigkeit am Adapter: Grundlage der Tooltip-Erklärung.
|
|
343
|
+
var MCP_EBENEN = [];
|
|
342
344
|
var quellenFilter = 'alle';
|
|
343
345
|
// Sortierung der Skills-Rubrik (E79, Richtung E81): hält für die Sitzung wie quellenFilter,
|
|
344
346
|
// übersteht also ladeAlles. SKILLS selbst bleibt unsortiert nach Datum, damit li.dataset.index
|
|
@@ -473,6 +475,7 @@
|
|
|
473
475
|
rufe(function () { return window.hmgr.mcpPlaetze(); }),
|
|
474
476
|
rufe(function () { return window.hmgr.bundles(); }),
|
|
475
477
|
rufe(function () { return window.hmgr.harnessHeime(); }),
|
|
478
|
+
rufe(function () { return window.hmgr.mcpEbenen(); }),
|
|
476
479
|
]);
|
|
477
480
|
// Alter Stand der Verzeichnis-Pfade, vor dem Überschreiben unten (E84 Teil 1): zielKarten()
|
|
478
481
|
// liest hier noch ZIELE/AKTIVE_HARNESSE von vor diesem Lauf. null heißt „kein Vergleich", der
|
|
@@ -495,6 +498,7 @@
|
|
|
495
498
|
BUNDLES.sort(function (a, b) { return a.name.localeCompare(b.name, 'de') || a.quelleText.localeCompare(b.quelleText, 'de'); });
|
|
496
499
|
BUNDLES_KAPUTT = ergebnisse[9].kaputte;
|
|
497
500
|
HARNESS_HEIME = ergebnisse[10] || [];
|
|
501
|
+
MCP_EBENEN = ergebnisse[11] || [];
|
|
498
502
|
STANDARD_REF = {};
|
|
499
503
|
SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
|
|
500
504
|
|
|
@@ -813,7 +817,7 @@
|
|
|
813
817
|
/** Skill zu einem Mitglied einer Definition aus dem Skill-Katalog (E114 L1): gleiche Quelle, gleicher
|
|
814
818
|
Pfad. null, wenn die Quelle (noch) nicht eingerichtet oder nicht lesbar ist. */
|
|
815
819
|
function bundleMitgliedSkill(m) {
|
|
816
|
-
return SKILLS.filter(function (s) { return s.quelleSchluessel === m.quelleSchluessel && s.pfad === m.pfad; })[0] || null;
|
|
820
|
+
return SKILLS.filter(function (s) { return s.quelleSchluessel === m.quelleSchluessel && (s.pfad === m.pfad || (s.jeHarness || []).indexOf(m.pfad) > -1); })[0] || null;
|
|
817
821
|
}
|
|
818
822
|
|
|
819
823
|
/** Skill eines nicht verlinkten Mitglieds über seinen Namen, gesucht unter den Mitgliedern der
|
|
@@ -1112,54 +1116,115 @@
|
|
|
1112
1116
|
return zeilen;
|
|
1113
1117
|
}
|
|
1114
1118
|
|
|
1115
|
-
/**
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1119
|
+
/** Quelle-Spalte und Beschreibung einer MCP-Zeile (E120, F142). */
|
|
1120
|
+
function mcpQuelleText(row) {
|
|
1121
|
+
if (row.art === 'katalog') return 'Katalog';
|
|
1122
|
+
if (row.art === 'fremd') return 'nicht im Katalog';
|
|
1123
|
+
return '.mcp.json des Projekts';
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/** Tooltip an den Harness-Zeichen einer MCP-Zeile unter „Alle" (E120): wo der Server hier hängt und,
|
|
1127
|
+
je weiterem aktivem Harness des Orts, warum nicht. Der Grund kommt aus den MCP-Ebenen der
|
|
1128
|
+
Fähigkeit (MCP_EBENEN, E16), nie aus dem Harness-Namen. Ein Harness, der den Server hier haben
|
|
1129
|
+
könnte (Ebene arbeitsverzeichnis), bekommt keinen Satz: ein Grund wäre geraten (etwa fehlende
|
|
1130
|
+
CLI, E103). */
|
|
1131
|
+
function mcpZeichenTitel(name, hatHarnesse, teile) {
|
|
1132
|
+
var saetze = ['Hier hängt ' + name + ' für ' + hatHarnesse.map(harnessName).join(', ') + '.'];
|
|
1133
|
+
var gesehen = {};
|
|
1134
|
+
teile.forEach(function (t) {
|
|
1135
|
+
var h = t.ziel.harness;
|
|
1136
|
+
if (gesehen[h] || hatHarnesse.indexOf(h) > -1 || !harnessAktiv(h)) return;
|
|
1137
|
+
gesehen[h] = true;
|
|
1138
|
+
var f = MCP_EBENEN.find(function (x) { return x.harness === h; });
|
|
1139
|
+
if (!f) return;
|
|
1140
|
+
if (f.ebenen.indexOf('arbeitsverzeichnis') > -1) return;
|
|
1141
|
+
if (f.ebenen.length === 0) {
|
|
1142
|
+
saetze.push(harnessName(h) + ' kennt keine MCP-Server.');
|
|
1143
|
+
} else {
|
|
1144
|
+
saetze.push(harnessName(h) + ' liest MCP-Server nicht pro Ordner, nur im Home'
|
|
1145
|
+
+ (f.ebenen.indexOf('profil') > -1 ? ' oder in einem Profil' : '') + '.');
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
return saetze.join(' ');
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/** Der MCP-Abschnitt einer Ziel-Karte (E61-GUI-Nachtrag, E120): eine Tabelle mit denselben Spalten
|
|
1152
|
+
wie die Skill-Tabelle darüber. `ziele` ist ein Ziel (Harness-Chip) oder mehrere (Chip „Alle",
|
|
1153
|
+
`teile` gesetzt: dann stehen die Harness-Zeichen vor dem Namen). Nur, wenn dort etwas
|
|
1154
|
+
eingehängt ist oder der „hinzufügen"-Knopf ohnehin sichtbar wäre (mcpKannAn). Sperrnotiz (E91)
|
|
1155
|
+
und „Noch nichts eingehängt." bleiben eine Zeile über die Breite. */
|
|
1156
|
+
function mcpAbschnitt(ziele, teile) {
|
|
1157
|
+
var alle = teile !== undefined;
|
|
1158
|
+
if (!Array.isArray(ziele)) ziele = [ziele];
|
|
1159
|
+
var notizen = [];
|
|
1160
|
+
var zeilen = [];
|
|
1161
|
+
var kannAn = false;
|
|
1162
|
+
ziele.forEach(function (ziel) {
|
|
1163
|
+
var wurzel = mcpGitWurzel(ziel);
|
|
1164
|
+
if (wurzel !== null) { notizen.push(mcpWurzelNotiz(wurzel)); return; }
|
|
1165
|
+
if (mcpKannAn(ziel)) kannAn = true;
|
|
1166
|
+
mcpZeilenFuerZiel(ziel).forEach(function (row) { row.ziel = ziel; zeilen.push(row); });
|
|
1167
|
+
});
|
|
1168
|
+
if (zeilen.length === 0 && notizen.length === 0 && !kannAn) return null;
|
|
1169
|
+
zeilen.sort(function (a, b) {
|
|
1170
|
+
return a.name.localeCompare(b.name, 'de') || harnessName(a.ziel.harness).localeCompare(harnessName(b.ziel.harness), 'de');
|
|
1171
|
+
});
|
|
1127
1172
|
var abschnitt = e('div', 'mcpkarteabschnitt');
|
|
1128
|
-
abschnitt.appendChild(e('span', 'etikett', 'MCP'));
|
|
1129
1173
|
if (zeilen.length === 0) {
|
|
1130
|
-
abschnitt.appendChild(e('
|
|
1174
|
+
abschnitt.appendChild(e('span', 'etikett', 'MCP'));
|
|
1175
|
+
if (notizen.length === 0) abschnitt.appendChild(e('p', 'ohne mcpkarteleer', 'Noch nichts eingehängt.'));
|
|
1176
|
+
notizen.forEach(function (n) { abschnitt.appendChild(e('p', 'ohne mcpkarteleer', n)); });
|
|
1131
1177
|
return abschnitt;
|
|
1132
1178
|
}
|
|
1133
|
-
var
|
|
1179
|
+
var kopf = e('div', 'linkkopf');
|
|
1180
|
+
kopf.appendChild(e('span'));
|
|
1181
|
+
kopf.appendChild(e('span', 'etikett', 'MCP'));
|
|
1182
|
+
kopf.appendChild(e('span', 'etikett', 'Beschreibung'));
|
|
1183
|
+
kopf.appendChild(e('span', 'etikett', 'Quelle'));
|
|
1184
|
+
kopf.appendChild(e('span'));
|
|
1185
|
+
abschnitt.appendChild(kopf);
|
|
1186
|
+
var ul = e('ul', 'linkliste mcpzeilen');
|
|
1134
1187
|
zeilen.forEach(function (row) {
|
|
1188
|
+
var ziel = row.ziel;
|
|
1135
1189
|
var li = e('li');
|
|
1136
|
-
li.appendChild(e('span', '
|
|
1190
|
+
li.appendChild(e('span', 'repo'));
|
|
1191
|
+
var nameSpan = e('span', 'name');
|
|
1192
|
+
if (alle) {
|
|
1193
|
+
var hat = [];
|
|
1194
|
+
zeilen.forEach(function (r) {
|
|
1195
|
+
if (r.name === row.name && hat.indexOf(r.ziel.harness) === -1) hat.push(r.ziel.harness);
|
|
1196
|
+
});
|
|
1197
|
+
hat.sort(function (a, b) { return harnessName(a).localeCompare(harnessName(b), 'de'); });
|
|
1198
|
+
var zeichen = harnesseZeichenSammel([ziel.harness]);
|
|
1199
|
+
zeichen.title = mcpZeichenTitel(row.name, hat, teile);
|
|
1200
|
+
nameSpan.appendChild(zeichen);
|
|
1201
|
+
}
|
|
1202
|
+
nameSpan.appendChild(document.createTextNode(row.name));
|
|
1203
|
+
li.appendChild(nameSpan);
|
|
1204
|
+
var wasFeld = e('span', 'wasfeld');
|
|
1205
|
+
if (row.art === 'katalog' || row.art === 'geteilt') wasFeld.appendChild(e('span', 'was', row.beschreibung || ''));
|
|
1206
|
+
else if (row.art === 'fremd-geteilt') wasFeld.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
|
|
1207
|
+
li.appendChild(wasFeld);
|
|
1208
|
+
li.appendChild(e('span', 'quelle', mcpQuelleText(row)));
|
|
1137
1209
|
if (row.art === 'katalog') {
|
|
1138
|
-
li.appendChild(e('span', 'was', row.beschreibung || ''));
|
|
1139
1210
|
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
1140
1211
|
weg.type = 'button';
|
|
1141
1212
|
weg.setAttribute('aria-label', row.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen');
|
|
1142
1213
|
weg.addEventListener('click', function () { frageMcpAus(row.eintrag, ziel); });
|
|
1143
1214
|
li.appendChild(weg);
|
|
1144
1215
|
} else if (row.art === 'geteilt' || row.art === 'fremd-geteilt') {
|
|
1145
|
-
// E64: der Vermerk nennt die Bedeutung („steht im Projekt, nicht bei dir"), nicht mehr bloß
|
|
1146
|
-
// den Dateinamen — und daneben steht der Weg, das zu ändern.
|
|
1147
|
-
li.appendChild(row.art === 'geteilt'
|
|
1148
|
-
? e('span', 'was', row.beschreibung || '')
|
|
1149
|
-
: e('span', 'was fehlt', 'nicht im Katalog'));
|
|
1150
|
-
li.appendChild(e('span', 'geteiltvermerk', 'in der .mcp.json des Projekts'));
|
|
1151
1216
|
var her = e('button', 'knopf leise klein', 'Übernehmen …');
|
|
1152
1217
|
her.type = 'button';
|
|
1153
1218
|
her.setAttribute('aria-label', row.name + ' aus der .mcp.json des Projekts in deine private Konfiguration übernehmen');
|
|
1154
1219
|
her.addEventListener('click', function () { frageMcpUebernahme(row.name, ziel, row.datei); });
|
|
1155
1220
|
li.appendChild(her);
|
|
1156
1221
|
} else {
|
|
1157
|
-
li.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
|
|
1158
1222
|
li.appendChild(e('span'));
|
|
1159
1223
|
}
|
|
1160
1224
|
ul.appendChild(li);
|
|
1161
1225
|
});
|
|
1162
1226
|
abschnitt.appendChild(ul);
|
|
1227
|
+
notizen.forEach(function (n) { abschnitt.appendChild(e('p', 'ohne mcpkarteleer', n)); });
|
|
1163
1228
|
return abschnitt;
|
|
1164
1229
|
}
|
|
1165
1230
|
|
|
@@ -1357,10 +1422,8 @@
|
|
|
1357
1422
|
if (mcpKnopfLeer) leer.appendChild(mcpKnopfLeer);
|
|
1358
1423
|
});
|
|
1359
1424
|
teil.appendChild(leer);
|
|
1360
|
-
verzMcpZiele
|
|
1361
|
-
|
|
1362
|
-
if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
|
|
1363
|
-
});
|
|
1425
|
+
var mcpAbschnLeer = mcpAbschnitt(verzMcpZiele, teile);
|
|
1426
|
+
if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
|
|
1364
1427
|
return teil;
|
|
1365
1428
|
}
|
|
1366
1429
|
|
|
@@ -1494,10 +1557,8 @@
|
|
|
1494
1557
|
|
|
1495
1558
|
// MCP-Abschnitt (E61-GUI-Nachtrag): auf Kartenebene unter „Alle", wie unter dem Harness-Chip,
|
|
1496
1559
|
// der an diesem Pfad MCP kann.
|
|
1497
|
-
verzMcpZiele
|
|
1498
|
-
|
|
1499
|
-
if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
|
|
1500
|
-
});
|
|
1560
|
+
var mcpAbschnAlle = mcpAbschnitt(verzMcpZiele, teile);
|
|
1561
|
+
if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
|
|
1501
1562
|
|
|
1502
1563
|
var fuss = e('div', 'zielfuss');
|
|
1503
1564
|
fuss.appendChild(alleSkillsKnopf(pfad, teile));
|
|
@@ -2274,7 +2335,8 @@
|
|
|
2274
2335
|
// Skill-Ordner berührt. Nicht der Pull-Zeitpunkt — der steht als „Zuletzt geprüft" in der
|
|
2275
2336
|
// Update-Ansicht und wäre für alle Skills einer Quelle derselbe.
|
|
2276
2337
|
var geaendert = datumDeutsch(skill.geaendert);
|
|
2277
|
-
var nachRef = ' · Ref ' + skill.ref + (geaendert ? ' · geändert ' + geaendert : '')
|
|
2338
|
+
var nachRef = ' · Ref ' + skill.ref + (geaendert ? ' · geändert ' + geaendert : '')
|
|
2339
|
+
+ (skill.jeHarness ? ' · ' + JE_HARNESS_VERMERK : '');
|
|
2278
2340
|
if (herkunftQuelle) {
|
|
2279
2341
|
herkunft.appendChild(quelleLinkElement(herkunftQuelle));
|
|
2280
2342
|
herkunft.appendChild(e('b', null, nachRef));
|
|
@@ -2341,7 +2403,8 @@
|
|
|
2341
2403
|
var sichtbar = 0;
|
|
2342
2404
|
document.querySelectorAll('#katalog .zeile').forEach(function (li) {
|
|
2343
2405
|
var skill = SKILLS[+li.dataset.index];
|
|
2344
|
-
var passtSuche = !suche || suchform(skill.name).indexOf(suche) > -1 || suchform(skill.pfad).indexOf(suche) > -1
|
|
2406
|
+
var passtSuche = !suche || suchform(skill.name).indexOf(suche) > -1 || suchform(skill.pfad).indexOf(suche) > -1
|
|
2407
|
+
|| (skill.jeHarness || []).some(function (p) { return suchform(p).indexOf(suche) > -1; });
|
|
2345
2408
|
var passtFilter = quellenFilter === 'alle' || skill.quelleSchluessel === quellenFilter;
|
|
2346
2409
|
var an = passtSuche && passtFilter;
|
|
2347
2410
|
li.hidden = !an;
|
|
@@ -2581,9 +2644,13 @@
|
|
|
2581
2644
|
/** Quelle · [Pfad ·] Ref für eine Skill-Karte im „Skills hinzufügen"-Dialog (F106): der Pfad
|
|
2582
2645
|
unterscheidet Skills, die unter demselben Namen mehrfach im selben Repo liegen (z. B. je
|
|
2583
2646
|
Harness ein eigener Ordner). Ein Wurzel-Skill hat pfad === '' und zeigt kein Pfad-Segment. */
|
|
2647
|
+
var JE_HARNESS_VERMERK = 'je Harness eigene Fassung';
|
|
2648
|
+
|
|
2584
2649
|
function skillQuelleBeschriftung(skill) {
|
|
2585
2650
|
var teile = [skill.quelleName];
|
|
2586
|
-
|
|
2651
|
+
// Mehrfach in der Quelle, je Harness eine eigene Fassung (E121): der Kern löst beim Verlinken auf.
|
|
2652
|
+
if (skill.jeHarness) teile.push(JE_HARNESS_VERMERK);
|
|
2653
|
+
else if (skill.pfad) teile.push(skill.pfad);
|
|
2587
2654
|
teile.push('Ref ' + skill.ref);
|
|
2588
2655
|
return teile.join(' · ');
|
|
2589
2656
|
}
|
|
@@ -771,21 +771,11 @@ dialog::backdrop{background:rgba(9,45,37,.62)}
|
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
/* MCP an der Ziel-Karte (E61-GUI-Nachtrag), Gegenrichtung zur MCP-Ansicht (F46: die einzige Stelle,
|
|
774
|
-
die noch zeigt, wo ein Server hängt): eigener Abschnitt unter der Skill-Link-Tabelle,
|
|
775
|
-
|
|
774
|
+
die noch zeigt, wo ein Server hängt): eigener Abschnitt unter der Skill-Link-Tabelle, seit E120 mit
|
|
775
|
+
derselben Tabelle (linkkopf/linkliste) wie die Skills; flach in der Karte statt in einem eigenen Kasten. */
|
|
776
776
|
.mcpkarteabschnitt{margin-top:16px;padding-top:14px;border-top:1px solid var(--strich)}
|
|
777
777
|
.mcpkarteabschnitt > .etikett{display:block;margin-bottom:10px}
|
|
778
778
|
.mcpkarteleer{margin:0;color:var(--tinte-leise);font-style:italic}
|
|
779
|
-
.mcpkartezeilen{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
|
|
780
|
-
.mcpkartezeilen li{display:flex;flex-wrap:wrap;align-items:center;gap:5px 14px}
|
|
781
|
-
.mcpkartezeilen .name{font-family:var(--mono);font-size:16px;letter-spacing:-.012em;color:var(--tinte)}
|
|
782
|
-
.mcpkartezeilen .was{font-size:16px;color:var(--tinte-weich)}
|
|
783
|
-
.mcpkartezeilen .was.fehlt{color:var(--tinte-leise);font-style:italic}
|
|
784
|
-
.mcpkartezeilen .knopf{margin-left:auto}
|
|
785
|
-
.mcpkartezeilen .kein-weg{margin-left:auto;font-size:16px;color:var(--tinte-leise);text-align:right}
|
|
786
|
-
/* Wo ein Eintrag steht (E64): kein margin-left:auto, sonst teilte er sich den freien Platz mit dem
|
|
787
|
-
Übernehmen-Knopf und stünde in der Mitte statt beim Text. */
|
|
788
|
-
.mcpkartezeilen .geteiltvermerk{font-size:16px;color:var(--tinte-leise)}
|
|
789
779
|
/* Bundle-Gruppe an der Ziel-Karte (E69, F68): eine Einheit über ihren Skill-Zeilen, sichtbar
|
|
790
780
|
zusammengefasst durch eine linke Kante statt durch einen eigenen Kasten — die Zeilen darin sind
|
|
791
781
|
dieselbe Tabelle wie darunter und sollen mit ihr fluchten. */
|
|
@@ -578,6 +578,15 @@ export class Anwendungsfaelle {
|
|
|
578
578
|
}
|
|
579
579
|
return ziele;
|
|
580
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Die MCP-Ebenen je Harness-Typ (E120), aus der Fähigkeit am Adapter, nie am Namen (E16): eine
|
|
583
|
+
* leere Liste heißt, der Harness kennt MCP-Server gar nicht (agents). Grundlage für die Erklärung,
|
|
584
|
+
* warum ein Server an einem Ort für einen Harness fehlt (GUI-Tooltip, Hinweis von `mcp list`).
|
|
585
|
+
* Unabhängig von Installation und Aktivierung; das entscheidet der Aufrufer.
|
|
586
|
+
*/
|
|
587
|
+
mcpEbenen() {
|
|
588
|
+
return this.p.harnesses.map((h) => ({ harness: h.name, ebenen: kannMcp(h) ? h.mcp.ebenen() : [] }));
|
|
589
|
+
}
|
|
581
590
|
/**
|
|
582
591
|
* Die Plätze für den Einhängen-Dialog der GUI (E61-Folgerunde, F42): dieselbe Liste wie in
|
|
583
592
|
* `mcpUebersicht`, aber roh, ohne den Abgleich mit Katalog-Einträgen. Die Fähigkeit hängt am
|
|
@@ -861,10 +870,10 @@ export class Anwendungsfaelle {
|
|
|
861
870
|
ergebnis.push({
|
|
862
871
|
quelle,
|
|
863
872
|
ref,
|
|
864
|
-
skills: skills.map((skill) => {
|
|
873
|
+
skills: this.fassungenZusammenfassen(skills.map((skill) => {
|
|
865
874
|
const geaendert = daten.get(skill.pfad);
|
|
866
875
|
return geaendert === undefined ? skill : { ...skill, geaendert };
|
|
867
|
-
}),
|
|
876
|
+
})),
|
|
868
877
|
});
|
|
869
878
|
}
|
|
870
879
|
catch (fehler) {
|
|
@@ -873,6 +882,39 @@ export class Anwendungsfaelle {
|
|
|
873
882
|
}
|
|
874
883
|
return ergebnis;
|
|
875
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Katalog-Zusammenfassung (E121): Ein Name, der in einer Quelle mehrfach vorkommt und mindestens
|
|
887
|
+
* einmal unmittelbar in einem Harness-Skill-Ordner liegt, erscheint einmal, mit `jeHarness`. Als
|
|
888
|
+
* Vertreter dient die Fassung im ersten passenden Harness-Ordner (Reihenfolge der Harnesses),
|
|
889
|
+
* sonst die erste. Alle anderen Namen bleiben unverändert, ebenso die Reihenfolge.
|
|
890
|
+
*/
|
|
891
|
+
fassungenZusammenfassen(skills) {
|
|
892
|
+
const ordner = this.p.harnesses.map((h) => `${h.quellOrdner()}/`);
|
|
893
|
+
const direkt = (s, o) => s.pfad.startsWith(o) && !s.pfad.slice(o.length).includes('/');
|
|
894
|
+
const gruppen = new Map();
|
|
895
|
+
for (const s of skills)
|
|
896
|
+
gruppen.set(s.name, [...(gruppen.get(s.name) ?? []), s]);
|
|
897
|
+
const ergebnis = [];
|
|
898
|
+
for (const s of skills) {
|
|
899
|
+
const gruppe = gruppen.get(s.name);
|
|
900
|
+
if (gruppe.length < 2 || !gruppe.some((g) => ordner.some((o) => direkt(g, o)))) {
|
|
901
|
+
ergebnis.push(s);
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (gruppe[0] !== s)
|
|
905
|
+
continue; // Vertreter steht an der Stelle der ersten Fassung
|
|
906
|
+
let vertreter = gruppe[0];
|
|
907
|
+
for (const o of ordner) {
|
|
908
|
+
const treffer = gruppe.find((g) => direkt(g, o));
|
|
909
|
+
if (treffer !== undefined) {
|
|
910
|
+
vertreter = treffer;
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
ergebnis.push({ ...vertreter, jeHarness: gruppe.map((g) => g.pfad) });
|
|
915
|
+
}
|
|
916
|
+
return ergebnis;
|
|
917
|
+
}
|
|
876
918
|
/** Alle Marketplaces der bekannten Quellen, für `hmgr plugin list`. */
|
|
877
919
|
async marketplaces() {
|
|
878
920
|
const zustand = await this.p.zustand.lesen();
|
|
@@ -1566,7 +1608,7 @@ export class Anwendungsfaelle {
|
|
|
1566
1608
|
throw new Error(`Mehrere Quellen passen auf „${hinweis}", bitte eingrenzen:\n${zeilen(treffer)}`);
|
|
1567
1609
|
}
|
|
1568
1610
|
/** Skill über alle Quellen suchen. Mehrere Treffer sind nur mit Quellenhinweis erlaubt. */
|
|
1569
|
-
async skillFinden(name, quelleHinweis) {
|
|
1611
|
+
async skillFinden(name, quelleHinweis, wahl = {}) {
|
|
1570
1612
|
const zustand = await this.p.zustand.lesen();
|
|
1571
1613
|
const funde = [];
|
|
1572
1614
|
for (const quelle of zustand.quellen) {
|
|
@@ -1574,16 +1616,33 @@ export class Anwendungsfaelle {
|
|
|
1574
1616
|
continue;
|
|
1575
1617
|
const ref = await this.p.quellen.standardRef(quelle);
|
|
1576
1618
|
const skills = await this.p.quellen.skills(quelle, ref);
|
|
1577
|
-
for (const skill of skills)
|
|
1578
|
-
|
|
1579
|
-
funde.push({ quelle, ref, skill });
|
|
1619
|
+
for (const skill of Anwendungsfaelle.skillTreffer(skills, name, wahl))
|
|
1620
|
+
funde.push({ quelle, ref, skill });
|
|
1580
1621
|
}
|
|
1581
1622
|
if (funde.length === 0)
|
|
1582
1623
|
throw new SkillUnbekannt(name);
|
|
1583
1624
|
if (funde.length > 1)
|
|
1584
|
-
throw new SkillMehrdeutig(name, funde.map((f) => f.quelle));
|
|
1625
|
+
throw new SkillMehrdeutig(name, funde.map((f) => ({ quelle: f.quelle, pfad: f.skill.pfad })));
|
|
1585
1626
|
return funde[0];
|
|
1586
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Die Skills einer Quelle zu einem Namen (E121). Mit `pfad` zählt nur der Skill mit genau diesem
|
|
1630
|
+
* Pfad. Sonst gewinnt bei mehreren Fassungen die eine, die unmittelbar im Skill-Ordner des
|
|
1631
|
+
* Harness liegt (`<quellOrdner>/<ordner>`, eine Ebene); gibt es keine solche oder mehrere,
|
|
1632
|
+
* bleiben alle, und der Aufrufer meldet die Mehrdeutigkeit.
|
|
1633
|
+
*/
|
|
1634
|
+
static skillTreffer(skills, name, wahl) {
|
|
1635
|
+
const gleichnamig = skills.filter((s) => s.name === name);
|
|
1636
|
+
if (wahl.pfad !== undefined) {
|
|
1637
|
+
const pfad = wahl.pfad.replace(/^\.?\/+|\/+$/g, '');
|
|
1638
|
+
return gleichnamig.filter((s) => s.pfad === pfad);
|
|
1639
|
+
}
|
|
1640
|
+
if (gleichnamig.length < 2 || wahl.quellOrdner === undefined)
|
|
1641
|
+
return gleichnamig;
|
|
1642
|
+
const praefix = `${wahl.quellOrdner}/`;
|
|
1643
|
+
const direkt = gleichnamig.filter((s) => s.pfad.startsWith(praefix) && !s.pfad.slice(praefix.length).includes('/'));
|
|
1644
|
+
return direkt.length === 1 ? direkt : gleichnamig;
|
|
1645
|
+
}
|
|
1587
1646
|
/** Checkout anlegen oder erweitern, Ziel vorbereiten, Link setzen. Ohne Zeiger-Arbeit. */
|
|
1588
1647
|
async linkAnlegen(harness, ziel, fund, ersetzen) {
|
|
1589
1648
|
if (!harness.orte().includes(ziel.ort.art))
|
|
@@ -1627,8 +1686,12 @@ export class Anwendungsfaelle {
|
|
|
1627
1686
|
if (!harness.orte().includes(args.ziel.ort.art))
|
|
1628
1687
|
throw new OrtNichtUnterstuetzt(harness.name, args.ziel.ort.art);
|
|
1629
1688
|
const version = args.version;
|
|
1689
|
+
const wahl = {
|
|
1690
|
+
quellOrdner: harness.quellOrdner(),
|
|
1691
|
+
...(args.pfad === undefined ? {} : { pfad: args.pfad }),
|
|
1692
|
+
};
|
|
1630
1693
|
if (version === undefined) {
|
|
1631
|
-
const fund = await this.skillFinden(args.skill, args.quelle);
|
|
1694
|
+
const fund = await this.skillFinden(args.skill, args.quelle, wahl);
|
|
1632
1695
|
const ergebnis = await this.linkAusFund(harness, args.ziel, fund, args.ersetzen, args.lokal);
|
|
1633
1696
|
return Anwendungsfaelle.hinweisAnhaengen(ergebnis, await this.bundleMitgliedZurueckholen(args.ziel, fund));
|
|
1634
1697
|
}
|
|
@@ -1640,7 +1703,7 @@ export class Anwendungsfaelle {
|
|
|
1640
1703
|
// Eine Versionseinschränkung schreibt immer geteilt (siehe EinschraenkungOhneZeiger oben), das
|
|
1641
1704
|
// trifft die Sperre also ausnahmslos, solange sie steht (E57).
|
|
1642
1705
|
await this.zeigerSchreibenPruefen(args.ziel, args.lokal);
|
|
1643
|
-
const fund = await this.fundAusZeiger(args.skill, version, args.quelle);
|
|
1706
|
+
const fund = await this.fundAusZeiger(args.skill, version, args.quelle, wahl);
|
|
1644
1707
|
const link = await this.linkAnlegen(harness, args.ziel, fund, args.ersetzen);
|
|
1645
1708
|
await this.zeigerErgaenzen(zeigerPfad, fund, version);
|
|
1646
1709
|
const hinweis = await this.harnessHinweis(harness, args.ziel.ort);
|
|
@@ -1957,9 +2020,9 @@ export class Anwendungsfaelle {
|
|
|
1957
2020
|
* gilt der Text als genauer Ref-Name gegen alle Refs, damit Tags und Branches ohne
|
|
1958
2021
|
* Versions-Schema erreichbar bleiben. Nie auf den Standard-Ref ausweichen (B8).
|
|
1959
2022
|
*/
|
|
1960
|
-
async fundAusZeiger(name, einschraenkung, quelleHinweis) {
|
|
2023
|
+
async fundAusZeiger(name, einschraenkung, quelleHinweis, wahl = {}) {
|
|
1961
2024
|
if (einschraenkung === null)
|
|
1962
|
-
return await this.skillFinden(name, quelleHinweis);
|
|
2025
|
+
return await this.skillFinden(name, quelleHinweis, wahl);
|
|
1963
2026
|
const zustand = await this.p.zustand.lesen();
|
|
1964
2027
|
const quellen = zustand.quellen.filter((q) => quellePasst(q, quelleHinweis));
|
|
1965
2028
|
for (const quelle of quellen) {
|
|
@@ -1968,7 +2031,7 @@ export class Anwendungsfaelle {
|
|
|
1968
2031
|
const ref = passenderRef(tags, einschraenkung);
|
|
1969
2032
|
if (ref === null)
|
|
1970
2033
|
continue;
|
|
1971
|
-
const skill = (await this.p.quellen.skills(quelle, ref)
|
|
2034
|
+
const skill = Anwendungsfaelle.skillTreffer(await this.p.quellen.skills(quelle, ref), name, wahl)[0];
|
|
1972
2035
|
if (skill)
|
|
1973
2036
|
return { quelle, ref, skill };
|
|
1974
2037
|
}
|
|
@@ -1976,7 +2039,7 @@ export class Anwendungsfaelle {
|
|
|
1976
2039
|
const refs = await this.p.quellen.refs(quelle).catch(() => []);
|
|
1977
2040
|
if (!refs.includes(einschraenkung))
|
|
1978
2041
|
continue;
|
|
1979
|
-
const skill = (await this.p.quellen.skills(quelle, einschraenkung)
|
|
2042
|
+
const skill = Anwendungsfaelle.skillTreffer(await this.p.quellen.skills(quelle, einschraenkung), name, wahl)[0];
|
|
1980
2043
|
if (skill)
|
|
1981
2044
|
return { quelle, ref: einschraenkung, skill };
|
|
1982
2045
|
}
|
|
@@ -2053,7 +2116,7 @@ export class Anwendungsfaelle {
|
|
|
2053
2116
|
}
|
|
2054
2117
|
for (const [name, einschraenkung] of zeiger.skills) {
|
|
2055
2118
|
try {
|
|
2056
|
-
const fund = await this.fundAusZeiger(name, einschraenkung);
|
|
2119
|
+
const fund = await this.fundAusZeiger(name, einschraenkung, undefined, { quellOrdner: harness.quellOrdner() });
|
|
2057
2120
|
const gefunden = eintraege.find((e) => e.art === 'link' && e.link.name === name);
|
|
2058
2121
|
// Ein eigener Link auf einen anderen Ref weicht ab und wird umgelegt (E33). Das trifft
|
|
2059
2122
|
// die Einschränkung, die inzwischen einen höheren Tag findet.
|
package/dist/kern/modell.js
CHANGED
|
@@ -338,10 +338,24 @@ export class LinkAnlegenFehlgeschlagen extends Error {
|
|
|
338
338
|
export class SkillMehrdeutig extends Error {
|
|
339
339
|
name;
|
|
340
340
|
quellen;
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
funde;
|
|
342
|
+
/** Die Mehrdeutigkeit kommt aus einer einzigen Quelle: `--pfad` hilft, nicht `--quelle` (E121). */
|
|
343
|
+
nurEineQuelle;
|
|
344
|
+
constructor(name, funde) {
|
|
345
|
+
const quellen = [];
|
|
346
|
+
for (const f of funde)
|
|
347
|
+
if (!quellen.some((q) => quelleSchluessel(q) === quelleSchluessel(f.quelle)))
|
|
348
|
+
quellen.push(f.quelle);
|
|
349
|
+
const zeilen = funde
|
|
350
|
+
.map((f) => ` ${quelleSchluessel(f.quelle)}: ${f.pfad === '' ? '(Repo-Wurzel)' : f.pfad}`)
|
|
351
|
+
.join('\n');
|
|
352
|
+
super(quellen.length === 1
|
|
353
|
+
? `Skill „${name}" liegt in der Quelle ${quelleSchluessel(quellen[0])} mehrfach:\n${zeilen}`
|
|
354
|
+
: `Skill „${name}" liegt in mehreren Quellen:\n${zeilen}`);
|
|
343
355
|
this.name = name;
|
|
344
356
|
this.quellen = quellen;
|
|
357
|
+
this.funde = funde;
|
|
358
|
+
this.nurEineQuelle = quellen.length === 1;
|
|
345
359
|
}
|
|
346
360
|
}
|
|
347
361
|
export class SkillUnbekannt extends Error {
|