hmgr 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapter/agents.js +43 -0
- package/dist/adapter/claude-code.js +2 -1
- package/dist/adapter/hermes.js +81 -1
- package/dist/adapter/terminal.js +24 -0
- package/dist/cli/main.js +8 -3
- package/dist/kern/anwendungsfaelle.js +57 -12
- package/dist/ports/rueckfrage.js +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ status Ist-Zustand der Ziele
|
|
|
46
46
|
Optionen:
|
|
47
47
|
|
|
48
48
|
```
|
|
49
|
-
--harness <name> claude-code oder
|
|
49
|
+
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
50
50
|
--ort home|dir:<pfad>|profil:<name> Standard home
|
|
51
51
|
--quelle <teilstring> Quelle bei mehrdeutigem Skill-Namen
|
|
52
52
|
--version <spanne> Versionseinschränkung nach Composer-Regeln, nur mit Zeiger
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Das universelle Skill-Verzeichnis `.agents/skills`: ein Harness-Typ ohne Programm dahinter (E35).
|
|
2
|
+
// Home ist `~/.agents/skills`, das Arbeitsverzeichnis `<pfad>/.agents/skills`.
|
|
3
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { OrtNichtUnterstuetzt } from "../kern/modell.js";
|
|
6
|
+
import { gitignoreEintragen, istGitRepo } from "./gitignore.js";
|
|
7
|
+
const AGENTS_VERZEICHNIS = '.agents';
|
|
8
|
+
const SKILLS_ORDNER = 'skills';
|
|
9
|
+
const GITIGNORE_ZEILE = '.agents/';
|
|
10
|
+
export class Agents {
|
|
11
|
+
name = 'agents';
|
|
12
|
+
home;
|
|
13
|
+
/** `home` ist das HOME des Nutzers, das Verzeichnis liegt darunter als `.agents`. */
|
|
14
|
+
constructor(home) {
|
|
15
|
+
this.home = home;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wie bei den anderen Harness-Typen zählt das angelegte Verzeichnis. Legt es niemand an, gibt es
|
|
19
|
+
* auf diesem Rechner keinen Konsumenten dafür und `status` überspringt das Ziel.
|
|
20
|
+
*/
|
|
21
|
+
async vorhanden() {
|
|
22
|
+
return (await stat(path.join(this.home, AGENTS_VERZEICHNIS)).catch(() => undefined)) !== undefined;
|
|
23
|
+
}
|
|
24
|
+
orte() {
|
|
25
|
+
return ['home', 'arbeitsverzeichnis'];
|
|
26
|
+
}
|
|
27
|
+
zielVerzeichnis(ort) {
|
|
28
|
+
if (ort.art === 'home')
|
|
29
|
+
return path.join(this.home, AGENTS_VERZEICHNIS, SKILLS_ORDNER);
|
|
30
|
+
if (ort.art === 'arbeitsverzeichnis')
|
|
31
|
+
return path.join(ort.pfad, AGENTS_VERZEICHNIS, SKILLS_ORDNER);
|
|
32
|
+
throw new OrtNichtUnterstuetzt(this.name, ort.art);
|
|
33
|
+
}
|
|
34
|
+
async vorbereiten(ort) {
|
|
35
|
+
await mkdir(this.zielVerzeichnis(ort), { recursive: true });
|
|
36
|
+
// .agents/ darf nicht ins Repo (B7).
|
|
37
|
+
if (ort.art === 'arbeitsverzeichnis' && (await istGitRepo(ort.pfad)))
|
|
38
|
+
await gitignoreEintragen(ort.pfad, GITIGNORE_ZEILE);
|
|
39
|
+
}
|
|
40
|
+
folgtLinks() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -120,8 +120,9 @@ class ClaudePlugins {
|
|
|
120
120
|
const { scope, cwd } = this.scope(ziel);
|
|
121
121
|
await this.rufen(['plugin', 'uninstall', pluginSchluessel({ name: plugin, marketplace }), '--scope', scope], cwd);
|
|
122
122
|
}
|
|
123
|
+
/** Ohne Namen aktualisiert die CLI alle Marketplaces. Je Plugin verlangt sie einen Namen, das übernimmt hmgr nicht (E14). */
|
|
123
124
|
async aktualisieren() {
|
|
124
|
-
await this.rufen(['plugin', 'update']);
|
|
125
|
+
await this.rufen(['plugin', 'marketplace', 'update']);
|
|
125
126
|
}
|
|
126
127
|
/**
|
|
127
128
|
* Die Liste gilt nur für das cwd des Aufrufs und enthält dort alle Scopes. Zum Ort gehören im
|
package/dist/adapter/hermes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Hermes Agent: Grundverhalten plus Profile entdecken und Verzeichnis in der config.yaml registrieren.
|
|
2
2
|
// Skills landen nie in <hermes-home>/skills, sondern in einem Ordner aus skills.external_dirs (E13).
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { Document, isSeq, parseDocument } from 'yaml';
|
|
@@ -29,6 +29,23 @@ function externalDirs(daten) {
|
|
|
29
29
|
return [];
|
|
30
30
|
return dirs.filter((d) => typeof d === 'string');
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Liest skills.trusted_project_dirs aus einer geparsten config.yaml. Hermes nimmt dort auch einen
|
|
34
|
+
* einzelnen String, der zählt wie eine Liste mit einem Eintrag.
|
|
35
|
+
*/
|
|
36
|
+
function trustedProjectDirs(daten) {
|
|
37
|
+
if (typeof daten !== 'object' || daten === null)
|
|
38
|
+
return [];
|
|
39
|
+
const skills = daten['skills'];
|
|
40
|
+
if (typeof skills !== 'object' || skills === null)
|
|
41
|
+
return [];
|
|
42
|
+
const dirs = skills['trusted_project_dirs'];
|
|
43
|
+
if (typeof dirs === 'string')
|
|
44
|
+
return [dirs];
|
|
45
|
+
if (!Array.isArray(dirs))
|
|
46
|
+
return [];
|
|
47
|
+
return dirs.filter((d) => typeof d === 'string');
|
|
48
|
+
}
|
|
32
49
|
export class Hermes {
|
|
33
50
|
name = 'hermes';
|
|
34
51
|
home;
|
|
@@ -125,6 +142,69 @@ export class Hermes {
|
|
|
125
142
|
folgtLinks() {
|
|
126
143
|
return true;
|
|
127
144
|
}
|
|
145
|
+
/** Vergleich wie bei Hermes über den aufgelösten Pfad, Symlinks eingeschlossen. */
|
|
146
|
+
echterPfad(pfad) {
|
|
147
|
+
try {
|
|
148
|
+
return realpathSync(path.resolve(pfad));
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return path.resolve(pfad);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Steht die Wurzel schon in `skills.trusted_project_dirs` der übergebenen config.yaml. */
|
|
155
|
+
vertraut(daten, hermesHome, wurzel) {
|
|
156
|
+
for (const eintrag of trustedProjectDirs(daten)) {
|
|
157
|
+
const pfad = this.aufloesen(eintrag, hermesHome);
|
|
158
|
+
if (pfad !== undefined && this.echterPfad(pfad) === wurzel)
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fähigkeit Hinweis nach dem Link (E34): Hermes lädt `<wurzel>/.hermes/skills` nur, wenn die
|
|
165
|
+
* Wurzel in `skills.trusted_project_dirs` der config.yaml steht. hmgr trägt sie nicht von sich
|
|
166
|
+
* aus ein, sondern bietet es als Abhilfe an, entscheiden soll der Nutzer. Maßgeblich ist das
|
|
167
|
+
* Hermes-Home, ein Profil kennt der Ort Arbeitsverzeichnis nicht. Relative Einträge löst Hermes
|
|
168
|
+
* gegen das cwd auf, hmgr wie überall gegen das Hermes-Home, in der Praxis stehen dort absolute
|
|
169
|
+
* Pfade.
|
|
170
|
+
*/
|
|
171
|
+
async hinweisNachLink(ort) {
|
|
172
|
+
if (ort.art !== 'arbeitsverzeichnis')
|
|
173
|
+
return undefined;
|
|
174
|
+
const hermesHome = path.join(this.home, HERMES_VERZEICHNIS);
|
|
175
|
+
const wurzel = ort.pfad;
|
|
176
|
+
if (this.vertraut(this.gelesen(hermesHome), hermesHome, this.echterPfad(wurzel)))
|
|
177
|
+
return undefined;
|
|
178
|
+
return {
|
|
179
|
+
hinweis: `Hermes lädt Projekt-Skills in ${wurzel} erst nach \`hermes skills trust ${wurzel}\`.`,
|
|
180
|
+
abhilfe: {
|
|
181
|
+
frage: `Hermes lädt Projekt-Skills in ${wurzel} erst nach Vertrauen. Jetzt als vertrauenswürdig eintragen? [j/N]`,
|
|
182
|
+
erledigt: `${wurzel} als vertrauenswürdig in Hermes eingetragen.`,
|
|
183
|
+
ausfuehren: async () => await this.vertrauen(wurzel),
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Trägt die Wurzel in `skills.trusted_project_dirs` der config.yaml des Hermes-Home ein, falls
|
|
189
|
+
* sie fehlt. Geschrieben wird der aufgelöste absolute Pfad, so vergleicht Hermes selbst
|
|
190
|
+
* (HARNESS-FAKTEN). Kommentare bleiben erhalten, ein einzelner String wird zur Liste.
|
|
191
|
+
*/
|
|
192
|
+
async vertrauen(wurzel) {
|
|
193
|
+
const hermesHome = path.join(this.home, HERMES_VERZEICHNIS);
|
|
194
|
+
const pfad = path.join(hermesHome, CONFIG);
|
|
195
|
+
const text = await readFile(pfad, 'utf8').catch(() => undefined);
|
|
196
|
+
const doc = text === undefined ? new Document({ skills: { trusted_project_dirs: [] } }) : parseDocument(text);
|
|
197
|
+
const eintrag = this.echterPfad(wurzel);
|
|
198
|
+
if (this.vertraut(doc.toJS(), hermesHome, eintrag))
|
|
199
|
+
return;
|
|
200
|
+
const liste = doc.getIn(['skills', 'trusted_project_dirs']);
|
|
201
|
+
if (isSeq(liste))
|
|
202
|
+
liste.add(eintrag);
|
|
203
|
+
else
|
|
204
|
+
doc.setIn(['skills', 'trusted_project_dirs'], [...trustedProjectDirs(doc.toJS()), eintrag]);
|
|
205
|
+
await mkdir(hermesHome, { recursive: true });
|
|
206
|
+
await writeFile(pfad, doc.toString());
|
|
207
|
+
}
|
|
128
208
|
/** Fähigkeit Profile entdecken: Verzeichnisse mit config.yaml, bei jedem Aufruf frisch gelesen (E19). */
|
|
129
209
|
async profile() {
|
|
130
210
|
const basis = path.join(this.home, HERMES_VERZEICHNIS, PROFILE_VERZEICHNIS);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Rückfrage auf dem Terminal. Bleibt dünn: eine Weiche, eine gelesene Zeile, kein eigenes Urteil.
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
/** Antworten, die als Ja zählen. Alles andere, auch die leere Eingabe, ist Nein. */
|
|
4
|
+
const JA = new Set(['j', 'ja']);
|
|
5
|
+
export class TerminalRueckfrage {
|
|
6
|
+
/**
|
|
7
|
+
* Ohne Terminal auf beiden Seiten gibt es niemanden, der antworten könnte: hmgr läuft dann in
|
|
8
|
+
* einem Composer-Script, einer Pipe oder auf einem Deployment-Rechner (E20). Eine Frage ginge
|
|
9
|
+
* dort ins Leere und das Lesen von stdin bliebe hängen oder liefe sofort auf das Dateiende. Also
|
|
10
|
+
* gilt Nein, ohne Ausgabe: der Aufrufer bekommt seinen Hinweis und niemand wartet.
|
|
11
|
+
*/
|
|
12
|
+
async bestaetigen(frage) {
|
|
13
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
|
|
14
|
+
return false;
|
|
15
|
+
const leser = createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
+
try {
|
|
17
|
+
const antwort = await leser.question(`${frage} `);
|
|
18
|
+
return JA.has(antwort.trim().toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
leser.close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/cli/main.js
CHANGED
|
@@ -6,12 +6,14 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
import { Anwendungsfaelle } from "../kern/anwendungsfaelle.js";
|
|
8
8
|
import { AbhaengigkeitFehlt, EinschraenkungOhneZeiger, HarnessNichtVorhanden, KeinPassenderRef, NameBelegt, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, SkillMehrdeutig, SkillUnbekannt, ZeigerMehrdeutig, pluginSchluessel, } from "../kern/modell.js";
|
|
9
|
+
import { Agents } from "../adapter/agents.js";
|
|
9
10
|
import { ClaudeCode } from "../adapter/claude-code.js";
|
|
10
11
|
import { Hermes } from "../adapter/hermes.js";
|
|
11
12
|
import { ComposerZeiger } from "../adapter/composer.js";
|
|
12
13
|
import { DateisystemZiele } from "../adapter/dateisystem.js";
|
|
13
14
|
import { GitQuellen } from "../adapter/git.js";
|
|
14
15
|
import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
|
|
16
|
+
import { TerminalRueckfrage } from "../adapter/terminal.js";
|
|
15
17
|
import { ZustandJson } from "../adapter/zustand-json.js";
|
|
16
18
|
export function umgebungLesen(env = process.env) {
|
|
17
19
|
const home = env.HOME ?? homedir();
|
|
@@ -24,11 +26,12 @@ export function bauen(umgebung, optionen = {}) {
|
|
|
24
26
|
? new ClaudeCode(umgebung.home)
|
|
25
27
|
: new ClaudeCode(umgebung.home, optionen.claudeAusfuehrer);
|
|
26
28
|
const ports = {
|
|
27
|
-
harnesses: [claude, new Hermes(umgebung.home)],
|
|
29
|
+
harnesses: [claude, new Hermes(umgebung.home), new Agents(umgebung.home)],
|
|
28
30
|
quellen: new GitQuellen(wurzel),
|
|
29
31
|
ziele: new DateisystemZiele(wurzel, zustand),
|
|
30
32
|
zeiger: new ComposerZeiger(),
|
|
31
33
|
zustand,
|
|
34
|
+
rueckfrage: optionen.rueckfrage ?? new TerminalRueckfrage(),
|
|
32
35
|
};
|
|
33
36
|
return { anwendungsfaelle: new Anwendungsfaelle(ports), ports };
|
|
34
37
|
}
|
|
@@ -83,7 +86,7 @@ const HILFE = `hmgr <kommando>
|
|
|
83
86
|
update Alle Checkouts aktualisieren
|
|
84
87
|
status Ist-Zustand der Ziele
|
|
85
88
|
|
|
86
|
-
--harness <name> claude-code oder
|
|
89
|
+
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
87
90
|
--ort home|dir:<pfad>|profil:<name> Standard home
|
|
88
91
|
--quelle <teilstring> Quelle bei mehrdeutigem Skill-Namen
|
|
89
92
|
--version <spanne> Versionseinschränkung nach Composer-Regeln, nur mit Zeiger (E33)
|
|
@@ -292,8 +295,10 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
292
295
|
console.log(` Skills: ${ergebnis.skills.join(', ')}`);
|
|
293
296
|
if (ergebnis.imZeiger)
|
|
294
297
|
console.log(' im Zeiger vermerkt');
|
|
298
|
+
// Ein Hinweis kann mehrzeilig sein, jede Zeile bekommt dieselbe Einrückung.
|
|
295
299
|
if (ergebnis.hinweis !== undefined)
|
|
296
|
-
|
|
300
|
+
for (const zeile of ergebnis.hinweis.split('\n'))
|
|
301
|
+
console.log(` ${zeile}`);
|
|
297
302
|
return 0;
|
|
298
303
|
}
|
|
299
304
|
if (kommando === 'sync') {
|
|
@@ -11,6 +11,8 @@ function fehlertext(fehler) {
|
|
|
11
11
|
}
|
|
12
12
|
export class Anwendungsfaelle {
|
|
13
13
|
p;
|
|
14
|
+
/** Schon beantwortete Orte, je Ziel der Hinweis, der davon übrig blieb (E34). */
|
|
15
|
+
beantwortet = new Map();
|
|
14
16
|
constructor(p) {
|
|
15
17
|
this.p = p;
|
|
16
18
|
}
|
|
@@ -280,19 +282,47 @@ export class Anwendungsfaelle {
|
|
|
280
282
|
const fund = await this.fundAusZeiger(args.skill, version, args.quelle);
|
|
281
283
|
const link = await this.linkAnlegen(harness, args.ziel, fund, args.ersetzen);
|
|
282
284
|
await this.zeigerErgaenzen(zeigerPfad, fund, version);
|
|
283
|
-
|
|
285
|
+
const hinweis = await this.harnessHinweis(harness, args.ziel.ort);
|
|
286
|
+
return hinweis === undefined ? link : { ...link, hinweis };
|
|
284
287
|
}
|
|
285
288
|
/** Link legen, wenn Quelle, Ref und Skill schon feststehen. Die Zeiger-Arbeit ist dieselbe. */
|
|
286
289
|
async linkAusFund(harness, ziel, fund, ersetzen, lokal) {
|
|
287
290
|
const link = await this.linkAnlegen(harness, ziel, fund, ersetzen);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
const hinweise = [];
|
|
292
|
+
if (lokal !== true && ziel.ort.art === 'arbeitsverzeichnis') {
|
|
293
|
+
const zeigerPfad = await this.bindungFinden(ziel);
|
|
294
|
+
// Ziel ohne Zeiger: der Link wird trotzdem gelegt, hmgr weist darauf hin (E26).
|
|
295
|
+
if (zeigerPfad === undefined)
|
|
296
|
+
hinweise.push(`Ohne Zeiger bleibt „${link.name}" lokal, der Link ist gelegt (E26).`);
|
|
297
|
+
else
|
|
298
|
+
await this.zeigerErgaenzen(zeigerPfad, fund);
|
|
299
|
+
}
|
|
300
|
+
const vomHarness = await this.harnessHinweis(harness, ziel.ort);
|
|
301
|
+
if (vomHarness !== undefined)
|
|
302
|
+
hinweise.push(vomHarness);
|
|
303
|
+
return hinweise.length === 0 ? link : { ...link, hinweis: hinweise.join('\n') };
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Offener Punkt des Harness zu einem Ort (E34), etwa fehlendes Vertrauen für Projekt-Skills. Ein
|
|
307
|
+
* Harness ohne diese Fähigkeit schweigt. Nennt der Harness eine Abhilfe, fragt hmgr den Nutzer:
|
|
308
|
+
* bei Ja leistet der Harness sie und die Ergebniszeile tritt an die Stelle des Hinweises, bei
|
|
309
|
+
* Nein bleibt es beim Hinweis. Je Lauf und Ziel höchstens eine Frage, die Antwort gilt für den
|
|
310
|
+
* ganzen Lauf.
|
|
311
|
+
*/
|
|
312
|
+
async harnessHinweis(harness, ort) {
|
|
313
|
+
const schluessel = zielSchluessel({ harness: harness.name, ort });
|
|
314
|
+
if (this.beantwortet.has(schluessel))
|
|
315
|
+
return this.beantwortet.get(schluessel);
|
|
316
|
+
const punkt = await harness.hinweisNachLink?.(ort);
|
|
317
|
+
let hinweis = punkt?.hinweis;
|
|
318
|
+
const abhilfe = punkt?.abhilfe;
|
|
319
|
+
if (abhilfe !== undefined && (await this.p.rueckfrage.bestaetigen(abhilfe.frage))) {
|
|
320
|
+
await abhilfe.ausfuehren();
|
|
321
|
+
hinweis = abhilfe.erledigt;
|
|
322
|
+
}
|
|
323
|
+
if (punkt !== undefined)
|
|
324
|
+
this.beantwortet.set(schluessel, hinweis);
|
|
325
|
+
return hinweis;
|
|
296
326
|
}
|
|
297
327
|
/** Link entfernen. Ohne `lokal` verschwindet der Skill auch aus dem Zeiger (E17). */
|
|
298
328
|
async linkEntfernen(args) {
|
|
@@ -325,7 +355,15 @@ export class Anwendungsfaelle {
|
|
|
325
355
|
const skills = await this.flacheSkillListe(fund.plugin.name, fund.quelle);
|
|
326
356
|
for (const skill of skills)
|
|
327
357
|
await this.linkAnlegen(harness, ziel, { quelle: fund.quelle, ref: fund.ref, skill });
|
|
328
|
-
|
|
358
|
+
// Der Hinweis des Harness gilt dem Ort, also einmal für das ganze Plugin, nicht je Skill (E34).
|
|
359
|
+
const hinweis = await this.harnessHinweis(harness, ziel.ort);
|
|
360
|
+
return {
|
|
361
|
+
plugin: fund.plugin,
|
|
362
|
+
art: 'verlinkt',
|
|
363
|
+
skills: skills.map((s) => s.name),
|
|
364
|
+
imZeiger: false,
|
|
365
|
+
...(hinweis === undefined ? {} : { hinweis }),
|
|
366
|
+
};
|
|
329
367
|
}
|
|
330
368
|
/**
|
|
331
369
|
* Plugin legen (E16, E17, E24, E27). In ein Arbeitsverzeichnis mit Zeiger ist das Plugin
|
|
@@ -338,8 +376,11 @@ export class Anwendungsfaelle {
|
|
|
338
376
|
if (args.lokal === true || args.ziel.ort.art !== 'arbeitsverzeichnis')
|
|
339
377
|
return ergebnis;
|
|
340
378
|
const zeigerPfad = await this.bindungFinden(args.ziel);
|
|
341
|
-
if (zeigerPfad === undefined)
|
|
342
|
-
|
|
379
|
+
if (zeigerPfad === undefined) {
|
|
380
|
+
const ohneZeiger = `Ohne Zeiger bleibt „${args.plugin}" lokal, das Plugin liegt am Ziel (E26).`;
|
|
381
|
+
const hinweis = ergebnis.hinweis === undefined ? ohneZeiger : `${ohneZeiger}\n${ergebnis.hinweis}`;
|
|
382
|
+
return { ...ergebnis, hinweis };
|
|
383
|
+
}
|
|
343
384
|
const zeiger = await this.p.zeiger.lesen(zeigerPfad);
|
|
344
385
|
const key = quelleSchluessel(fund.quelle);
|
|
345
386
|
if (!zeiger.quellen.some((q) => quelleSchluessel(q) === key))
|
|
@@ -540,6 +581,10 @@ export class Anwendungsfaelle {
|
|
|
540
581
|
continue;
|
|
541
582
|
ergebnis.abweichend.push(`${harness.name}: ${name} steht nicht im Zeiger`);
|
|
542
583
|
}
|
|
584
|
+
// Hinweis des Harness zum Ort, einmal je Harness und Lauf, nicht je Skill (E34).
|
|
585
|
+
const vomHarness = await this.harnessHinweis(harness, ziel.ort);
|
|
586
|
+
if (vomHarness !== undefined)
|
|
587
|
+
ergebnis.hinweise.push(vomHarness);
|
|
543
588
|
}
|
|
544
589
|
return ergebnis;
|
|
545
590
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hmgr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tim Kaufmann",
|
|
@@ -31,5 +31,9 @@
|
|
|
31
31
|
"@types/node": "^24.0.0",
|
|
32
32
|
"typescript": "^5.9.0",
|
|
33
33
|
"vitest": "^3.2.0"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://gitea.timkaufmann.de/tim/hmgr.git"
|
|
34
38
|
}
|
|
35
39
|
}
|