From e998784ac6c5092a6170e0e643324e814f447502 Mon Sep 17 00:00:00 2001 From: Kosmos Date: Mon, 20 Apr 2026 21:13:27 +0000 Subject: [PATCH] Release 0.0.7 --- lang/de.json | 53 ++++++++++++++++++++--------- lang/en.json | 19 +++++++++++ module.json | 2 +- scripts/adapters/foundry-runtime.js | 3 +- scripts/core/analyzer.js | 20 ++++++----- scripts/core/finding-engine.js | 34 +++++++++++------- 6 files changed, 92 insertions(+), 39 deletions(-) diff --git a/lang/de.json b/lang/de.json index de954b8..bd8f941 100644 --- a/lang/de.json +++ b/lang/de.json @@ -4,19 +4,19 @@ "Menu": { "Name": "Kosmos Storage Audit", "Label": "Report öffnen", - "Hint": "Die Storage-Analyse ausführen und priorisierte Findings prüfen." + "Hint": "Die Speicher-Analyse ausführen und priorisierte Funde prüfen." }, "Hero": { "Title": "Kosmos Storage Audit", "Intro1": "Prüft Medienreferenzen und markiert primär benutzerrelevante Risiken in den Foundry-Roots {dataRoot} und {publicRoot}.", "Intro2": "Orphans werden bewusst nicht global für den gesamten {dataRoot}-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.", - "Intro3": "Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im owning Paket selbst nicht sichtbar referenziert werden." + "Intro3": "Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im besitzenden Paket selbst nicht sichtbar referenziert werden." }, "Action": { "Run": "Analyse starten", "Running": "Analysiere...", - "Export": "Report exportieren", - "ShowAll": "Alle Findings", + "Export": "Bericht exportieren", + "ShowAll": "Alle Funde", "ShowWarnings": "Nur Warnungen", "ShowRaw": "Einzelfälle anzeigen", "HideRaw": "Einzelfälle ausblenden" @@ -24,16 +24,20 @@ "Progress": { "Initialize": "Initialisiere Analyse", "Analyzing": "Analysiere", + "ScanFiles": "Scanne Dateien", + "ReadReferences": "Lese Referenzen", + "ClassifyFindings": "Klassifiziere Funde", + "Completed": "Analyse abgeschlossen", "Current": "Aktuell: {source}", "Files": "Dateien: {count}", "Sources": "Quellen: {count}", "References": "Referenzen: {count}", - "Findings": "Findings: {count}", + "Findings": "Funde: {count}", "BrowseStorage": "Durchsuche {storage}:{path}", "ReadPack": "Lese Paket-Pack {pack}" }, "Notify": { - "Completed": "Kosmos Storage Audit abgeschlossen: {count} Findings.", + "Completed": "Kosmos Storage Audit abgeschlossen: {count} Funde.", "Failed": "Kosmos Storage Audit fehlgeschlagen: {message}", "OpenSourceFailed": "Die Quelle konnte nicht geöffnet werden: {uuid}" }, @@ -41,11 +45,11 @@ "NoAnalysis": "Noch keine Analyse ausgeführt.", "Files": "Dateien", "References": "Referenzen", - "Findings": "Findings", - "High": "High", - "Warning": "Warning", - "ByType": "Findings nach Typ", - "NoFindings": "Keine Findings", + "Findings": "Funde", + "High": "Hoch", + "Warning": "Warnung", + "ByType": "Funde nach Typ", + "NoFindings": "Keine Funde", "WorkBlocks": "Arbeitsblöcke", "MissingTargets": "Deduplizierte fehlende Ziele", "UnanchoredPackageTargets": "Unverankerte Paketziele", @@ -53,21 +57,21 @@ }, "Section": { "WorkView": "Arbeitsansicht", - "NoGrouped": "Keine gruppierten {scope}Findings vorhanden.", + "NoGrouped": "Keine gruppierten {scope}Funde vorhanden.", "NoPrompt": "Die Analyse kann direkt aus dieser Ansicht gestartet werden.", "Running": "Analyse läuft...", - "NoRaw": "Keine {scope}Findings gefunden.", + "NoRaw": "Keine {scope}Funde gefunden.", "RawEntries": "Einzelfälle", "Samples": "Beispiele", "UnanchoredPackageTargets": "Unverankerte Paketziele", - "UnanchoredPackageTargetsDesc": "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im owning Paket selbst aber derzeit nicht als Referenz sichtbar.", + "UnanchoredPackageTargetsDesc": "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im besitzenden Paket selbst aber derzeit nicht als Referenz sichtbar.", "BrokenTargets": "Kaputte Ziele", "BrokenTargetsDesc": "Diese Dateien fehlen, werden aber weiterhin referenziert.", "OrphanCandidates": "Orphan-Kandidaten", "OrphanCandidatesDesc": "Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz." }, "Field": { - "OwnerPackage": "Owner-Paket", + "OwnerPackage": "Besitzendes Paket", "Assessment": "Bewertung", "Target": "Ziel", "Source": "Quelle" @@ -83,10 +87,25 @@ "WildcardNoMatch": "Musterreferenz: Wildcard wird von Foundry unterstützt, derzeit aber ohne passenden Treffer im Dateisystem." }, "Severity": { - "high": "High", - "warning": "Warning", + "high": "Hoch", + "warning": "Warnung", "info": "Info" }, + "FindingReason": { + "BrokenWildcard": "Die Wildcard-Referenz {locator} passt auf keine Datei in den gescannten Roots.", + "BrokenReference": "Die referenzierte Datei {locator} existiert in den gescannten Roots nicht.", + "UnanchoredPackageTarget": "{sourceOwner} verweist auf paketgebundenen Speicher {locator}, aber das Asset ist im besitzenden Paket selbst nicht sichtbar referenziert.", + "RiskyPublicTarget": "{sourceOwner} verweist auf release-nahen Public-Speicher {locator}.", + "OrphanFile": "Für {locator} wurde keine eingehende Medienreferenz gefunden." + }, + "FindingRecommendation": { + "CheckWildcard": "Prüfe, ob das Wildcard-Muster noch korrekt ist und ob passende Dateien noch vorhanden sind.", + "CheckMissingFile": "Prüfe, ob die Datei verschoben, gelöscht oder an einen stabilen Ort kopiert werden sollte.", + "MoveToStableStorage": "Prüfe, ob die Datei manuell in den Paketordner gelegt wurde. Falls das Paket sie selbst nicht nutzt, verschiebe sie bevorzugt in benutzerkontrollierten Speicher.", + "CopyToStableStorage": "Kopiere das Asset bevorzugt in Welt- oder benutzerkontrollierten Speicher.", + "ReviewOrMoveOrphan": "Prüfe, ob die Datei sicher entfernt oder an einen stabilen Speicherort verschoben werden sollte.", + "KeepAsReserve": "Prüfe, ob die Datei absichtlich als Reserveinhalt vorgehalten wird." + }, "Owner": { "Unknown": "unbekannt" }, diff --git a/lang/en.json b/lang/en.json index bb9cfc2..1a3d4ca 100644 --- a/lang/en.json +++ b/lang/en.json @@ -24,6 +24,10 @@ "Progress": { "Initialize": "Initializing analysis", "Analyzing": "Analyzing", + "ScanFiles": "Scanning files", + "ReadReferences": "Reading references", + "ClassifyFindings": "Classifying findings", + "Completed": "Analysis complete", "Current": "Current: {source}", "Files": "Files: {count}", "Sources": "Sources: {count}", @@ -87,6 +91,21 @@ "warning": "Warning", "info": "Info" }, + "FindingReason": { + "BrokenWildcard": "Wildcard reference {locator} did not match any files in the scanned roots.", + "BrokenReference": "Referenced file {locator} does not exist in the scanned roots.", + "UnanchoredPackageTarget": "{sourceOwner} references package-owned storage {locator}, but the asset is not visibly referenced by its owning package.", + "RiskyPublicTarget": "{sourceOwner} references release-public storage {locator}.", + "OrphanFile": "No incoming media reference was found for {locator}." + }, + "FindingRecommendation": { + "CheckWildcard": "Check whether the wildcard pattern is still correct and whether matching files still exist.", + "CheckMissingFile": "Check whether the file was moved, deleted, or should be copied into a stable location.", + "MoveToStableStorage": "Review whether the file was manually placed into the package folder. If the package does not use it itself, prefer moving it into user-controlled storage.", + "CopyToStableStorage": "Prefer copying the asset into world or user-controlled storage.", + "ReviewOrMoveOrphan": "Review whether the file is safe to remove or should be moved into a stable storage location.", + "KeepAsReserve": "Review whether the file is intentionally kept as reserve content." + }, "Owner": { "Unknown": "unknown" }, diff --git a/module.json b/module.json index c64b8e1..450d4de 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.6", + "version": "0.0.7", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/adapters/foundry-runtime.js b/scripts/adapters/foundry-runtime.js index af73279..2292c77 100644 --- a/scripts/adapters/foundry-runtime.js +++ b/scripts/adapters/foundry-runtime.js @@ -111,7 +111,8 @@ export async function runRuntimeAnalysis({ onProgress }={}) { return analyzeStorage({ listFiles: () => listFoundryFiles(onProgress), listSources: () => listFoundrySources(onProgress), - onProgress + onProgress, + i18n: { format } }); } diff --git a/scripts/core/analyzer.js b/scripts/core/analyzer.js index 1a58dd4..857326d 100644 --- a/scripts/core/analyzer.js +++ b/scripts/core/analyzer.js @@ -2,24 +2,24 @@ import { buildFindings, createFileRecord } from "./finding-engine.js"; import { extractReferencesFromValue } from "./reference-extractor.js"; import { createFileLocator } from "./path-utils.js"; -export async function analyzeStorage({ listFiles, listSources, onProgress }={}) { +export async function analyzeStorage({ listFiles, listSources, onProgress, i18n }={}) { const files = []; let fileCount = 0; let sourceCount = 0; let referenceCount = 0; - onProgress?.({ phase: "files", label: "Scanne Dateien", files: 0, sources: 0, references: 0 }); + onProgress?.({ phase: "files", label: format(i18n, "KSA.Progress.ScanFiles"), files: 0, sources: 0, references: 0 }); for await (const entry of listFiles()) { files.push(createFileRecord(createFileLocator(entry.storage, entry.path), entry.size ?? null)); fileCount += 1; if ((fileCount % 100) === 0) { - onProgress?.({ phase: "files", label: "Scanne Dateien", files: fileCount, sources: sourceCount, references: referenceCount }); + onProgress?.({ phase: "files", label: format(i18n, "KSA.Progress.ScanFiles"), files: fileCount, sources: sourceCount, references: referenceCount }); await yieldToUI(); } } const references = []; - onProgress?.({ phase: "sources", label: "Lese Referenzen", files: fileCount, sources: 0, references: 0 }); + onProgress?.({ phase: "sources", label: format(i18n, "KSA.Progress.ReadReferences"), files: fileCount, sources: 0, references: 0 }); for await (const source of listSources()) { sourceCount += 1; const extracted = extractReferencesFromValue(source.value, { @@ -34,7 +34,7 @@ export async function analyzeStorage({ listFiles, listSources, onProgress }={}) if ((sourceCount % 50) === 0) { onProgress?.({ phase: "sources", - label: "Lese Referenzen", + label: format(i18n, "KSA.Progress.ReadReferences"), files: fileCount, sources: sourceCount, references: referenceCount, @@ -44,11 +44,11 @@ export async function analyzeStorage({ listFiles, listSources, onProgress }={}) } } - onProgress?.({ phase: "findings", label: "Klassifiziere Findings", files: fileCount, sources: sourceCount, references: referenceCount }); - const findings = buildFindings({ files, references }); + onProgress?.({ phase: "findings", label: format(i18n, "KSA.Progress.ClassifyFindings"), files: fileCount, sources: sourceCount, references: referenceCount }); + const findings = buildFindings({ files, references, i18n }); onProgress?.({ phase: "done", - label: "Analyse abgeschlossen", + label: format(i18n, "KSA.Progress.Completed"), files: fileCount, sources: sourceCount, references: referenceCount, @@ -60,3 +60,7 @@ export async function analyzeStorage({ listFiles, listSources, onProgress }={}) async function yieldToUI() { await new Promise(resolve => setTimeout(resolve, 0)); } + +function format(i18n, key, data = {}) { + return i18n?.format?.(key, data) ?? key; +} diff --git a/scripts/core/finding-engine.js b/scripts/core/finding-engine.js index 08fdbd4..9730e5d 100644 --- a/scripts/core/finding-engine.js +++ b/scripts/core/finding-engine.js @@ -32,7 +32,7 @@ function compareOwner(sourceScope, targetOwner) { return "allowed"; } -export function buildFindings({ files, references }) { +export function buildFindings({ files, references, i18n }={}) { const fileByLocator = new Map(files.map(file => [createCanonicalLocator(file.storage, file.path), file])); const fileLocators = files.map(file => ({ storage: file.storage, @@ -71,11 +71,11 @@ export function buildFindings({ files, references }) { target: { ...normalized, locator: normalizedLocator }, source: reference, reason: normalized.targetKind === "wildcard" - ? `Wildcard reference ${normalized.locator} did not match any files in the scanned roots.` - : `Referenced file ${normalized.locator} does not exist in the scanned roots.`, + ? format(i18n, "KSA.FindingReason.BrokenWildcard", { locator: normalized.locator }) + : format(i18n, "KSA.FindingReason.BrokenReference", { locator: normalized.locator }), recommendation: normalized.targetKind === "wildcard" - ? "Check whether the wildcard pattern is still correct and whether matching files still exist." - : "Check whether the file was moved, deleted, or should be copied into a stable location.", + ? format(i18n, "KSA.FindingRecommendation.CheckWildcard") + : format(i18n, "KSA.FindingRecommendation.CheckMissingFile"), confidence: "high" }); continue; @@ -89,8 +89,11 @@ export function buildFindings({ files, references }) { severity: "high", target: { ...normalized, locator: normalizedLocator }, source: reference, - reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references package-owned storage ${normalized.locator}, but the asset is not visibly referenced by its owning package.`, - recommendation: "Review whether the file was manually placed into the package folder. If the package does not use it itself, prefer moving it into user-controlled storage.", + reason: format(i18n, "KSA.FindingReason.UnanchoredPackageTarget", { + sourceOwner: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId}`, + locator: normalized.locator + }), + recommendation: format(i18n, "KSA.FindingRecommendation.MoveToStableStorage"), confidence: "high" }); } else if (ownerRelation === "risky-public") { @@ -100,8 +103,11 @@ export function buildFindings({ files, references }) { severity: "high", target: { ...normalized, locator: normalizedLocator }, source: reference, - reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references release-public storage ${normalized.locator}.`, - recommendation: "Prefer copying the asset into world or user-controlled storage.", + reason: format(i18n, "KSA.FindingReason.RiskyPublicTarget", { + sourceOwner: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId}`, + locator: normalized.locator + }), + recommendation: format(i18n, "KSA.FindingRecommendation.CopyToStableStorage"), confidence: "high" }); } @@ -122,10 +128,10 @@ export function buildFindings({ files, references }) { severity, target: file, source: null, - reason: `No incoming media reference was found for ${file.locator}.`, + reason: format(i18n, "KSA.FindingReason.OrphanFile", { locator: file.locator }), recommendation: severity === "warning" - ? "Review whether the file is safe to remove or should be moved into a stable storage location." - : "Review whether the file is intentionally kept as reserve content.", + ? format(i18n, "KSA.FindingRecommendation.ReviewOrMoveOrphan") + : format(i18n, "KSA.FindingRecommendation.KeepAsReserve"), confidence: "medium" }); } @@ -194,3 +200,7 @@ export function createFileRecord(locator, size = null) { exists: true }; } + +function format(i18n, key, data = {}) { + return i18n?.format?.(key, data) ?? key; +}