hmgr 0.0.47 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ // Atomares Schreiben für Zustandsdateien (state.json, verlauf.json): Inhalt in eine Temp-Datei im
2
+ // selben Verzeichnis, dann rename über das Ziel. Ein Leser (GUI-Intervall, CLI in einer anderen
3
+ // Sitzung) sieht so immer den alten oder den neuen vollständigen Inhalt, nie eine leere oder halbe Datei.
4
+ import { randomBytes } from 'node:crypto';
5
+ import { rename, rm, writeFile } from 'node:fs/promises';
6
+ /** Unter Windows scheitert rename über eine Datei, die ein anderer Prozess gerade offen hat, mit diesen Codes. */
7
+ const WIEDERHOLBAR = new Set(['EPERM', 'EACCES', 'EBUSY']);
8
+ const VERSUCHE = 5;
9
+ async function warten(ms) {
10
+ await new Promise((r) => setTimeout(r, ms));
11
+ }
12
+ export async function atomarSchreiben(ziel, inhalt) {
13
+ const temp = `${ziel}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
14
+ try {
15
+ await writeFile(temp, inhalt);
16
+ for (let versuch = 1;; versuch++) {
17
+ try {
18
+ await rename(temp, ziel);
19
+ return;
20
+ }
21
+ catch (fehler) {
22
+ const code = fehler.code;
23
+ if (versuch >= VERSUCHE || code === undefined || !WIEDERHOLBAR.has(code))
24
+ throw fehler;
25
+ await warten(20 * versuch);
26
+ }
27
+ }
28
+ }
29
+ catch (fehler) {
30
+ await rm(temp, { force: true });
31
+ throw fehler;
32
+ }
33
+ }
@@ -1,8 +1,9 @@
1
1
  // Der Verlauf als eine JSON-Datei, Standard ~/.hmgr/verlauf.json (E80). Analog ZustandJson
2
2
  // (zustand-json.ts), aber ohne Fassungs-Migration: das Format ist neu, `fassung: 1` reicht.
3
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { mkdir, readFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { leererVerlauf } from "../ports/verlauf.js";
6
+ import { atomarSchreiben } from "./atomar-schreiben.js";
6
7
  import { ohneBom } from "./datei-text.js";
7
8
  /** Höchstzahl der Einträge in der Datei (E80): ein Changelog, keine Historie ohne Ende. */
8
9
  const MAX_EINTRAEGE = 20;
@@ -34,6 +35,6 @@ export class VerlaufJson {
34
35
  await mkdir(path.dirname(this.pfad), { recursive: true });
35
36
  // Neueste zuerst kappen, nicht sortieren: die Reihenfolge liegt beim Aufrufer.
36
37
  const gekappt = { gemerkt: daten.gemerkt, eintraege: daten.eintraege.slice(0, MAX_EINTRAEGE) };
37
- await writeFile(this.pfad, `${JSON.stringify({ fassung: 1, ...gekappt }, null, 2)}\n`);
38
+ await atomarSchreiben(this.pfad, `${JSON.stringify({ fassung: 1, ...gekappt }, null, 2)}\n`);
38
39
  }
39
40
  }
@@ -1,8 +1,9 @@
1
1
  // Lokaler Zustand als eine JSON-Datei, Standard ~/.hmgr/state.json (E29).
2
2
  import { readFileSync } from 'node:fs';
3
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { mkdir, readFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { ZUSTAND_FASSUNG, leererZustand, } from "../ports/zustand.js";
6
+ import { atomarSchreiben } from "./atomar-schreiben.js";
6
7
  import { ohneBom } from "./datei-text.js";
7
8
  /** Der gelesene Text als Zustand. Fehlende Felder bekommen den Wert, der dem Bestand entspricht. */
8
9
  function deuten(roh) {
@@ -54,6 +55,6 @@ export class ZustandJson {
54
55
  await mkdir(path.dirname(this.pfad), { recursive: true });
55
56
  // Immer in der aktuellen Fassung, egal welche gelesen wurde: das Lesen hat den Bestand schon
56
57
  // auf die aktuelle Form gebracht, ein alter Vermerk in der Datei wäre danach eine Lüge.
57
- await writeFile(this.pfad, `${JSON.stringify({ ...zustand, fassung: ZUSTAND_FASSUNG }, null, 2)}\n`);
58
+ await atomarSchreiben(this.pfad, `${JSON.stringify({ ...zustand, fassung: ZUSTAND_FASSUNG }, null, 2)}\n`);
58
59
  }
59
60
  }
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "fassung": 1,
3
3
  "versionen": {
4
+ "0.0.48": [
5
+ "Die Meldung „Unexpected end of JSON input“ taucht nicht mehr auf, wenn hmgr an mehreren Stellen gleichzeitig läuft, etwa die Oberfläche und die Kommandozeile: hmgr schreibt seinen Zustand und den Verlauf jetzt so, dass nie eine halb geschriebene Datei gelesen wird."
6
+ ],
4
7
  "0.0.47": [
5
8
  "Skills mit SOPS-verschlüsselten Dateien kommen jetzt entschlüsselt an, wenn sops, der passende Schlüssel und sops-clean.sh vorhanden sind; bereits verschlüsselt ausgecheckte Dateien repariert hmgr beim nächsten Aktualisieren. Klappt das nicht, bleibt die Datei verschlüsselt und hmgr sagt im Verlauf und in hmgr status, warum."
6
9
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.47",
3
+ "version": "0.0.48",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",